зеркало из https://github.com/nextcloud/forms.git
Multiple uniques & aria
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
Родитель
7aa3f7ace0
Коммит
df69a7a4a3
|
@ -95,7 +95,7 @@ class ApiController extends Controller {
|
|||
|
||||
} catch (DoesNotExistException $e) {
|
||||
//handle silently
|
||||
}finally{
|
||||
} finally {
|
||||
return $optionList;
|
||||
}
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ class ApiController extends Controller {
|
|||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* Read Form-List only with necessary information for Listing.
|
||||
*/
|
||||
public function getForms(): Http\JSONResponse {
|
||||
|
@ -150,7 +151,7 @@ class ApiController extends Controller {
|
|||
}
|
||||
|
||||
$result = $form->read();
|
||||
$result['questions'] = getQuestions();
|
||||
$result['questions'] = $this->getQuestions($id);
|
||||
|
||||
return new Http\JSONResponse($result);
|
||||
}
|
||||
|
@ -241,7 +242,7 @@ class ApiController extends Controller {
|
|||
// Delete Submissions(incl. Answers), Questions(incl. Options) and Form.
|
||||
$this->submissionMapper->deleteByForm($id);
|
||||
$this->questionMapper->deleteByForm($id);
|
||||
$this->formMapper->delete($formToDelete);
|
||||
$this->formMapper->delete($form);
|
||||
|
||||
return new Http\JSONResponse($id);
|
||||
}
|
||||
|
@ -291,10 +292,8 @@ class ApiController extends Controller {
|
|||
|
||||
$question = $this->questionMapper->insert($question);
|
||||
|
||||
$response = [
|
||||
'id' => $question->getId(),
|
||||
'order' => $question->getOrder()
|
||||
];
|
||||
$response = $question->read();
|
||||
$response['options'] = [];
|
||||
|
||||
return new Http\JSONResponse($response);
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ class Version010200Date20200323141300 extends SimpleMigrationStep {
|
|||
]);
|
||||
$table->addColumn('expires_timestamp', Type::INTEGER, [
|
||||
'notnull' => false,
|
||||
'default' => 0,
|
||||
'comment' => 'unix-timestamp',
|
||||
]);
|
||||
$table->addColumn('is_anonymous', Type::BOOLEAN, [
|
||||
|
@ -140,7 +141,7 @@ class Version010200Date20200323141300 extends SimpleMigrationStep {
|
|||
]);
|
||||
$table->addColumn('mandatory', Type::BOOLEAN, [
|
||||
'notnull' => true,
|
||||
'default' => 1,
|
||||
'default' => 0,
|
||||
]);
|
||||
$table->addColumn('text', Type::STRING, [
|
||||
'notnull' => true,
|
||||
|
|
|
@ -1415,11 +1415,11 @@
|
|||
}
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz",
|
||||
"integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==",
|
||||
"version": "7.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
|
||||
"integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.2"
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -1518,13 +1518,6 @@
|
|||
"requires": {
|
||||
"@nextcloud/event-bus": "^1.1.3",
|
||||
"core-js": "^3.6.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
|
||||
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nextcloud/axios": {
|
||||
|
@ -1586,9 +1579,9 @@
|
|||
}
|
||||
},
|
||||
"@nextcloud/event-bus": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.1.4.tgz",
|
||||
"integrity": "sha512-It27KzmUaSQ7w22nHFwOn8XgeVG0HYYOSNG9gs4UkP5VqcZ16m4ydt3GkMpWcyFec4OUjJc+yf7omRc3pNxsSw==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.1.3.tgz",
|
||||
"integrity": "sha512-/f3OMh9Tu3bn17sCc1Sb5AaC/fjegP9bjFmlsPDFNcCAHrKKM5B2X+2eUDF2osLirYaBjVqypBmD87zyiE0WjQ==",
|
||||
"requires": {
|
||||
"@types/semver": "^6.2.1",
|
||||
"core-js": "^3.6.2",
|
||||
|
@ -1596,9 +1589,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
|
||||
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
|
||||
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
|
@ -1614,16 +1607,6 @@
|
|||
"requires": {
|
||||
"core-js": "^3.6.4",
|
||||
"node-gettext": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-gettext": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz",
|
||||
"integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==",
|
||||
"requires": {
|
||||
"lodash.get": "^4.4.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nextcloud/moment": {
|
||||
|
@ -1657,16 +1640,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
<<<<<<< HEAD
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
|
||||
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
|
||||
=======
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.1.tgz",
|
||||
"integrity": "sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ=="
|
||||
>>>>>>> f89f534... fixup! New question ui
|
||||
"node-gettext": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-2.1.0.tgz",
|
||||
"integrity": "sha512-vsHImHl+Py0vB7M2UXcFEJ5NJ3950gcja45YclBFtYxYeZiqdfQdcu+G9s4L7jpRFSh/J/7VoS3upR4JM1nS+g==",
|
||||
"requires": {
|
||||
"lodash.get": "^4.4.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2873,14 +2853,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001040",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz",
|
||||
"integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ=="
|
||||
"version": "1.0.30001042",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz",
|
||||
"integrity": "sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw=="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.403",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.403.tgz",
|
||||
"integrity": "sha512-JaoxV4RzdBAZOnsF4dAlZ2ijJW72MbqO5lNfOBHUWiBQl3Rwe+mk2RCUMrRI3rSClLJ8HSNQNqcry12H+0ZjFw=="
|
||||
"version": "1.3.412",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.412.tgz",
|
||||
"integrity": "sha512-4bVdSeJScR8fT7ERveLWbxemY5uXEHVseqMRyORosiKcTUSGtVwBkV8uLjXCqoFLeImA57Z9hbz3TOid01U4Hw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3475,15 +3455,9 @@
|
|||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
|
||||
},
|
||||
"core-js": {
|
||||
<<<<<<< HEAD
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
|
||||
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
|
||||
=======
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
|
||||
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
|
||||
>>>>>>> f89f534... fixup! New question ui
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.6.5",
|
||||
|
@ -3658,12 +3632,6 @@
|
|||
"supports-color": "^6.1.0"
|
||||
}
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz",
|
||||
"integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz",
|
||||
|
@ -7835,9 +7803,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node-gettext": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-2.0.0.tgz",
|
||||
"integrity": "sha1-8dwSN83FRvUVk9o0AwS4vrpbhSU=",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz",
|
||||
"integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==",
|
||||
"requires": {
|
||||
"lodash.get": "^4.4.2"
|
||||
}
|
||||
|
@ -8824,9 +8792,9 @@
|
|||
}
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz",
|
||||
"integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz",
|
||||
"integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
<!-- No errors show router content -->
|
||||
<template v-else>
|
||||
<router-view :form="selectedForm" />
|
||||
<router-view :form.sync="selectedForm" />
|
||||
<router-view :form="selectedForm" name="sidebar" />
|
||||
</template>
|
||||
</Content>
|
||||
|
@ -105,9 +105,16 @@ export default {
|
|||
return this.$route.params.hash
|
||||
},
|
||||
|
||||
selectedForm() {
|
||||
// TODO: replace with form.hash
|
||||
return this.forms.find(form => form.form.hash === this.hash)
|
||||
selectedForm: {
|
||||
get() {
|
||||
return this.forms.find(form => form.hash === this.hash)
|
||||
},
|
||||
set(form) {
|
||||
const index = this.forms.findIndex(search => search.hash === this.hash)
|
||||
if (index > -1) {
|
||||
this.$set(this.forms, index, form)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -141,7 +148,7 @@ export default {
|
|||
const response = await axios.post(generateUrl('/apps/forms/api/v1/form'))
|
||||
const newForm = response.data
|
||||
this.forms.push(newForm)
|
||||
this.$router.push({ name: 'edit', params: { hash: newForm.form.hash } })
|
||||
this.$router.push({ name: 'edit', params: { hash: newForm.hash } })
|
||||
} catch (error) {
|
||||
showError(t('forms', 'Unable to create a new form'))
|
||||
console.error(error)
|
||||
|
|
|
@ -23,25 +23,43 @@
|
|||
<template>
|
||||
<li v-click-outside="disableEdit"
|
||||
:class="{ 'question--edit': edit }"
|
||||
:aria-label="t('forms', 'Question number {index}', {index})"
|
||||
class="question"
|
||||
@click="enableEdit">
|
||||
<!-- Drag handle -->
|
||||
<!-- TODO: implement arrow key mapping to reorder question -->
|
||||
<div class="question__drag-handle icon-drag-handle"
|
||||
:aria-label="t('forms', 'Drag to re-order the questions')" />
|
||||
<input v-if="edit"
|
||||
:value="title"
|
||||
class="question__title"
|
||||
type="text"
|
||||
minlength="1"
|
||||
maxlength="256"
|
||||
@input="onInput">
|
||||
<h3 v-else class="question__title" v-text="title" />
|
||||
|
||||
<!-- Header -->
|
||||
<div class="question__header">
|
||||
<input v-if="edit"
|
||||
:placeholder="t('forms', 'Enter a title for this question')"
|
||||
:aria-label="t('forms', 'The title of the question number {index}', {index})"
|
||||
:value="title"
|
||||
class="question__header-title"
|
||||
type="text"
|
||||
minlength="1"
|
||||
maxlength="256"
|
||||
required
|
||||
@input="onInput">
|
||||
<h3 v-else class="question__header-title" v-text="title" />
|
||||
<Actions class="question__header-menu" :force-menu="true">
|
||||
<ActionButton icon="icon-delete" @click="onDelete">
|
||||
{{ t('forms', 'Delete question') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
|
||||
<!-- Question content -->
|
||||
<slot />
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { directive as ClickOutside } from 'v-click-outside'
|
||||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
|
||||
export default {
|
||||
name: 'Question',
|
||||
|
@ -50,7 +68,20 @@ export default {
|
|||
ClickOutside,
|
||||
},
|
||||
|
||||
components: {
|
||||
Actions,
|
||||
ActionButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -79,11 +110,18 @@ export default {
|
|||
disableEdit() {
|
||||
this.$emit('update:edit', false)
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete this question
|
||||
*/
|
||||
onDelete() {
|
||||
this.$emit('delete', this.id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.question {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -92,6 +130,8 @@ export default {
|
|||
justify-content: stretch;
|
||||
margin-bottom: 22px;
|
||||
padding-left: 44px;
|
||||
// room for the new question menu
|
||||
padding-right: 44px;
|
||||
user-select: none;
|
||||
background-color: var(--color-main-background);
|
||||
|
||||
|
@ -118,26 +158,41 @@ export default {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
// Using type to have a higher order than the input styling of server
|
||||
&__title,
|
||||
&__title[type=text] {
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 1 100%;
|
||||
justify-content: space-between;
|
||||
width: auto;
|
||||
max-width: calc(100% - 44px);
|
||||
min-height: 22px;
|
||||
margin: 20px;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
padding-bottom: 6px;
|
||||
color: var(--color-text-light);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
&__title[type=text] {
|
||||
border-bottom: 1px dotted var(--color-border-dark);
|
||||
// Using type to have a higher order than the input styling of server
|
||||
&-title,
|
||||
&-title[type=text] {
|
||||
flex: 1 1 100%;
|
||||
min-height: 22px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-bottom: 6px;
|
||||
color: var(--color-text-light);
|
||||
border: 0;
|
||||
border-bottom: 1px dotted transparent;
|
||||
border-radius: 0;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
&-title[type=text] {
|
||||
border-bottom-color: var(--color-border-dark);
|
||||
}
|
||||
|
||||
&-menu.action-item {
|
||||
position: sticky;
|
||||
top: var(--header-height);
|
||||
// above other actions
|
||||
z-index: 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,11 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<Question :title="title" :edit.sync="edit" @update:title="onTitleChange">
|
||||
<Question
|
||||
v-bind.sync="$attrs"
|
||||
:title="title"
|
||||
:edit.sync="edit"
|
||||
@update:title="onTitleChange">
|
||||
<div class="question__content">
|
||||
<!-- TODO: properly choose max length -->
|
||||
<textarea ref="textarea"
|
||||
|
|
|
@ -21,25 +21,38 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<Question :title="title" :edit.sync="edit" @update:title="onTitleChange">
|
||||
<ul class="question__content">
|
||||
<template v-for="(answer, index) in values">
|
||||
<Question
|
||||
v-bind.sync="$attrs"
|
||||
:title="title"
|
||||
:edit.sync="edit"
|
||||
@update:title="onTitleChange">
|
||||
<ul class="question__content" :role="isUnique ? 'radiogroup' : ''">
|
||||
<template v-for="(answer, index) in options">
|
||||
<li :key="index" class="question__item">
|
||||
<input :id="`${id}-check-${index}`"
|
||||
<!-- Answer radio/checkbox + label -->
|
||||
<!-- TODO: migrate to radio/checkbox component once ready -->
|
||||
<input :id="`${id}-answer-${index}`"
|
||||
ref="checkbox"
|
||||
:checked="false"
|
||||
:aria-checked="isChecked(index)"
|
||||
:checked="isChecked(index)"
|
||||
:class="{
|
||||
'radio question__radio': isUnique,
|
||||
'checkbox question__checkbox': !isUnique,
|
||||
}"
|
||||
:name="`${id}-answer`"
|
||||
:readonly="true"
|
||||
type="checkbox"
|
||||
class="checkbox question__checkbox">
|
||||
:type="isUnique ? 'radio' : 'checkbox'">
|
||||
<label v-if="!edit"
|
||||
ref="label"
|
||||
:for="`${id}-check-${index}`"
|
||||
:for="`${id}-answer-${index}`"
|
||||
class="question__label">{{ answer }}</label>
|
||||
|
||||
<!-- Answer text input edit -->
|
||||
<!-- TODO: properly choose max length -->
|
||||
<input v-else
|
||||
ref="input"
|
||||
:aria-label="t('forms', 'An answer for checkbox {index}', { index: index + 1 })"
|
||||
:placeholder="t('forms', 'Answer for checkbox {index}', { index: index + 1 })"
|
||||
:aria-label="t('forms', 'An answer for the {index} option', { index: index + 1 })"
|
||||
:placeholder="t('forms', 'Answer number {index}', { index: index + 1 })"
|
||||
:value="answer"
|
||||
class="question__input"
|
||||
maxlength="256"
|
||||
|
@ -60,8 +73,8 @@
|
|||
<li v-if="edit && !isLastEmpty" class="question__item">
|
||||
<!-- TODO: properly choose max length -->
|
||||
<input
|
||||
:aria-label="t('forms', 'Add a new checkbox')"
|
||||
:placeholder="t('forms', 'Add a new checkbox')"
|
||||
:aria-label="t('forms', 'Add a new answer')"
|
||||
:placeholder="t('forms', 'Add a new answer')"
|
||||
class="question__input"
|
||||
maxlength="256"
|
||||
minlength="1"
|
||||
|
@ -79,6 +92,9 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
|||
import QuestionMixin from '../../mixins/QuestionMixin'
|
||||
import GenRandomId from '../../utils/GenRandomId'
|
||||
|
||||
// Implementations docs
|
||||
// https://www.w3.org/TR/2016/WD-wai-aria-practices-1.1-20160317/examples/radio/radio.html
|
||||
// https://www.w3.org/TR/2016/WD-wai-aria-practices-1.1-20160317/examples/checkbox/checkbox-2.html
|
||||
export default {
|
||||
name: 'QuestionMultiple',
|
||||
|
||||
|
@ -100,6 +116,10 @@ export default {
|
|||
const value = this.values[this.values.length - 1]
|
||||
return value && value.trim().length === 0
|
||||
},
|
||||
|
||||
isUnique() {
|
||||
return this.model.unique === true
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -112,6 +132,25 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* Is the provided index checked
|
||||
* @param {number} index the option index
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isChecked(index) {
|
||||
// TODO implement based on answers
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the values
|
||||
* @param {Array} values values to change
|
||||
*/
|
||||
updateValues(values) {
|
||||
this.$emit('update:values', this.isUnique ? [values[0]] : values)
|
||||
},
|
||||
|
||||
onInput(index) {
|
||||
// Update values
|
||||
const input = this.$refs.input[index]
|
||||
|
@ -119,7 +158,7 @@ export default {
|
|||
values[index] = input.value
|
||||
|
||||
// Update question
|
||||
this.$emit('update:values', values)
|
||||
this.updateValues(values)
|
||||
},
|
||||
|
||||
addNewEntry() {
|
||||
|
@ -128,7 +167,7 @@ export default {
|
|||
values.push('')
|
||||
|
||||
// Update question
|
||||
this.$emit('update:values', values)
|
||||
this.updateValues(values)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.focusIndex(values.length - 1)
|
||||
|
@ -147,7 +186,7 @@ export default {
|
|||
values.splice(index, 1)
|
||||
|
||||
// Update question
|
||||
this.$emit('update:values', values)
|
||||
this.updateValues(values)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.focusNext(index)
|
||||
|
@ -187,6 +226,11 @@ export default {
|
|||
margin: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure to respect readonly on radio/checkbox
|
||||
input[readonly] {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Using type to have a higher order than the input styling of server
|
||||
|
|
|
@ -21,7 +21,11 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<Question :title="title" :edit.sync="edit" @update:title="onTitleChange">
|
||||
<Question
|
||||
v-bind.sync="$attrs"
|
||||
:title="title"
|
||||
:edit.sync="edit"
|
||||
@update:title="onTitleChange">
|
||||
<div class="question__content">
|
||||
<!-- TODO: properly choose max length -->
|
||||
<input ref="input"
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
|
||||
-
|
||||
- @author René Gieling <github@dartcafe.de>
|
||||
-
|
||||
- @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="close flex-row">
|
||||
<a id="closeDetails"
|
||||
:title="closeDetailLabel"
|
||||
:alt="closeDetailLabelAlt"
|
||||
class="close icon-close has-tooltip-bottom"
|
||||
href="#"
|
||||
@:click="hideSidebar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
closeDetailLabel: t('Close details'),
|
||||
closeDetailLabelAlt: t('Close'),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
hideSidebar() {
|
||||
OC.Apps.hideAppSidebar()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -23,12 +23,37 @@ import Question from '../components/Questions/Question'
|
|||
export default {
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
/**
|
||||
* The question title
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* The user answers
|
||||
*/
|
||||
values: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The question list of answers
|
||||
*/
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Answer type model object
|
||||
*/
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
@ -39,14 +64,26 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
// Do we display this question in edit or fill mode
|
||||
edit: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Forward the title change to the parent
|
||||
*
|
||||
* @param {string} title the title
|
||||
*/
|
||||
onTitleChange(title) {
|
||||
this.$emit('update:title', title)
|
||||
},
|
||||
|
||||
/**
|
||||
* Forward the answer(s) change to the parent
|
||||
*
|
||||
* @param {Array} values the array of answers
|
||||
*/
|
||||
onValuesChange(values) {
|
||||
this.$emit('update:values', values)
|
||||
},
|
||||
|
|
|
@ -21,14 +21,9 @@
|
|||
|
||||
export default {
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
// TODO: use default Form object ?
|
||||
default: {},
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -20,29 +20,42 @@
|
|||
*
|
||||
*/
|
||||
|
||||
export default [
|
||||
{
|
||||
label: t('forms', 'Multiple choice'),
|
||||
value: 'radiogroup',
|
||||
import QuestionLong from '../components/Questions/QuestionLong'
|
||||
import QuestionShort from '../components/Questions/QuestionShort'
|
||||
import QuestionMultiple from '../components/Questions/QuestionMultiple'
|
||||
|
||||
/**
|
||||
* @typedef {Object} AnswerTypes
|
||||
* @property {string} multiple_unique
|
||||
* @property {string} multiple
|
||||
* @property {string} short
|
||||
* @property {string} long
|
||||
*/
|
||||
export default {
|
||||
|
||||
multiple_unique: {
|
||||
component: QuestionMultiple,
|
||||
icon: 'icon-answer-multiple',
|
||||
label: t('forms', 'Multiple choice'),
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
label: t('forms', 'Checkboxes'),
|
||||
value: 'checkbox',
|
||||
|
||||
multiple: {
|
||||
component: QuestionMultiple,
|
||||
icon: 'icon-answer-checkbox',
|
||||
label: t('forms', 'Checkboxes'),
|
||||
},
|
||||
{
|
||||
label: t('forms', 'Short answer'),
|
||||
value: 'text',
|
||||
|
||||
short: {
|
||||
component: QuestionShort,
|
||||
icon: 'icon-answer-short',
|
||||
label: t('forms', 'Short answer'),
|
||||
},
|
||||
{
|
||||
label: t('forms', 'Long text'),
|
||||
value: 'comment',
|
||||
|
||||
long: {
|
||||
component: QuestionLong,
|
||||
icon: 'icon-answer-long',
|
||||
label: t('forms', 'Long text'),
|
||||
},
|
||||
// {
|
||||
// label: 'Drop Down',
|
||||
// value: 'dropdown',
|
||||
// },
|
||||
]
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
*/
|
||||
const formatForm = function(form) {
|
||||
// clone form
|
||||
const newForm = Object.assign({}, form, form.form)
|
||||
const newForm = Object.assign({}, form)
|
||||
|
||||
// cleanup
|
||||
delete newForm.event
|
||||
|
|
|
@ -27,7 +27,13 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<AppContent>
|
||||
<AppContent v-if="loadingForm">
|
||||
<EmptyContent icon="icon-loading">
|
||||
{{ t('forms', 'Loading form “{title}”', { title: form.title }) }}
|
||||
</EmptyContent>
|
||||
</AppContent>
|
||||
|
||||
<AppContent v-else>
|
||||
<!-- Show results & sidebar button -->
|
||||
<TopBar>
|
||||
<button class="primary" @click="showResults">
|
||||
|
@ -46,7 +52,7 @@
|
|||
<label class="hidden-visually" for="form-title">{{ t('forms', 'Title') }}</label>
|
||||
<input
|
||||
id="form-title"
|
||||
v-model="form.form.title"
|
||||
v-model="form.title"
|
||||
:minlength="0"
|
||||
:placeholder="t('forms', 'Title')"
|
||||
:required="true"
|
||||
|
@ -57,7 +63,7 @@
|
|||
<textarea
|
||||
id="form-desc"
|
||||
ref="description"
|
||||
v-model="form.form.description"
|
||||
v-model="form.description"
|
||||
:placeholder="t('forms', 'Description')"
|
||||
@change="autoSizeDescription"
|
||||
@keydown="autoSizeDescription" />
|
||||
|
@ -70,40 +76,20 @@
|
|||
v-tooltip="t('forms', 'Add a question to this form')"
|
||||
:aria-label="t('forms', 'Add a question to this form')"
|
||||
:open.sync="questionMenuOpened"
|
||||
default-icon="icon-add-white">
|
||||
<ActionButton v-for="type in answerTypes"
|
||||
:key="type.label"
|
||||
:default-icon="loadingQuestions ? 'icon-loading-small' : 'icon-add-white'">
|
||||
<ActionButton v-for="(answer, type) in answerTypes"
|
||||
:key="answer.label"
|
||||
:disabled="loadingQuestions"
|
||||
:icon="answer.icon"
|
||||
class="question-toolbar__question"
|
||||
:icon="type.icon"
|
||||
@click="addQuestion">
|
||||
{{ type.label }}
|
||||
@click="addQuestion(type)">
|
||||
{{ answer.label }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
|
||||
<!-- <div id="quiz-form-selector-text">
|
||||
<label for="ans-type">Answer Type: </label>
|
||||
<select v-model="selected">
|
||||
<option value="" disabled>
|
||||
Select
|
||||
</option>
|
||||
<option v-for="type in questionTypes" :key="type.value" :value="type.value">
|
||||
{{ type.text }}
|
||||
</option>
|
||||
</select>
|
||||
<input
|
||||
v-model="newQuestion"
|
||||
:placeholder=" t('forms', 'Add Question') "
|
||||
maxlength="2048"
|
||||
@keyup.enter="addQuestion()">
|
||||
<button id="questButton"
|
||||
@click="addQuestion()">
|
||||
{{ t('forms', 'Add Question') }}
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
<!-- No questions -->
|
||||
<EmptyContent v-if="form.questions.length === 0">
|
||||
<EmptyContent v-if="hasQuestions">
|
||||
{{ t('forms', 'This form does not have any questions') }}
|
||||
<template #desc>
|
||||
<button class="empty-content__button primary" @click="openQuestionMenu">
|
||||
|
@ -129,17 +115,21 @@
|
|||
@deleteOption="deleteOption"
|
||||
@deleteQuestion="deleteQuestion(question, index)" />
|
||||
</transitionGroup> -->
|
||||
|
||||
<Draggable v-model="questions"
|
||||
:animation="200"
|
||||
tag="ul"
|
||||
@start="dragging = true"
|
||||
@end="dragging = false">
|
||||
<Questions :is="question.type"
|
||||
v-for="question in questions"
|
||||
:key="question.id"
|
||||
v-bind.sync="question" />
|
||||
</Draggable>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<Draggable v-model="questions"
|
||||
:animation="200"
|
||||
tag="ul"
|
||||
@start="dragging = true"
|
||||
@end="dragging = false">
|
||||
<Questions :is="answerTypes[question.type].component"
|
||||
v-for="(question, index) in questions"
|
||||
:key="question.id"
|
||||
:model="answerTypes[question.type]"
|
||||
:index="index + 1"
|
||||
v-bind.sync="question"
|
||||
@delete="deleteQuestion" />
|
||||
</Draggable>
|
||||
</form>
|
||||
</section>
|
||||
</AppContent>
|
||||
</template>
|
||||
|
@ -166,6 +156,8 @@ import QuizFormItem from '../components/quizFormItem'
|
|||
import TopBar from '../components/TopBar'
|
||||
import ViewsMixin from '../mixins/ViewsMixin'
|
||||
|
||||
window.axios = axios
|
||||
|
||||
export default {
|
||||
name: 'Create',
|
||||
components: {
|
||||
|
@ -187,36 +179,32 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
questionMenuOpened: false,
|
||||
placeholder: '',
|
||||
newOption: '',
|
||||
newQuestion: '',
|
||||
nextOptionId: 1,
|
||||
nextQuestionId: 1,
|
||||
writingForm: false,
|
||||
loadingForm: true,
|
||||
selected: '',
|
||||
uniqueQuestionText: false,
|
||||
uniqueOptionText: false,
|
||||
allHaveOpt: false,
|
||||
answerTypes,
|
||||
loadingForm: true,
|
||||
loadingQuestions: false,
|
||||
errorForm: false,
|
||||
questions: [
|
||||
{
|
||||
id: 1,
|
||||
type: QuestionShort,
|
||||
type: 'short',
|
||||
title: 'How old are you ?',
|
||||
values: ['I\'m 48 years old'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: QuestionLong,
|
||||
type: 'long',
|
||||
title: 'Your latest best memory ?',
|
||||
values: ['One day I was at the beach.\nIt was fun. The sun was shinning.\nThe water was warm'],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: QuestionMultiple,
|
||||
type: 'multiple',
|
||||
title: 'Choose an answer ?',
|
||||
values: ['Answer 1', 'Answer 2', 'Answer 3', 'Answer 4'],
|
||||
options: ['Answer 1', 'Answer 2', 'Answer 3', 'Answer 4'],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'multiple_unique',
|
||||
title: 'Choose an answer ?',
|
||||
options: ['Answer 1', 'Answer 2', 'Answer 3', 'Answer 4'],
|
||||
},
|
||||
],
|
||||
dragging: false,
|
||||
|
@ -225,32 +213,18 @@ export default {
|
|||
|
||||
computed: {
|
||||
title() {
|
||||
if (this.form.form.title === '') {
|
||||
if (this.form.title === '') {
|
||||
return t('forms', 'Create new form')
|
||||
} else {
|
||||
return this.form.form.title
|
||||
|
||||
return this.form.title
|
||||
}
|
||||
},
|
||||
|
||||
saveButtonTitle() {
|
||||
if (this.writingForm) {
|
||||
return t('forms', 'Writing form')
|
||||
} else if (this.form.mode === 'edit') {
|
||||
return t('forms', 'Update form')
|
||||
} else {
|
||||
return t('forms', 'Done')
|
||||
}
|
||||
hasQuestions() {
|
||||
return this.form.questions && this.form.questions.length === 0
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
watch: {
|
||||
title() {
|
||||
// only used when the title changes after page load
|
||||
document.title = t('forms', 'Forms') + ' - ' + this.title
|
||||
},
|
||||
|
||||
form: {
|
||||
deep: true,
|
||||
handler: function() {
|
||||
|
@ -259,53 +233,68 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.$route.name === 'edit') {
|
||||
this.form.mode = 'edit'
|
||||
} else if (this.$route.name === 'clone') {
|
||||
// TODO: CLONE
|
||||
}
|
||||
beforeMount() {
|
||||
this.fetchFullForm(this.form.id)
|
||||
},
|
||||
|
||||
mounted() {
|
||||
updated() {
|
||||
this.autoSizeDescription()
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Fetch the full form data and update parent
|
||||
*
|
||||
* @param {number} id the unique form hash
|
||||
*/
|
||||
async fetchFullForm(id) {
|
||||
this.loadingForm = true
|
||||
console.debug('Loading form', id)
|
||||
|
||||
switchSidebar() {
|
||||
this.sidebar = !this.sidebar
|
||||
try {
|
||||
const form = await axios.get(generateUrl('/apps/forms/api/v1/form/{id}', { id }))
|
||||
this.$emit('update:form', form.data)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.errorForm = true
|
||||
} finally {
|
||||
this.loadingForm = false
|
||||
}
|
||||
},
|
||||
|
||||
checkQuestionText() {
|
||||
this.uniqueQuestionText = true
|
||||
this.form.questions.forEach(q => {
|
||||
if (q.text === this.newQuestion) {
|
||||
this.uniqueQuestionText = false
|
||||
}
|
||||
})
|
||||
onSubmit() {
|
||||
this.saveForm()
|
||||
},
|
||||
|
||||
async addQuestion() {
|
||||
this.checkQuestionText()
|
||||
if (this.selected === '') {
|
||||
showError(t('forms', 'Select a question type!'), { duration: 3000 })
|
||||
} else if (!this.uniqueQuestionText) {
|
||||
showError(t('forms', 'Cannot have the same question!'))
|
||||
} else {
|
||||
if (this.newQuestion !== null & this.newQuestion !== '' & (/\S/.test(this.newQuestion))) {
|
||||
const response = await axios.post(generateUrl('/apps/forms/api/v1/question/'), { formId: this.form.id, type: this.selected, text: this.newQuestion })
|
||||
const respData = response.data
|
||||
/**
|
||||
* Add a new question to the current form
|
||||
*
|
||||
* @param {string} type the question type, see AnswerTypes
|
||||
*/
|
||||
async addQuestion(type) {
|
||||
const text = t('forms', 'New question')
|
||||
this.loadingQuestions = true
|
||||
|
||||
this.form.questions.push({
|
||||
id: respData.id,
|
||||
order: respData.order,
|
||||
text: this.newQuestion,
|
||||
type: this.selected,
|
||||
answers: [],
|
||||
})
|
||||
}
|
||||
this.newQuizQuestion = ''
|
||||
try {
|
||||
const response = await axios.post(generateUrl('/apps/forms/api/v1/question'), {
|
||||
formId: this.form.id,
|
||||
type,
|
||||
text,
|
||||
})
|
||||
const question = response.data
|
||||
|
||||
// Add newly created question
|
||||
this.form.questions.push(Object.assign({
|
||||
text,
|
||||
type,
|
||||
answers: [],
|
||||
}, question))
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
showError(t('forms', 'There was an error while adding the new question'))
|
||||
} finally {
|
||||
this.loadingQuestions = false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -315,31 +304,14 @@ export default {
|
|||
this.form.questions.splice(index, 1)
|
||||
},
|
||||
|
||||
checkOptionText(item, question) {
|
||||
this.uniqueOptionText = true
|
||||
question.options.forEach(o => {
|
||||
if (o.text === item.newOption) {
|
||||
this.uniqueOptionText = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async addOption(item, question) {
|
||||
this.checkOptionText(item, question)
|
||||
if (!this.uniqueOptionText) {
|
||||
showError(t('forms', 'Two options cannot be the same!'), { duration: 3000 })
|
||||
} else {
|
||||
if (item.newOption !== null & item.newOption !== '' & (/\S/.test(item.newOption))) {
|
||||
const response = await axios.post(generateUrl('/apps/forms/api/v1/option/'), { formId: this.form.id, questionId: question.id, text: item.newOption })
|
||||
const optionId = response.data
|
||||
const response = await axios.post(generateUrl('/apps/forms/api/v1/option/'), { formId: this.form.id, questionId: question.id, text: item.newOption })
|
||||
const optionId = response.data
|
||||
|
||||
question.options.push({
|
||||
id: optionId,
|
||||
text: item.newOption,
|
||||
})
|
||||
}
|
||||
item.newOption = ''
|
||||
}
|
||||
question.options.push({
|
||||
id: optionId,
|
||||
text: item.newOption,
|
||||
})
|
||||
},
|
||||
|
||||
async deleteOption(question, option, index) {
|
||||
|
@ -348,52 +320,25 @@ export default {
|
|||
question.options.splice(index, 1)
|
||||
},
|
||||
|
||||
checkAllHaveOpt() {
|
||||
this.allHaveOpt = true
|
||||
this.form.questions.forEach(q => {
|
||||
if (q.type !== 'text' && q.type !== 'comment' && q.options.length === 0) {
|
||||
this.allHaveOpt = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
autoSizeDescription() {
|
||||
const textarea = this.$refs.description
|
||||
textarea.style.cssText = 'height:auto; padding:0'
|
||||
textarea.style.cssText = `height: ${textarea.scrollHeight + 20}px`
|
||||
if (textarea) {
|
||||
textarea.style.cssText = 'height:auto; padding:0'
|
||||
textarea.style.cssText = `height: ${textarea.scrollHeight + 20}px`
|
||||
}
|
||||
},
|
||||
|
||||
debounceWriteForm: debounce(function() {
|
||||
this.writeForm()
|
||||
debounceSaveForm: debounce(function() {
|
||||
this.saveForm()
|
||||
}, 200),
|
||||
|
||||
writeForm() {
|
||||
this.checkAllHaveOpt()
|
||||
if (this.form.form.title.length === 0 | !(/\S/.test(this.form.form.title))) {
|
||||
this.titleEmpty = true
|
||||
showError(t('forms', 'Title must not be empty!'), { duration: 3000 })
|
||||
} else if (!this.allHaveOpt) {
|
||||
showError(t('forms', 'All questions need answers!'), { duration: 3000 })
|
||||
} else if (this.form.form.expires & this.form.form.expirationDate === '') {
|
||||
showError(t('forms', 'Need to pick an expiration date!'), { duration: 3000 })
|
||||
} else {
|
||||
this.writingForm = true
|
||||
this.titleEmpty = false
|
||||
|
||||
axios.post(OC.generateUrl('apps/forms/write/form'), this.form)
|
||||
.then((response) => {
|
||||
this.form.mode = 'edit'
|
||||
this.form.form.hash = response.data.hash
|
||||
this.form.form.id = response.data.id
|
||||
this.writingForm = false
|
||||
showSuccess(t('forms', '%n successfully saved', 1, this.form.form.title), { duration: 3000 })
|
||||
}, (error) => {
|
||||
this.form.form.hash = ''
|
||||
this.writingForm = false
|
||||
showError(t('forms', 'Error on saving form, see console'))
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.log(error.response)
|
||||
})
|
||||
async saveForm() {
|
||||
try {
|
||||
await axios.post(OC.generateUrl('apps/forms/write/form'), this.form)
|
||||
showSuccess(t('forms', '%n successfully saved', 1, this.form.title), { duration: 3000 })
|
||||
} catch (error) {
|
||||
showError(t('forms', 'Error on saving form, see console'))
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -437,7 +382,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#app-content {
|
||||
.app-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -21,14 +21,14 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<AppSidebar v-show="opened" :title="form.form.title" @close="onClose">
|
||||
<AppSidebar v-show="opened" :title="form.title" @close="onClose">
|
||||
<div class="configBox ">
|
||||
<label class="title icon-settings">
|
||||
{{ t('forms', 'Form configurations') }}
|
||||
</label>
|
||||
|
||||
<input id="isAnonymous"
|
||||
v-model="form.form.isAnonymous"
|
||||
v-model="form.isAnonymous"
|
||||
|
||||
type="checkbox"
|
||||
class="checkbox">
|
||||
|
@ -37,8 +37,8 @@
|
|||
</label>
|
||||
|
||||
<input id="submitOnce"
|
||||
v-model="form.form.submitOnce"
|
||||
:disabled="form.form.access.type === 'public' || form.form.isAnonymous"
|
||||
v-model="form.submitOnce"
|
||||
:disabled="form.access.type === 'public' || form.isAnonymous"
|
||||
type="checkbox"
|
||||
class="checkbox">
|
||||
<label for="submitOnce" class="title">
|
||||
|
@ -46,7 +46,7 @@
|
|||
</label>
|
||||
|
||||
<input id="expires"
|
||||
v-model="form.form.expires"
|
||||
v-model="form.expires"
|
||||
|
||||
type="checkbox"
|
||||
class="checkbox">
|
||||
|
@ -54,9 +54,9 @@
|
|||
{{ t('forms', 'Expires') }}
|
||||
</label>
|
||||
|
||||
<DatetimePicker v-show="form.form.expires"
|
||||
<DatetimePicker v-show="form.expires"
|
||||
id="expiresDatetimePicker"
|
||||
v-model="form.form.expiresTimestamp"
|
||||
v-model="form.expiresTimestamp"
|
||||
v-bind="expirationDatePicker" />
|
||||
</div>
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
|||
</label>
|
||||
|
||||
<input id="registered"
|
||||
v-model="form.form.access.type"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="registered"
|
||||
class="radio">
|
||||
|
@ -76,7 +76,7 @@
|
|||
</label>
|
||||
|
||||
<input id="public"
|
||||
v-model="form.form.access.type"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="public"
|
||||
class="radio">
|
||||
|
@ -86,7 +86,7 @@
|
|||
</label>
|
||||
|
||||
<input id="selected"
|
||||
v-model="form.form.access.type"
|
||||
v-model="form.access.type"
|
||||
type="radio"
|
||||
value="selected"
|
||||
class="radio">
|
||||
|
@ -96,7 +96,7 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<ShareDiv v-show="form.form.access.type === 'selected'"
|
||||
<ShareDiv v-show="form.access.type === 'selected'"
|
||||
:active-shares="form.shares"
|
||||
:placeholder="t('forms', 'Name of user or group')"
|
||||
:hide-names="true"
|
||||
|
|
Загрузка…
Ссылка в новой задаче