Add editorJS for Form Description
This commit is contained in:
parent
6e6a4595cd
commit
1edbdab8fd
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class Form extends Model implements HasMedia
|
|||
|
||||
public $casts = [
|
||||
'config' => 'json',
|
||||
'description' => 'json',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,7 +63,7 @@ class FormResource extends JsonResource
|
|||
],
|
||||
'templates' => FormtemplateResource::collection(Formtemplate::get()),
|
||||
'default' => [
|
||||
'description' => '',
|
||||
'description' => [],
|
||||
'name' => '',
|
||||
'excerpt' => '',
|
||||
'from' => null,
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -9,3 +9,4 @@
|
|||
@import 'table';
|
||||
@import 'bool';
|
||||
@import 'form';
|
||||
@import 'editor';
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
::selection {
|
||||
@apply bg-blue-800;
|
||||
}
|
||||
|
||||
.button-group > div {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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"> *</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>
|
|
@ -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"> *</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"> *</span></span>
|
||||
<span v-if="label" class="font-semibold text-gray-400" :class="labelClass(size)">{{ label }}<span v-show="required" class="text-red-800"> *</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>
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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']];
|
||||
|
|
|
@ -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]]
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue