Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement custom submission message #1659

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Returns the full-depth object of the requested form (without submissions).
"title": "Form 1",
"description": "Description Text",
"ownerId": "jonas",
"submissionMessage": "Thank **you** for submitting the form."
"created": 1611240961,
"access": {
"permitAllUsers": false,
Expand Down
27 changes: 14 additions & 13 deletions docs/DataStructure.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
# Forms Data Structure
**State: Forms v3.0.0 - 23.03.2022**
**State: Forms v3.3.1 - 08.10.2023**

This document describes the Object-Structure, that is used within the Forms App and on Forms API v2. It does partially **not** equal the actual database structure behind.

## Data Structures
### Form
| Property | Type | Restrictions | Description |
|-------------|-----------------|--------------|-------------|
| id | Integer | unique | An instance-wide unique id of the form |
| hash | 16-char String | unique | An instance-wide unique hash |
| title | String | max. 256 ch. | The form title |
| Property | Type | Restrictions | Description |
|-------------|-----------------|---------------|-------------|
| id | Integer | unique | An instance-wide unique id of the form |
| hash | 16-char String | unique | An instance-wide unique hash |
| title | String | max. 256 ch. | The form title |
| description | String | max. 8192 ch. | The Form description |
| ownerId | String | | The nextcloud userId of the form owner |
| created | unix timestamp | | When the form has been created |
| ownerId | String | | The nextcloud userId of the form owner |
| submissionMessage | String | max. 2048 ch. | Optional custom message, with Markdown support, to be shown to users when the form is submitted (default is used if set to null) |
| created | unix timestamp | | When the form has been created |
| access | [Access-Object](#access-object) | | Describing access-settings of the form |
| expires | unix-timestamp | | When the form should expire. Timestamp `0` indicates _never_ |
| isAnonymous | Boolean | | If Answers will be stored anonymously |
| submitMultiple | Boolean | | If users are allowed to submit multiple times to the form |
| showExpiration | Boolean | | If the expiration date will be shown on the form |
| canSubmit | Boolean | | If the user can Submit to the form, i.e. calculated information out of `submitMultiple` and existing submissions. |
| expires | unix-timestamp | | When the form should expire. Timestamp `0` indicates _never_ |
| isAnonymous | Boolean | | If Answers will be stored anonymously |
| submitMultiple | Boolean | | If users are allowed to submit multiple times to the form |
| showExpiration | Boolean | | If the expiration date will be shown on the form |
| canSubmit | Boolean | | If the user can Submit to the form, i.e. calculated information out of `submitMultiple` and existing submissions. |
| permissions | Array of [Permissions](#permissions) | Array of permissions regarding the form |
| questions | Array of [Questions](#question) | | Array of questions belonging to the form |
| shares | Array of [Shares](#share) | | Array of shares of the form |
Expand Down
1 change: 1 addition & 0 deletions lib/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Constants {
public const MAX_STRING_LENGTHS = [
'formTitle' => 256,
'formDescription' => 8192,
'submissionMessage' => 2048,
'questionText' => 2048,
'questionDescription' => 4096,
'optionText' => 1024,
Expand Down
6 changes: 4 additions & 2 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,10 @@ public function updateForm(int $id, array $keyValuePairs): DataResponse {
}

// Create FormEntity with given Params & Id.
$form = Form::fromParams($keyValuePairs);
$form->setId($id);
foreach ($keyValuePairs as $key => $value) {
$method = 'set' . ucfirst($key);
$form->$method($value);
}

// Update changed Columns in Db.
$this->formMapper->update($form);
Expand Down
6 changes: 5 additions & 1 deletion lib/Db/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
* @method void setShowExpiration(bool $value)
* @method integer getLastUpdated()
* @method void setLastUpdated(integer $value)
* @method ?string getSubmissionMessage()
* @method void setSubmissionMessage(?string $value)
*/
class Form extends Entity {
protected $hash;
Expand All @@ -64,6 +66,7 @@ class Form extends Entity {
protected $isAnonymous;
protected $submitMultiple;
protected $showExpiration;
protected $submissionMessage;
protected $lastUpdated;

/**
Expand Down Expand Up @@ -102,7 +105,8 @@ public function read() {
'isAnonymous' => (bool)$this->getIsAnonymous(),
'submitMultiple' => (bool)$this->getSubmitMultiple(),
'showExpiration' => (bool)$this->getShowExpiration(),
'lastUpdated' => (int)$this->getLastUpdated()
'lastUpdated' => (int)$this->getLastUpdated(),
'submissionMessage' => $this->getSubmissionMessage(),
];
}
}
61 changes: 61 additions & 0 deletions lib/Migration/Version030400Date20230628011500.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Ferdinand Thiessen <rpm@fthiessen.de>
*
* @author Ferdinand Thiessen <rpm@fthiessen.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/>.
*
*/

namespace OCA\Forms\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

class Version030400Date20230628011500 extends SimpleMigrationStep {

/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('forms_v2_forms');

if (!$table->hasColumn('submission_message')) {
$table->addColumn('submission_message', Types::STRING, [
'notnull' => false,
'default' => null,
'length' => 2048,
'comment' => 'custom thank you message',
]);

return $schema;
}

return null;
}
}
98 changes: 98 additions & 0 deletions src/components/SidebarTabs/SettingsSidebarTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,35 @@
{{ t('forms', 'Show expiration date on form') }}
</NcCheckboxRadioSwitch>
</div>
<NcCheckboxRadioSwitch :checked="hasCustomSubmissionMessage"
Chartman123 marked this conversation as resolved.
Show resolved Hide resolved
type="switch"
@update:checked="onUpdateHasCustomSubmissionMessage">
{{ t('forms', 'Custom submission message') }}
</NcCheckboxRadioSwitch>
<div v-show="hasCustomSubmissionMessage"
class="settings-div--indent submission-message"
:tabindex="editMessage ? undefined : '0'"
@focus="editMessage = true">
<textarea v-if="editMessage || !form.submissionMessage"
v-click-outside="() => { editMessage = false }"
aria-describedby="forms-submission-message-description"
:aria-label="t('forms', 'Custom submission message')"
:value="form.submissionMessage"
:maxlength="maxStringLengths.submissionMessage"
:placeholder="t('forms', 'Message to show after a user submitted the form (formatting using Markdown is supported)')"
class="submission-message__input"
@blur="editMessage = false"
@change="onSubmissionMessageChange" />
<!-- eslint-disable vue/no-v-html -->
<div v-else
:aria-label="t('forms', 'Custom submission message')"
class="submission-message__output"
v-html="submissionMessageHTML" />
<!-- eslint-enable vue/no-v-html -->
<div id="forms-submission-message-description" class="submission-message__description">
{{ t('forms', 'Message to show after a user submitted the form. Please note that the message will not be translated!') }}
</div>
Chartman123 marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
</template>

Expand All @@ -68,14 +97,23 @@ import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadi
import NcDatetimePicker from '@nextcloud/vue/dist/Components/NcDatetimePicker.js'
import ShareTypes from '../../mixins/ShareTypes.js'

import { directive as ClickOutside } from 'v-click-outside'
import { loadState } from '@nextcloud/initial-state'

export default {
components: {
NcCheckboxRadioSwitch,
NcDatetimePicker,
},

directives: {
ClickOutside,
},

mixins: [ShareTypes],

inject: ['$markdownit'],

props: {
form: {
type: Object,
Expand All @@ -89,10 +127,20 @@ export default {
stringify: this.stringifyDate,
parse: this.parseTimestampToDate,
},
maxStringLengths: loadState('forms', 'maxStringLengths'),
/** If custom submission message is shown as input or rendered markdown */
editMessage: false,
}
},

computed: {
/**
* If the form has a custom submission message or the user wants to add one (settings switch)
*/
hasCustomSubmissionMessage() {
return this.form?.submissionMessage !== undefined && this.form?.submissionMessage !== null
},

/**
* Submit Multiple is disabled, if it cannot be controlled.
*/
Expand Down Expand Up @@ -125,6 +173,12 @@ export default {
expirationDate() {
return moment(this.form.expires, 'X').toDate()
},
/**
* The submission message rendered as HTML
*/
submissionMessageHTML() {
return this.$markdownit.render(this.form.submissionMessage || '')
},
},

methods: {
Expand Down Expand Up @@ -159,6 +213,22 @@ export default {
this.$emit('update:formProp', 'expires', parseInt(moment(datetime).format('X')))
},

onSubmissionMessageChange({ target }) {
this.$emit('update:formProp', 'submissionMessage', target.value)
},

/**
* Enable or disable the whole custom submission message
* Disabled means the value is set to null.
*/
onUpdateHasCustomSubmissionMessage() {
if (this.hasCustomSubmissionMessage) {
this.$emit('update:formProp', 'submissionMessage', null)
} else {
this.$emit('update:formProp', 'submissionMessage', '')
}
},

/**
* Datepicker timestamp to string
*
Expand Down Expand Up @@ -215,4 +285,32 @@ export default {
.settings-div--indent {
margin-inline-start: 40px;
}

.submission-message {
&__description {
color: var(--color-text-maxcontrast);
font-size: 13px;
}

&__input, &__output {
width: 100%;
min-height: 100px;
line-height: 24px;
}

&__output {
@import '../../scssmixins/markdownOutput';

padding: 12px;
margin-block: 3px;
border: 2px solid var(--color-border-maxcontrast);
border-radius: var(--border-radius-large);

&:hover {
border-color: var(--color-primary-element);
}

@include markdown-output;
}
}
</style>
7 changes: 6 additions & 1 deletion src/scssmixins/markdownOutput.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
@mixin markdown-output {
overflow-wrap: break-word;

::v-deep {
:deep() {
>:not(:first-child) {
margin-block-start: 1.5em;
}
Expand All @@ -37,6 +37,11 @@
color: var(--color-main-text);
}

a {
color: var(--color-primary-element);
text-decoration: underline;
}

blockquote {
padding-inline-start: 1em;
border-inline-start: 4px solid var(--color-primary-element);
Expand Down
22 changes: 21 additions & 1 deletion src/views/Submit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,15 @@
</template>
</NcEmptyContent>
<NcEmptyContent v-else-if="success || !form.canSubmit"
:title="t('forms', 'Thank you for completing the form!')">
:title="t('forms', 'Thank you for completing the form!')"
:description="form.submissionMessage">
<template #icon>
<IconCheck :size="64" />
</template>
<template v-if="submissionMessageHTML" #description>
<!-- eslint-disable-next-line vue/no-v-html -->
<p class="submission-message" v-html="submissionMessageHTML" />
Chartman123 marked this conversation as resolved.
Show resolved Hide resolved
</template>
</NcEmptyContent>
<NcEmptyContent v-else-if="isExpired"
:title="t('forms', 'Form expired')"
Expand Down Expand Up @@ -237,6 +242,16 @@ export default {
return message
},

/**
* Rendered HTML of the custom submission message
*/
submissionMessageHTML() {
if (this.form.submissionMessage && (this.success || !this.form.canSubmit)) {
return this.markdownit.render(this.form.submissionMessage)
}
return ''
},

expirationMessage() {
const relativeDate = moment(this.form.expires, 'X').fromNow()
if (this.isExpired) {
Expand Down Expand Up @@ -424,6 +439,11 @@ export default {
}
}

.submission-message {
@include markdown-output;
susnux marked this conversation as resolved.
Show resolved Hide resolved
text-align: center;
}

form {
.question {
// Less padding needed as submit view does not have drag handles
Expand Down