Skip to content

Commit

Permalink
feat: Implement custom submission message
Browse files Browse the repository at this point in the history
This message is shown when a user submits the form and can be formatted using markdown.
Make the custom submission message hidden behind a checkbox
Null on submissionMessage means disabled

Co-authored-by: Chartman123 <chris-hartmann@gmx.de>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux and Chartman123 committed Oct 16, 2023
1 parent 30cda62 commit 812a637
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 21 deletions.
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);

Check warning on line 344 in lib/Controller/ApiController.php

View check run for this annotation

Codecov / codecov/patch

lib/Controller/ApiController.php#L342-L344

Added lines #L342 - L344 were not covered by tests
}

// 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 {

Check warning on line 43 in lib/Migration/Version030400Date20230628011500.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version030400Date20230628011500.php#L43

Added line #L43 was not covered by tests
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('forms_v2_forms');

Check warning on line 46 in lib/Migration/Version030400Date20230628011500.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version030400Date20230628011500.php#L45-L46

Added lines #L45 - L46 were not covered by tests

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

Check warning on line 54 in lib/Migration/Version030400Date20230628011500.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version030400Date20230628011500.php#L48-L54

Added lines #L48 - L54 were not covered by tests

return $schema;

Check warning on line 56 in lib/Migration/Version030400Date20230628011500.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version030400Date20230628011500.php#L56

Added line #L56 was not covered by tests
}

return null;

Check warning on line 59 in lib/Migration/Version030400Date20230628011500.php

View check run for this annotation

Codecov / codecov/patch

lib/Migration/Version030400Date20230628011500.php#L59

Added line #L59 was not covered by tests
}
}
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"
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>
</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
21 changes: 20 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" />
</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,10 @@ export default {
}
}
@include markdown-output;
text-align: center;
}
form {
.question {
// Less padding needed as submit view does not have drag handles
Expand Down

0 comments on commit 812a637

Please sign in to comment.