Clean Expires
Signed-off-by: Jonas Rittershofer <jotoeri@users.noreply.github.com>
This commit is contained in:
parent
df69a7a4a3
commit
2cd445d201
|
@ -131,7 +131,7 @@ class ApiController extends Controller {
|
|||
'id' => $form->getId(),
|
||||
'hash' => $form->getHash(),
|
||||
'title' => $form->getTitle(),
|
||||
'expired' => $form->getExpired(),
|
||||
'expires' => $form->getExpires(),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -173,13 +173,8 @@ class PageController extends Controller {
|
|||
return new TemplateResponse('forms', 'no.acc.tmpl', []);
|
||||
}
|
||||
|
||||
if ($form->getExpiresTimestamp() === 0) {
|
||||
$expired = false;
|
||||
} else {
|
||||
$expired = time() > $form->getExpiresTimestamp();
|
||||
}
|
||||
|
||||
if ($expired) {
|
||||
// If form expired, return Expired-Template
|
||||
if ( ($form->getExpires() !== 0) && (time() > $form->getExpires()) ) {
|
||||
return new TemplateResponse('forms', 'expired.tmpl');
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ use OCP\AppFramework\Db\Entity;
|
|||
* @method void setAccess(array $value)
|
||||
* @method integer getCreated()
|
||||
* @method void setCreated(integer $value)
|
||||
* @method integer getExpiresTimestamp()
|
||||
* @method void setExpiresTimestamp(integer $value)
|
||||
* @method integer getExpires()
|
||||
* @method void setExpires(integer $value)
|
||||
* @method integer getIsAnonymous()
|
||||
* @method void setIsAnonymous(bool $value)
|
||||
* @method integer getSubmitOnce()
|
||||
|
@ -56,7 +56,7 @@ class Form extends Entity {
|
|||
protected $ownerId;
|
||||
protected $accessJson;
|
||||
protected $created;
|
||||
protected $expiresTimestamp;
|
||||
protected $expires;
|
||||
protected $isAnonymous;
|
||||
protected $submitOnce;
|
||||
|
||||
|
@ -65,7 +65,7 @@ class Form extends Entity {
|
|||
*/
|
||||
public function __construct() {
|
||||
$this->addType('created', 'integer');
|
||||
$this->addType('expiresTimestamp', 'integer');
|
||||
$this->addType('expires', 'integer');
|
||||
$this->addType('isAnonymous', 'bool');
|
||||
$this->addType('submitOnce', 'bool');
|
||||
}
|
||||
|
@ -80,20 +80,6 @@ class Form extends Entity {
|
|||
$this->setAccessJson(json_encode($access));
|
||||
}
|
||||
|
||||
// Get virtual column expires. Set should only be done by setExpiresTimestamp().
|
||||
public function getExpires(): bool {
|
||||
return (bool) $this->getExpiresTimestamp();
|
||||
}
|
||||
|
||||
// Get virtual column expired. Set should only be done by setExpiresTimestamp().
|
||||
public function getExpired(): bool {
|
||||
if ($this->getExpires()) {
|
||||
return time() > $this->getExpiresTimestamp();
|
||||
}
|
||||
// else - does not expire
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read full form
|
||||
public function read() {
|
||||
return [
|
||||
|
@ -102,12 +88,9 @@ class Form extends Entity {
|
|||
'title' => $this->getTitle(),
|
||||
'description' => $this->getDescription(),
|
||||
'ownerId' => $this->getOwnerId(),
|
||||
'ownerDisplayName' => \OC_User::getDisplayName($this->getOwnerId()),
|
||||
'created' => $this->getCreated(),
|
||||
'access' => $this->getAccess(),
|
||||
'expires' => $this->getExpires(),
|
||||
'expired' => $this->getExpired(),
|
||||
'expiresTimestamp' => $this->getExpiresTimestamp(),
|
||||
'isAnonymous' => $this->getIsAnonymous(),
|
||||
'submitOnce' => $this->getSubmitOnce()
|
||||
];
|
||||
|
|
|
@ -105,7 +105,7 @@ class Version010200Date20200323141300 extends SimpleMigrationStep {
|
|||
'notnull' => false,
|
||||
'comment' => 'unix-timestamp',
|
||||
]);
|
||||
$table->addColumn('expires_timestamp', Type::INTEGER, [
|
||||
$table->addColumn('expires', Type::INTEGER, [
|
||||
'notnull' => false,
|
||||
'default' => 0,
|
||||
'comment' => 'unix-timestamp',
|
||||
|
@ -237,7 +237,7 @@ class Version010200Date20200323141300 extends SimpleMigrationStep {
|
|||
'owner_id' => $qb_restore->createNamedParameter($event['owner'], IQueryBuilder::PARAM_STR),
|
||||
'access_json' => $qb_restore->createNamedParameter($newAccessJSON, IQueryBuilder::PARAM_STR),
|
||||
'created' => $qb_restore->createNamedParameter($this->convertDateTime($event['created']), IQueryBuilder::PARAM_INT),
|
||||
'expires_timestamp' => $qb_restore->createNamedParameter($this->convertDateTime($event['expire']), IQueryBuilder::PARAM_INT),
|
||||
'expires' => $qb_restore->createNamedParameter($this->convertDateTime($event['expire']), IQueryBuilder::PARAM_INT),
|
||||
'is_anonymous' => $qb_restore->createNamedParameter($event['is_anonymous'], IQueryBuilder::PARAM_BOOL),
|
||||
'submit_once' => $qb_restore->createNamedParameter($event['unique'], IQueryBuilder::PARAM_BOOL)
|
||||
]);
|
||||
|
|
|
@ -66,6 +66,7 @@ import ActionSeparator from '@nextcloud/vue/dist/Components/ActionSeparator'
|
|||
import AppNavigationIconBullet from '@nextcloud/vue/dist/Components/AppNavigationIconBullet'
|
||||
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
|
||||
import axios from '@nextcloud/axios'
|
||||
import moment from '@nextcloud/moment'
|
||||
import Vue from 'vue'
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
|
||||
|
@ -106,7 +107,7 @@ export default {
|
|||
*/
|
||||
bulletColor() {
|
||||
const style = getComputedStyle(document.body)
|
||||
if (this.form.expired) {
|
||||
if (this.form.expires && moment().unix() > this.form.expires) {
|
||||
return style.getPropertyValue('--color-error').slice(-6)
|
||||
}
|
||||
return style.getPropertyValue('--color-success').slice(-6)
|
||||
|
|
|
@ -36,14 +36,14 @@
|
|||
<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"
|
||||
:value="text"
|
||||
class="question__header-title"
|
||||
type="text"
|
||||
minlength="1"
|
||||
maxlength="256"
|
||||
required
|
||||
@input="onInput">
|
||||
<h3 v-else class="question__header-title" v-text="title" />
|
||||
<h3 v-else class="question__header-title" v-text="text" />
|
||||
<Actions class="question__header-menu" :force-menu="true">
|
||||
<ActionButton icon="icon-delete" @click="onDelete">
|
||||
{{ t('forms', 'Delete question') }}
|
||||
|
@ -82,7 +82,7 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
@ -94,7 +94,7 @@ export default {
|
|||
|
||||
methods: {
|
||||
onInput({ target }) {
|
||||
this.$emit('update:title', target.value)
|
||||
this.$emit('update:text', target.value)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -115,7 +115,7 @@ export default {
|
|||
* Delete this question
|
||||
*/
|
||||
onDelete() {
|
||||
this.$emit('delete', this.id)
|
||||
this.$emit('delete')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -23,13 +23,14 @@
|
|||
<template>
|
||||
<Question
|
||||
v-bind.sync="$attrs"
|
||||
:title="title"
|
||||
:text="text"
|
||||
:edit.sync="edit"
|
||||
@update:title="onTitleChange">
|
||||
@delete="onDelete"
|
||||
@update:text="onTitleChange">
|
||||
<div class="question__content">
|
||||
<!-- TODO: properly choose max length -->
|
||||
<textarea ref="textarea"
|
||||
:aria-label="t('forms', 'A long answer for the question “{title}”', { title })"
|
||||
:aria-label="t('forms', 'A long answer for the question “{text}”', { text })"
|
||||
:placeholder="t('forms', 'Long answer text')"
|
||||
:readonly="edit"
|
||||
:value="values[0]"
|
||||
|
|
|
@ -23,9 +23,10 @@
|
|||
<template>
|
||||
<Question
|
||||
v-bind.sync="$attrs"
|
||||
:title="title"
|
||||
:text="text"
|
||||
:edit.sync="edit"
|
||||
@update:title="onTitleChange">
|
||||
@delete="onDelete"
|
||||
@update:text="onTitleChange">
|
||||
<ul class="question__content" :role="isUnique ? 'radiogroup' : ''">
|
||||
<template v-for="(answer, index) in options">
|
||||
<li :key="index" class="question__item">
|
||||
|
@ -45,7 +46,7 @@
|
|||
<label v-if="!edit"
|
||||
ref="label"
|
||||
:for="`${id}-answer-${index}`"
|
||||
class="question__label">{{ answer }}</label>
|
||||
class="question__label">{{ answer.text }}</label>
|
||||
|
||||
<!-- Answer text input edit -->
|
||||
<!-- TODO: properly choose max length -->
|
||||
|
@ -53,7 +54,7 @@
|
|||
ref="input"
|
||||
:aria-label="t('forms', 'An answer for the {index} option', { index: index + 1 })"
|
||||
:placeholder="t('forms', 'Answer number {index}', { index: index + 1 })"
|
||||
:value="answer"
|
||||
:value="answer.text"
|
||||
class="question__input"
|
||||
maxlength="256"
|
||||
minlength="1"
|
||||
|
|
|
@ -23,13 +23,14 @@
|
|||
<template>
|
||||
<Question
|
||||
v-bind.sync="$attrs"
|
||||
:title="title"
|
||||
:text="text"
|
||||
:edit.sync="edit"
|
||||
@update:title="onTitleChange">
|
||||
@delete="onDelete"
|
||||
@update:text="onTitleChange">
|
||||
<div class="question__content">
|
||||
<!-- TODO: properly choose max length -->
|
||||
<input ref="input"
|
||||
:aria-label="t('forms', 'A short answer for the question “{title}”', { title })"
|
||||
:aria-label="t('forms', 'A short answer for the question “{text}”', { text })"
|
||||
:placeholder="t('forms', 'Short answer text')"
|
||||
:readonly="edit"
|
||||
:value="values[0]"
|
||||
|
|
|
@ -26,7 +26,7 @@ export default {
|
|||
/**
|
||||
* The question title
|
||||
*/
|
||||
title: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
@ -73,10 +73,10 @@ export default {
|
|||
/**
|
||||
* Forward the title change to the parent
|
||||
*
|
||||
* @param {string} title the title
|
||||
* @param {string} text the title
|
||||
*/
|
||||
onTitleChange(title) {
|
||||
this.$emit('update:title', title)
|
||||
onTitleChange(text) {
|
||||
this.$emit('update:text', text)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -87,5 +87,12 @@ export default {
|
|||
onValuesChange(values) {
|
||||
this.$emit('update:values', values)
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete this question
|
||||
*/
|
||||
onDelete() {
|
||||
this.$emit('delete')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -100,34 +100,19 @@
|
|||
</EmptyContent>
|
||||
|
||||
<!-- Questions list -->
|
||||
<!-- <transitionGroup
|
||||
v-else
|
||||
id="form-list"
|
||||
name="list"
|
||||
tag="ul"
|
||||
class="form-table">
|
||||
<QuizFormItem
|
||||
v-for="(question, index) in form.questions"
|
||||
:key="question.id"
|
||||
:question="question"
|
||||
:type="question.type"
|
||||
@addOption="addOption"
|
||||
@deleteOption="deleteOption"
|
||||
@deleteQuestion="deleteQuestion(question, index)" />
|
||||
</transitionGroup> -->
|
||||
<form @submit.prevent="onSubmit">
|
||||
<Draggable v-model="questions"
|
||||
<Draggable v-model="form.questions"
|
||||
:animation="200"
|
||||
tag="ul"
|
||||
@start="dragging = true"
|
||||
@end="dragging = false">
|
||||
<Questions :is="answerTypes[question.type].component"
|
||||
v-for="(question, index) in questions"
|
||||
v-for="(question, index) in form.questions"
|
||||
:key="question.id"
|
||||
:model="answerTypes[question.type]"
|
||||
:index="index + 1"
|
||||
v-bind.sync="question"
|
||||
@delete="deleteQuestion" />
|
||||
@delete="deleteQuestion(question)" />
|
||||
</Draggable>
|
||||
</form>
|
||||
</section>
|
||||
|
@ -183,30 +168,6 @@ export default {
|
|||
loadingForm: true,
|
||||
loadingQuestions: false,
|
||||
errorForm: false,
|
||||
questions: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'short',
|
||||
title: 'How old are you ?',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'long',
|
||||
title: 'Your latest best memory ?',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'multiple',
|
||||
title: 'Choose an answer ?',
|
||||
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,
|
||||
}
|
||||
},
|
||||
|
@ -227,8 +188,12 @@ export default {
|
|||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler: function() {
|
||||
this.debounceWriteForm()
|
||||
handler: function(newForm, oldForm) {
|
||||
if (newForm.hash === oldForm.hash) {
|
||||
this.debounceSaveForm()
|
||||
} else {
|
||||
this.fetchFullForm(newForm.id)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -262,7 +227,7 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
onSubmit(e) {
|
||||
this.saveForm()
|
||||
},
|
||||
|
||||
|
@ -298,10 +263,26 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
async deleteQuestion(question, index) {
|
||||
await axios.delete(generateUrl('/apps/forms/api/v1/question/{id}', { id: question.id }))
|
||||
// TODO catch Error
|
||||
this.form.questions.splice(index, 1)
|
||||
/**
|
||||
* Delete a question
|
||||
* @param {Object} question the question to delete
|
||||
* @param {number} question.id the question id to delete
|
||||
*/
|
||||
async deleteQuestion(question) {
|
||||
console.info(question)
|
||||
const id = question.id
|
||||
this.loadingQuestions = true
|
||||
|
||||
try {
|
||||
await axios.delete(generateUrl('/apps/forms/api/v1/question/{id}', { id }))
|
||||
const index = this.form.questions.findIndex(search => search.id === id)
|
||||
this.form.questions.splice(index, 1)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
showError(t('forms', 'There was an error while removing the question'))
|
||||
} finally {
|
||||
this.loadingQuestions = false
|
||||
}
|
||||
},
|
||||
|
||||
async addOption(item, question) {
|
||||
|
@ -320,6 +301,9 @@ export default {
|
|||
question.options.splice(index, 1)
|
||||
},
|
||||
|
||||
/**
|
||||
* Auto adjust the description height based on lines number
|
||||
*/
|
||||
autoSizeDescription() {
|
||||
const textarea = this.$refs.description
|
||||
if (textarea) {
|
||||
|
@ -328,14 +312,17 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Forms saving handlers
|
||||
*/
|
||||
debounceSaveForm: debounce(function() {
|
||||
this.saveForm()
|
||||
}, 200),
|
||||
|
||||
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 })
|
||||
// TODO: add loading status feedback ?
|
||||
await axios.post(OC.generateUrl('/apps/forms/write/form'), this.form)
|
||||
} catch (error) {
|
||||
showError(t('forms', 'Error on saving form, see console'))
|
||||
console.error(error)
|
||||
|
@ -365,7 +352,10 @@ export default {
|
|||
// conflicting with the click outside directive
|
||||
setTimeout(() => {
|
||||
this.questionMenuOpened = true
|
||||
}, 100)
|
||||
this.$nextTick(() => {
|
||||
this.$refs.questionMenu.focusFirstAction()
|
||||
})
|
||||
}, 10)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -437,7 +427,8 @@ export default {
|
|||
|
||||
.question-toolbar {
|
||||
position: sticky;
|
||||
z-index: 50;
|
||||
// Above other menus
|
||||
z-index: 55;
|
||||
top: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -446,6 +437,7 @@ export default {
|
|||
height: var(--top-bar-height);
|
||||
// make sure this doesn't take any space and appear floating
|
||||
margin-top: -44px;
|
||||
|
||||
.icon-add-white {
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</label>
|
||||
|
||||
<input id="expires"
|
||||
v-model="form.expires"
|
||||
v-model="formExpires"
|
||||
|
||||
type="checkbox"
|
||||
class="checkbox">
|
||||
|
@ -54,9 +54,9 @@
|
|||
{{ t('forms', 'Expires') }}
|
||||
</label>
|
||||
|
||||
<DatetimePicker v-show="form.expires"
|
||||
<DatetimePicker v-show="formExpires"
|
||||
id="expiresDatetimePicker"
|
||||
v-model="form.expiresTimestamp"
|
||||
v-model="form.expires"
|
||||
v-bind="expirationDatePicker" />
|
||||
</div>
|
||||
|
||||
|
@ -131,6 +131,7 @@ export default {
|
|||
locale: '',
|
||||
longDateFormat: '',
|
||||
dateTimeFormat: '',
|
||||
formExpires: false,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -151,21 +152,17 @@ export default {
|
|||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
optionDatePicker() {
|
||||
return {
|
||||
editable: false,
|
||||
minuteStep: 1,
|
||||
type: 'datetime',
|
||||
format: moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT'),
|
||||
lang: this.lang.split('-')[0],
|
||||
placeholder: t('forms', 'Click to add a date'),
|
||||
timePickerOptions: {
|
||||
start: '00:00',
|
||||
step: '00:30',
|
||||
end: '23:30',
|
||||
},
|
||||
}
|
||||
watch: {
|
||||
formExpires: {
|
||||
handler: function() {
|
||||
if (!this.formExpires) {
|
||||
this.form.expires = 0
|
||||
} else {
|
||||
this.form.expires = moment().unix() + 3600 // Expires in one hour.
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -185,6 +182,13 @@ export default {
|
|||
this.longDateFormat = moment.localeData().longDateFormat('L')
|
||||
this.dateTimeFormat = moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT')
|
||||
|
||||
// Compute current formExpires for checkbox
|
||||
if (this.form.expires) {
|
||||
this.formExpires = true
|
||||
} else {
|
||||
this.formExpires = false
|
||||
}
|
||||
|
||||
// Watch for Sidebar toggle
|
||||
subscribe('toggleSidebar', this.onToggle)
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue