Add editorJS for Form Description

This commit is contained in:
philipp lang 2024-02-02 01:05:45 +01:00
parent 6e6a4595cd
commit 1edbdab8fd
17 changed files with 391 additions and 82 deletions

View File

@ -20,7 +20,9 @@ class FormStoreAction
{
return [
...$this->globalRules(),
'description' => 'required|string',
'description.time' => 'required|integer',
'description.blocks' => 'required|array',
'description.version' => 'required|string',
'excerpt' => 'required|string|max:130',
'from' => 'required|date',
'to' => 'required|date',
@ -53,6 +55,7 @@ class FormStoreAction
'from' => 'Start',
'to' => 'Ende',
'header_image' => 'Bild',
'description.blocks' => 'Beschreibung',
];
}

View File

@ -20,7 +20,10 @@ class FormUpdateAction
{
return [
...$this->globalRules(),
'description' => 'required|string',
'description' => 'required|array',
'description.time' => 'required|integer',
'description.blocks' => 'required|array',
'description.version' => 'required|string',
'excerpt' => 'required|string|max:130',
'from' => 'required|date',
'to' => 'required|date',
@ -49,6 +52,7 @@ class FormUpdateAction
...$this->globalValidationAttributes(),
'from' => 'Start',
'to' => 'Ende',
'description.blocks' => 'Beschreibung',
];
}

View File

@ -24,6 +24,7 @@ class Form extends Model implements HasMedia
public $casts = [
'config' => 'json',
'description' => 'json',
];
/**

View File

@ -63,7 +63,7 @@ class FormResource extends JsonResource
],
'templates' => FormtemplateResource::collection(Formtemplate::get()),
'default' => [
'description' => '',
'description' => [],
'name' => '',
'excerpt' => '',
'from' => null,

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('forms', function (Blueprint $table) {
$table->dropColumn('description');
});
Schema::table('forms', function (Blueprint $table) {
$table->json('description')->after('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('forms', function (Blueprint $table) {
$table->dropColumn('description');
});
Schema::table('forms', function (Blueprint $table) {
$table->text('description')->after('name');
});
}
};

34
package-lock.json generated
View File

@ -5,6 +5,9 @@
"packages": {
"": {
"dependencies": {
"@editorjs/editorjs": "^2.29.0",
"@editorjs/header": "^2.8.1",
"@editorjs/paragraph": "^2.11.3",
"@inertiajs/vue3": "^1.0.14",
"@tailwindcss/typography": "^0.5.10",
"@vitejs/plugin-vue": "^4.6.2",
@ -68,6 +71,11 @@
"node": ">=6.0.0"
}
},
"node_modules/@codexteam/icons": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.5.tgz",
"integrity": "sha512-s6H2KXhLz2rgbMZSkRm8dsMJvyUNZsEjxobBEg9ztdrb1B2H3pEzY6iTwI4XUPJWJ3c3qRKwV4TrO3J5jUdoQA=="
},
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
@ -86,6 +94,32 @@
"kuler": "^2.0.0"
}
},
"node_modules/@editorjs/editorjs": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.29.0.tgz",
"integrity": "sha512-w2BVboSHokMBd/cAOZn0UU328o3gSZ8XUvFPA2e9+ciIGKILiRSPB70kkNdmhHkuNS3q2px+vdaIFaywBl7wGA=="
},
"node_modules/@editorjs/header": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@editorjs/header/-/header-2.8.1.tgz",
"integrity": "sha512-y0HVXRP7m2W617CWo3fsb5HhXmSLaRpb9GzFx0Vkp/HEm9Dz5YO1s8tC7R8JD3MskwoYh7V0hRFQt39io/r6hA==",
"dependencies": {
"@codexteam/icons": "^0.0.5"
}
},
"node_modules/@editorjs/paragraph": {
"version": "2.11.3",
"resolved": "https://registry.npmjs.org/@editorjs/paragraph/-/paragraph-2.11.3.tgz",
"integrity": "sha512-ON72lhvhgWzPrq4VXpHUeut9bsFeJgVATDeL850FVToOwYHKvdsNpfu0VgxEodhkXgzU/IGl4FzdqC2wd3AJUQ==",
"dependencies": {
"@codexteam/icons": "^0.0.4"
}
},
"node_modules/@editorjs/paragraph/node_modules/@codexteam/icons": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.4.tgz",
"integrity": "sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q=="
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",

View File

@ -12,9 +12,9 @@
"fix": "eslint \"resources/js/**/*.{js,vue}\" --fix"
},
"devDependencies": {
"accounting": "^0.4.1",
"autoprefixer": "^10.4.17",
"axios": "^1.6.6",
"accounting": "^0.4.1",
"dayjs": "^1.11.10",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0",
@ -24,6 +24,9 @@
"vue-axios": "^3.5.2"
},
"dependencies": {
"@editorjs/editorjs": "^2.29.0",
"@editorjs/header": "^2.8.1",
"@editorjs/paragraph": "^2.11.3",
"@inertiajs/vue3": "^1.0.14",
"@tailwindcss/typography": "^0.5.10",
"@vitejs/plugin-vue": "^4.6.2",

View File

@ -9,3 +9,4 @@
@import 'table';
@import 'bool';
@import 'form';
@import 'editor';

View File

@ -1,3 +1,7 @@
::selection {
@apply bg-blue-800;
}
.button-group > div {
padding-left: 0 !important;
padding-right: 0 !important;

71
resources/css/editor.css vendored Normal file
View File

@ -0,0 +1,71 @@
.ce-inline-toolbar,
.codex-editor--narrow .ce-toolbox,
.ce-conversion-toolbar,
.ce-settings,
.ce-settings__button,
.ce-toolbar__settings-btn,
.cdx-button,
.ce-popover,
.ce-toolbar__plus:hover {
@apply bg-primary-700;
color: inherit;
}
.ce-inline-tool-input {
@apply bg-primary-700 text-primary-200 placeholder-primary-500;
}
.ce-block--selected {
@apply bg-gray-800;
}
.ce-block--selected .ce-block__content {
@apply bg-gray-800;
}
.ce-conversion-tool__icon,
.ce-popover-item__icon {
@apply bg-primary-800;
}
.ce-popover-item__title {
@apply text-primary-200;
}
.ce-popover-item:hover:not(.ce-popover-item--no-hover) {
@apply bg-primary-800;
}
.ce-inline-tool,
.ce-conversion-toolbar__label,
.ce-toolbox__button,
.cdx-settings-button,
.ce-toolbar__plus {
color: inherit;
}
.codex-editor ::selection {
@apply bg-blue-800;
}
.cdx-settings-button:hover,
.ce-settings__button:hover,
.ce-toolbox__button--active,
.ce-toolbox__button:hover,
.cdx-button:hover,
.ce-inline-toolbar__dropdown:hover,
.ce-inline-tool:hover,
.ce-popover__item:hover,
.ce-toolbar__settings-btn:hover {
background-color: #439a86;
color: inherit;
}
.cdx-notify--error {
background: #fb5d5d !important;
}
.cdx-notify__cross::after,
.cdx-notify__cross::before {
background: white;
}

View File

@ -0,0 +1,91 @@
<template>
<div>
<span v-if="label" class="font-semibold text-gray-400" :class="labelClass(size)">{{ label }}<span v-show="required" class="text-red-800">&nbsp;*</span></span>
<div class="relative w-full h-full">
<div :id="id" :class="[defaultFieldClass, fieldClass(size)]"></div>
<div v-if="hint" v-tooltip="hint" class="absolute right-0 top-0 mr-2 mt-2">
<ui-sprite src="info-button" class="w-5 h-5 text-indigo-200"></ui-sprite>
</div>
</div>
</div>
</template>
<script setup>
import {debounce} from 'lodash';
import {onMounted, ref} from 'vue';
import EditorJS from '@editorjs/editorjs';
import Header from '@editorjs/header';
import Paragraph from '@editorjs/paragraph';
import useFieldSize from '../../composables/useFieldSize.js';
const emit = defineEmits(['update:modelValue']);
const {labelClass, fieldClass, defaultFieldClass} = useFieldSize();
const props = defineProps({
required: {
type: Boolean,
default: false,
},
size: {
default: null,
},
rows: {
default: function () {
return 4;
},
},
id: {
required: true,
},
hint: {
default: null,
},
modelValue: {
default: undefined,
},
label: {
default: false,
},
placeholder: {
default: '',
},
});
const editor = ref(null);
onMounted(async () => {
editor.value = new EditorJS({
placeholder: props.placeholder,
holder: props.id,
defaultBlock: 'paragraph',
data: JSON.parse(JSON.stringify(props.modelValue)),
tools: {
paragraph: {
class: Paragraph,
shortcut: 'SHIFT+P',
inlineToolbar: true,
config: {
preserveBlank: true,
placeholder: 'Absatz',
},
},
heading: {
class: Header,
shortcut: 'CTRL+H',
inlineToolbar: [],
config: {
placeholder: 'Überschrift',
levels: [2, 3, 4],
defaultLevel: 2,
},
},
},
onChange: debounce(async (api, event) => {
const data = await editor.value.save();
emit('update:modelValue', data);
}, 500),
});
await editor.value.isReady;
console.log('Editor is ready');
});
</script>

View File

@ -1,20 +1,8 @@
<template>
<label class="flex flex-col">
<span v-if="label && !inset" class="font-semibold text-gray-400" :class="{
'text-xs': size == 'sm',
'text-sm': size === null,
}">{{ label }}<span v-show="required" class="text-red-800">&nbsp;*</span></span>
<span v-if="label && inset" class="absolute top-0 left-0 -mt-2 px-1 ml-3 inset-bg font-semibold text-gray-700"
:class="{
'text-xs': size == 'sm',
'text-sm': size === null,
}">{{ label }}<span v-show="required" class="text-red-800">&nbsp;*</span></span>
<span v-if="label" class="font-semibold text-gray-400" :class="labelClass(size)">{{ label }}<span v-show="required" class="text-red-800">&nbsp;*</span></span>
<div class="relative w-full h-full">
<textarea :placeholder="placeholder" class="h-full w-full outline-none bg-gray-700 border-gray-600 border-solid"
:rows="rows" :class="{
'rounded-lg text-sm border-2 p-2 text-gray-300': size === null,
'rounded-lg py-2 px-2 text-xs border-2 text-gray-300': size == 'sm',
}" @input="trigger" v-text="modelValue"></textarea>
<textarea :placeholder="placeholder" class="h-full w-full outline-none" :class="[defaultFieldClass, fieldClass(size)]" :rows="rows" @input="trigger" v-text="modelValue"></textarea>
<div v-if="hint" v-tooltip="hint" class="absolute right-0 top-0 mr-2 mt-2">
<ui-sprite src="info-button" class="w-5 h-5 text-indigo-200"></ui-sprite>
</div>
@ -23,17 +11,16 @@
</template>
<script setup>
import useFieldSize from '../../composables/useFieldSize.js';
const emit = defineEmits(['update:modelValue']);
const {labelClass, fieldClass, defaultFieldClass} = useFieldSize();
const props = defineProps({
required: {
type: Boolean,
default: false,
},
inset: {
default: false,
type: Boolean,
},
size: {
default: null,
},
@ -51,18 +38,9 @@ const props = defineProps({
modelValue: {
default: undefined,
},
mask: {
default: undefined,
},
label: {
default: false,
},
type: {
required: false,
default: function () {
return 'text';
},
},
placeholder: {
default: '',
},
@ -74,9 +52,3 @@ if (typeof props.modelValue === 'undefined') {
emit('update:modelValue', '');
}
</script>
<style scope>
.inset-bg {
background: linear-gradient(to bottom, hsl(247.5, 66.7%, 97.6%) 0%, hsl(247.5, 66.7%, 97.6%) 41%, hsl(0deg 0% 100%) 41%, hsl(180deg 0% 100%) 100%);
}
</style>

View File

@ -0,0 +1,32 @@
import {ref, inject, computed, onBeforeUnmount} from 'vue';
import {router} from '@inertiajs/vue3';
import useQueueEvents from './useQueueEvents.js';
export default function () {
const sizes = {
sm: {
label: 'text-xs',
field: 'text-xs',
},
default: {
label: 'text-sm',
field: 'text-sm',
},
};
const defaultFieldClass = 'border-2 p-2 rounded-lg bg-gray-700 border-gray-600 text-gray-300 border-solid';
function labelClass(size) {
return sizes[size ? size : 'default'].label;
}
function fieldClass(size) {
return sizes[size ? size : 'default'].field;
}
return {
labelClass,
fieldClass,
defaultFieldClass,
};
}

View File

@ -1,20 +1,23 @@
<template>
<page-layout>
<template #toolbar>
<page-toolbar-button :href="meta.links.formtemplate_index" color="primary"
icon="event">Vorlagen</page-toolbar-button>
<page-toolbar-button color="primary" icon="plus" @click.prevent="create">Veranstaltung
erstellen</page-toolbar-button>
<page-toolbar-button :href="meta.links.formtemplate_index" color="primary" icon="event">Vorlagen</page-toolbar-button>
<page-toolbar-button color="primary" icon="plus" @click.prevent="create">Veranstaltung erstellen</page-toolbar-button>
</template>
<ui-popup v-if="deleting !== null" :heading="`Veranstaltung ${deleting.name} löschen?`" @close="deleting = null">
<div>
<p class="mt-4">Diese Veranstaltung löschen?</p>
<div class="grid grid-cols-2 gap-3 mt-6">
<a href="#" class="text-center btn btn-danger" @click.prevent="
remove(deleting);
deleting = null;
">Veranstaltung löschen</a>
<a
href="#"
class="text-center btn btn-danger"
@click.prevent="
remove(deleting);
deleting = null;
"
>Veranstaltung löschen</a
>
<a href="#" class="text-center btn btn-primary" @click.prevent="deleting = null">Abbrechen</a>
</div>
</div>
@ -22,45 +25,52 @@
<ui-popup v-if="single !== null && single.config === null" heading="Vorlage auswählen" @close="cancel">
<div class="mt-3 grid gap-3 grid-cols-2">
<a v-for="(template, index) in meta.templates" :key="index"
class="py-2 px-3 border rounded bg-zinc-800 hover:bg-zinc-700 transition" href="#"
@click.prevent="setTemplate(template)">
<a v-for="(template, index) in meta.templates" :key="index" class="py-2 px-3 border rounded bg-zinc-800 hover:bg-zinc-700 transition" href="#" @click.prevent="setTemplate(template)">
<span v-text="template.name"></span>
</a>
</div>
</ui-popup>
<ui-popup v-if="single !== null && single.config !== null"
:heading="`Veranstaltung ${single.id ? 'bearbeiten' : 'erstellen'}`" full @close="cancel">
<ui-popup v-if="single !== null && single.config !== null" :heading="`Veranstaltung ${single.id ? 'bearbeiten' : 'erstellen'}`" full @close="cancel">
<div class="flex flex-col mt-3">
<ui-tabs v-model="active" :entries="tabs"></ui-tabs>
<div v-if="active === 0" class="grid grid-cols-2 gap-3">
<f-text id="name" v-model="single.name" name="name" label="Name" required></f-text>
<f-singlefile id="header_image" v-model="single.header_image" label="Bild" name="header_image"
parent-name="form" :parent-id="single.id" collection="headerImage" required></f-singlefile>
<f-singlefile
id="header_image"
v-model="single.header_image"
label="Bild"
name="header_image"
parent-name="form"
:parent-id="single.id"
collection="headerImage"
required
></f-singlefile>
<f-text id="from" v-model="single.from" type="date" name="from" label="Von" required></f-text>
<f-text id="to" v-model="single.to" type="date" name="to" label="Bis" required></f-text>
<f-textarea id="excerpt" v-model="single.excerpt"
<f-textarea
id="excerpt"
v-model="single.excerpt"
hint="Gebe hier eine kurze Beschreibung für die Veranstaltungs-Übersicht ein (Maximal 130 Zeichen)."
name="excerpt" label="Auszug" rows="5" required></f-textarea>
<f-textarea id="description" v-model="single.description" name="description" label="Beschreibung"
rows="10" required></f-textarea>
name="excerpt"
label="Auszug"
rows="5"
required
></f-textarea>
<f-editor id="description" v-model="single.description" name="description" label="Beschreibung" rows="10" required></f-editor>
</div>
<div v-if="active === 1">
<ui-note class="mt-2"> Sobald sich der erste Teilnehmer für die Veranstaltung angemeldet hat, kann
dieses Formular nicht mehr geändert werden. </ui-note>
<ui-note class="mt-2"> Sobald sich der erste Teilnehmer für die Veranstaltung angemeldet hat, kann dieses Formular nicht mehr geändert werden. </ui-note>
<form-builder v-model="single.config" :meta="meta"></form-builder>
</div>
<div v-if="active === 2" class="grid gap-3">
<ui-note class="mt-2">
Hier kannst du die E-Mail anpassen, die nach der Anmeldung an den Teilnehmer verschickt wird.<br />
Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten
des Teilnehmers aufgelistet.<br />
Die Anrede ("Hallo Max Mustermann") wird automatisch an den Anfang gesetzt.</ui-note>
<f-textarea id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" rows="8"
required></f-textarea>
<f-textarea id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2"
rows="8" required></f-textarea>
Es gibt dafür einen ersten E-Mail-Teil und einen zweiten E-Mail-Teil. Dazwischen werden die Daten des Teilnehmers aufgelistet.<br />
Die Anrede ("Hallo Max Mustermann") wird automatisch an den Anfang gesetzt.</ui-note
>
<f-textarea id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" rows="8" required></f-textarea>
<f-textarea id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2" rows="8" required></f-textarea>
</div>
</div>
<template #actions>
@ -71,10 +81,8 @@
</ui-popup>
<page-filter breakpoint="xl">
<f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm"
@update:model-value="setFilter('search', $event)"></f-text>
<f-switch id="past" :model-value="getFilter('past')" label="vergangene zeigen" size="sm"
@update:model-value="setFilter('past', $event)"></f-switch>
<f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
<f-switch id="past" :model-value="getFilter('past')" label="vergangene zeigen" size="sm" @update:model-value="setFilter('past', $event)"></f-switch>
</page-filter>
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm">
@ -96,10 +104,8 @@
<div v-text="form.to_human"></div>
</td>
<td>
<a v-tooltip="`Bearbeiten`" href="#" class="ml-2 inline-flex btn btn-warning btn-sm"
@click.prevent="edit(form)"><ui-sprite src="pencil"></ui-sprite></a>
<a v-tooltip="`Löschen`" href="#" class="ml-2 inline-flex btn btn-danger btn-sm"
@click.prevent="deleting = form"><ui-sprite src="trash"></ui-sprite></a>
<a v-tooltip="`Bearbeiten`" href="#" class="ml-2 inline-flex btn btn-warning btn-sm" @click.prevent="edit(form)"><ui-sprite src="pencil"></ui-sprite></a>
<a v-tooltip="`Löschen`" href="#" class="ml-2 inline-flex btn btn-danger btn-sm" @click.prevent="deleting = form"><ui-sprite src="trash"></ui-sprite></a>
</td>
</tr>
</table>
@ -110,17 +116,17 @@
</template>
<script setup>
import { ref } from 'vue';
import { indexProps, useIndex } from '../../composables/useInertiaApiIndex.js';
import {ref} from 'vue';
import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js';
import FormBuilder from '../formtemplate/FormBuilder.vue';
const props = defineProps(indexProps);
var { meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFilter, setFilter } = useIndex(props.data, 'form');
var {meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFilter, setFilter} = useIndex(props.data, 'form');
const active = ref(0);
const deleting = ref(null);
const tabs = [{ title: 'Allgemeines' }, { title: 'Formular' }, { title: 'E-Mail' }, { title: 'Export' }];
const tabs = [{title: 'Allgemeines'}, {title: 'Formular'}, {title: 'E-Mail'}, {title: 'Export'}];
function setTemplate(template) {
active.value = 0;

View File

@ -4,13 +4,14 @@ namespace Tests\Feature\Form;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\RequestFactories\EditorRequestFactory;
use Worksome\RequestFactories\RequestFactory;
/**
* @method self name(string $name)
* @method self from(string $date)
* @method self to(string $date)
* @method self description(string $description)
* @method self description(?EditorRequestFactory $description)
* @method self mailTop(string $content)
* @method self mailBottom(string $content)
* @method self excerpt(string $description)
@ -26,7 +27,13 @@ class FormRequest extends RequestFactory
{
return [
'name' => $this->faker->words(4, true),
'description' => $this->faker->text(),
'description' => [
'time' => 45069432,
'blocks' => [
['id' => 'TTzz66', 'type' => 'paragraph', 'data' => ['text' => 'lorem']]
],
'version' => '1.0',
],
'excerpt' => $this->faker->words(10, true),
'config' => ['sections' => []],
'from' => $this->faker->dateTime()->format('Y-m-d H:i:s'),

View File

@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Event;
use Tests\TestCase;
use Generator;
use Illuminate\Support\Facades\Storage;
use Tests\RequestFactories\EditorRequestFactory;
class FormStoreActionTest extends TestCase
{
@ -26,9 +27,10 @@ class FormStoreActionTest extends TestCase
{
Event::fake([Succeeded::class]);
$this->login()->loginNami()->withoutExceptionHandling();
$description = EditorRequestFactory::new()->text(10, 'Lorem');
FormRequest::new()
->name('formname')
->description('lala ggg')
->description($description->create())
->excerpt('avff')
->registrationFrom('2023-05-04 01:00:00')->registrationUntil('2023-07-07 01:00:00')->from('2023-07-07')->to('2023-07-08')
->mailTop('Guten Tag')
@ -43,7 +45,7 @@ class FormStoreActionTest extends TestCase
$this->assertEquals('sname', $form->config['sections'][0]['name']);
$this->assertEquals('formname', $form->name);
$this->assertEquals('avff', $form->excerpt);
$this->assertEquals('lala ggg', $form->description);
$this->assertEquals($description->paragraphBlock(10, 'Lorem'), $form->description);
$this->assertEquals('Guten Tag', $form->mail_top);
$this->assertEquals('Viele Grüße', $form->mail_bottom);
$this->assertEquals('2023-05-04 01:00', $form->registration_from->format('Y-m-d H:i'));
@ -71,7 +73,7 @@ class FormStoreActionTest extends TestCase
{
yield [FormRequest::new()->name(''), ['name' => 'Name ist erforderlich.']];
yield [FormRequest::new()->excerpt(''), ['excerpt' => 'Auszug ist erforderlich.']];
yield [FormRequest::new()->description(''), ['description' => 'Beschreibung ist erforderlich.']];
yield [FormRequest::new()->description(null), ['description.blocks' => 'Beschreibung ist erforderlich.']];
yield [FormRequest::new()->state(['from' => null]), ['from' => 'Start ist erforderlich']];
yield [FormRequest::new()->state(['to' => null]), ['to' => 'Ende ist erforderlich']];
yield [FormRequest::new()->state(['header_image' => null]), ['header_image' => 'Bild ist erforderlich']];

View File

@ -0,0 +1,40 @@
<?php
namespace Tests\RequestFactories;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Worksome\RequestFactories\RequestFactory;
class EditorRequestFactory extends RequestFactory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'time' => 45069432,
'blocks' => [
['id' => 'TTzz66', 'type' => 'paragraph', 'data' => ['text' => 'lorem']]
],
'version' => '1.0',
];
}
public function text(int $id, string $text): self
{
return $this->state($this->paragraphBlock($id, $text));
}
public function paragraphBlock(int $id, string $text): array
{
return [
'time' => 1,
'version' => '1.0',
'blocks' => [
['id' => $id, 'type' => 'paragraph', 'data' => ['text' => $text]]
],
];
}
}