From 4fb798073c71c361bbdf728e2720b30dae1bc76d Mon Sep 17 00:00:00 2001
From: Benoit Rospars <benoit.rospars@inria.fr>
Date: Wed, 18 Jan 2023 12:09:45 +0100
Subject: [PATCH 01/13] Fix paths

---
 electron/components/main.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/electron/components/main.js b/electron/components/main.js
index 22cd34d2..4cf8962f 100644
--- a/electron/components/main.js
+++ b/electron/components/main.js
@@ -17,7 +17,7 @@ module.exports.createMainWindow = function () {
             webPreferences: {
                 nodeIntegration: false,
                 contextIsolation: true,
-                preload: path.join(__dirname, 'preload.js')
+                preload: path.join(__dirname, '../preload.js')
             }
         });
 
@@ -25,7 +25,7 @@ module.exports.createMainWindow = function () {
     mainWindow.loadURL(
         isDev
             ? 'http://localhost:8000'
-            : `file://${path.join(__dirname, '../dist/index.html')}`
+            : `file://${path.join(__dirname, '../../dist/index.html')}`
     );
     mainWindow.center();
     return mainWindow
-- 
GitLab


From b037a97ac8f01e6b168b600d47eafc000335e515 Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 10:18:14 +0100
Subject: [PATCH 02/13] Issue #55 #75: Can click on a screen node to edit the
 screen

---
 src/_colors.scss                              |  2 ++
 src/components/ContentButton.vue              |  2 +-
 src/features/ePocFlow/ePocFlow.vue            | 20 ++++++++----
 src/features/ePocFlow/nodes/ChapterNode.vue   |  4 +--
 .../nodes/{ContentNode.vue => ScreenNode.vue} |  8 +++--
 src/features/ePocFlow/nodes/ePocNode.vue      |  4 +--
 .../components/inputs/card/CardGroup.vue      |  7 ++--
 .../components/inputs/card/CardInput.vue      | 16 ++++------
 src/features/sideBar/components/ModelMenu.vue |  2 +-
 .../sideBar/components/ModelMenuV0.vue        |  6 ++--
 .../{ScreenNode.vue => ScreenTemplate.vue}    |  0
 src/global.scss                               |  6 ++++
 src/shared/data/form.data.ts                  | 32 ++++++++++++++++++-
 src/shared/stores/editorStore.ts              | 20 ++++++------
 14 files changed, 85 insertions(+), 44 deletions(-)
 rename src/features/ePocFlow/nodes/{ContentNode.vue => ScreenNode.vue} (89%)
 rename src/features/sideBar/components/{ScreenNode.vue => ScreenTemplate.vue} (100%)

diff --git a/src/_colors.scss b/src/_colors.scss
index 918a68c8..741f7dba 100644
--- a/src/_colors.scss
+++ b/src/_colors.scss
@@ -22,6 +22,7 @@ $editor-lightblue: #E8EDF3;
 $editor-shadow-outer: #ADBCD5;
 $editor-grayblue: #7789A6;
 $editor-blue: #00B3E9;
+$node-active: #E1F4FA;
 
 [color-scheme="light"] {
     --background: #{$editor-light};
@@ -35,6 +36,7 @@ $editor-blue: #00B3E9;
     --list-border: #E6EAF4;
     --button-blue: #{$editor-lightblue};
     --node: #{$inria-light};
+    --node-active: #{$node-active};
 }
 
 // Dark theme not implemented yet
diff --git a/src/components/ContentButton.vue b/src/components/ContentButton.vue
index 7618d846..a8ea82f3 100644
--- a/src/components/ContentButton.vue
+++ b/src/components/ContentButton.vue
@@ -22,7 +22,7 @@ function click(event) {
         class="btn btn-content"
         :class="[classList, { active: isActive }, { 'draggable': isDraggable }]"
         :draggable="isDraggable"
-        @click="click($event)"
+        @click.stop="click($event)"
     >
         <i :class="icon" />
         <span v-if="subtitle" class="subtitle">{{ subtitle }}</span>
diff --git a/src/features/ePocFlow/ePocFlow.vue b/src/features/ePocFlow/ePocFlow.vue
index b8a54fa9..a4caf150 100644
--- a/src/features/ePocFlow/ePocFlow.vue
+++ b/src/features/ePocFlow/ePocFlow.vue
@@ -1,9 +1,9 @@
 <script setup lang="ts">
 import { VueFlow, useVueFlow, Panel, PanelPosition } from '@vue-flow/core';
 import { markRaw, nextTick, watch } from 'vue';
-import ContentNode from './nodes/ContentNode.vue';
+import ScreenNode from './nodes/ScreenNode.vue';
 import CustomConnectContent from './edges/CustomConnectContent.vue';
-import { SideAction, NodeElement } from '../../shared/interfaces';
+import { SideAction, NodeElement, Form } from '../../shared/interfaces';
 import { useEditorStore } from '../../shared/stores';
 import ChapterNode from './nodes/ChapterNode.vue';
 import ePocNode from './nodes/ePocNode.vue';
@@ -16,7 +16,7 @@ onConnect((params) => addEdges([{...params, updatable: true, style: { stroke: '#
 const editorStore = useEditorStore();
 
 const nodeTypes = {
-    content: markRaw(ContentNode),
+    content: markRaw(ScreenNode),
     chapter: markRaw(ChapterNode),
     epoc: markRaw(ePocNode),
     add: markRaw(AddChapterNode), 
@@ -89,7 +89,8 @@ function addNode(position, actions: SideAction[]) {
     let elements: NodeElement[] = [];
 
     
-    const id = (nodes.value.length + 1).toString();
+    const id = editorStore.generateId();
+    const form = editorStore.getForm('screen');
     
     actions.forEach((action) => {
         elements.push({
@@ -104,11 +105,11 @@ function addNode(position, actions: SideAction[]) {
         id: id,
         type: 'content',
         // Put animated: nodeIcons.length === 1 when implementing v2
-        data: { elements: elements, readyToDrop: false, animated: false, title: 'Screen' },
+        data: { elements: elements, readyToDrop: false, animated: false, title: 'Screen', form: form },
         position,
         events: {
             click: () => {
-                console.log('node' + id + ' clicked');
+                openForm(id, form);
             }
         }
     };
@@ -180,6 +181,11 @@ function addChapter() {
     findNode('2').position.y += 200;
 }
 
+function openForm(id: string, form: Form) {
+    console.log('open screen Form');
+    editorStore.openFormPanel(id, form);
+}
+
 </script>
 
 <template>
@@ -199,7 +205,7 @@ function addChapter() {
             <button style="background-color: #ff0000; padding: 1rem; border-radius: 8px; border: none; cursor: pointer; font-size: 1.2rem;" @click="onDelete">Delete all</button>
         </Panel>
         <template #node-custom="{ id, data }">
-            <ContentNode :id="id" :data="data" />
+            <ScreenNode :id="id" :data="data" />
         </template>
         <template #node-chapter="{ id, data }">
             <ChapterNode :id="id" :data="data" />
diff --git a/src/features/ePocFlow/nodes/ChapterNode.vue b/src/features/ePocFlow/nodes/ChapterNode.vue
index 8044da99..260b8d50 100644
--- a/src/features/ePocFlow/nodes/ChapterNode.vue
+++ b/src/features/ePocFlow/nodes/ChapterNode.vue
@@ -19,7 +19,7 @@ const props = defineProps<{
 const element: NodeElement = { id: props.id, action: { icon: 'icon-chapitre', type: 'chapter'}, form: editorStore.getForm('chapter') };
 
 function openForm(element: NodeElement) {
-    editorStore.openFormPanel(element);
+    editorStore.openFormPanel(element.id, element.form);
 }
 
 </script>
@@ -28,7 +28,7 @@ function openForm(element: NodeElement) {
     <div>
         <ContentButton 
             :icon="element.action.icon"
-            :is-active="editorStore.formPanel.openedElement ? editorStore.formPanel.openedElement.id === element.id : false"
+            :is-active="editorStore.openedNodeId ? editorStore.openedNodeId === element.id : false"
             :is-draggable="false"
             :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }"
             :subtitle="data.title"
diff --git a/src/features/ePocFlow/nodes/ContentNode.vue b/src/features/ePocFlow/nodes/ScreenNode.vue
similarity index 89%
rename from src/features/ePocFlow/nodes/ContentNode.vue
rename to src/features/ePocFlow/nodes/ScreenNode.vue
index b05d932f..bdfa2786 100644
--- a/src/features/ePocFlow/nodes/ContentNode.vue
+++ b/src/features/ePocFlow/nodes/ScreenNode.vue
@@ -3,7 +3,7 @@ import { Handle, useVueFlow } from '@vue-flow/core';
 import ContentButton from '../../../components/ContentButton.vue';
 import { onMounted } from 'vue';
 import { useEditorStore } from '../../../shared/stores';
-import { NodeElement } from '../../..//shared/interfaces';
+import { NodeElement } from '../../../shared/interfaces';
 import { Position } from '@vue-flow/core';
 
 const editorStore = useEditorStore();
@@ -62,7 +62,7 @@ function dragEnter(event) {
 const { nodes } = useVueFlow();
 
 function openForm(element: NodeElement) {
-    editorStore.openFormPanel(element);
+    editorStore.openFormPanel(element.id, element.form);
 }
 
 </script>
@@ -72,6 +72,8 @@ function openForm(element: NodeElement) {
     <Handle type="target" :position="Position.Left" />
     <div
         :id="'node'+props.id"
+        :class=" { 'active': editorStore.openedNodeId ? editorStore.openedNodeId === props.id : false }"
+        class="node"
         @dragover="dragOver"
         @dragleave="dragLeave"
         @dragenter="dragEnter"
@@ -80,7 +82,7 @@ function openForm(element: NodeElement) {
             v-for="element of props.data.elements"
             :key="element.action.icon"
             :icon="element.action.icon"
-            :is-active="editorStore.formPanel.openedElement ? editorStore.formPanel.openedElement.id === element.id : false"
+            :is-active="editorStore.openedNodeId ? editorStore.openedNodeId === element.id : false"
             :is-draggable="false"
             :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true }"
             @click="openForm(element)"
diff --git a/src/features/ePocFlow/nodes/ePocNode.vue b/src/features/ePocFlow/nodes/ePocNode.vue
index 9cc58976..8e0239ae 100644
--- a/src/features/ePocFlow/nodes/ePocNode.vue
+++ b/src/features/ePocFlow/nodes/ePocNode.vue
@@ -15,7 +15,7 @@ const props = defineProps<{
 const element: NodeElement = { id: props.id, action: { icon: 'icon-epoc', type: 'epoc'}, form: editorStore.getForm('epoc') };
 
 function openForm(element: NodeElement) {
-    editorStore.openFormPanel(element);
+    editorStore.openFormPanel(element.id, element.form);
 }
 
 </script>
@@ -24,7 +24,7 @@ function openForm(element: NodeElement) {
     <div>
         <ContentButton 
             :icon="element.action.icon"
-            :is-active="editorStore.formPanel.openedElement ? editorStore.formPanel.openedElement.id === element.id : false"
+            :is-active="editorStore.openedNodeId ? editorStore.openedNodeId === element.id : false"
             :is-draggable="false"
             :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }"
             subtitle="ePoc"
diff --git a/src/features/forms/components/inputs/card/CardGroup.vue b/src/features/forms/components/inputs/card/CardGroup.vue
index da7deed8..00c1ce50 100644
--- a/src/features/forms/components/inputs/card/CardGroup.vue
+++ b/src/features/forms/components/inputs/card/CardGroup.vue
@@ -35,6 +35,7 @@ const cardMap = new Map([
         :model-value="inputs"
         item-key="index"
         handle=".card-header"
+        ghost-class="ghost"
         @change="emit('swapCard', $event)"
     >
         <template #item="{element, index}">
@@ -57,8 +58,4 @@ const cardMap = new Map([
         class="add-card"
         @click="emit('addCard')"
     />
-</template>
-
-<style scoped lang="scss">
-
-</style>
\ No newline at end of file
+</template>
\ No newline at end of file
diff --git a/src/features/forms/components/inputs/card/CardInput.vue b/src/features/forms/components/inputs/card/CardInput.vue
index 8c2b983b..c6e4005e 100644
--- a/src/features/forms/components/inputs/card/CardInput.vue
+++ b/src/features/forms/components/inputs/card/CardInput.vue
@@ -20,18 +20,12 @@ const emit = defineEmits<{
     (e: 'moveDownCard'): void;
 }>();
 
-
-const dragging = ref(false);
-
 </script>
 
 <template>
     <Transition>
         <div
             class="card draggable-card"
-            :class="{ 'dragging' : dragging }"
-            @dragstart="dragging = true"
-            @dragend="dragging = false"
         >
             <div class="card-header">
                 <h3>{{ title }} {{ pos }}</h3>
@@ -64,7 +58,7 @@ const dragging = ref(false);
     width: 25rem;
     border-radius: 4px;
     margin-bottom: 1rem;
-    cursor: grab;
+    cursor: move;
     transition: all .2s linear;
     &-header {
         padding: 0 .7rem;
@@ -100,8 +94,12 @@ const dragging = ref(false);
     &-content {
         padding: .7rem;
     }
-    &.dragging {
-        opacity: .3;
+    &.ghost {
+        background-color: var(--item-background);
+        border: 2px dashed var(--border);
+        * {
+            opacity: 0;
+        }
     }
 }
 </style>
\ No newline at end of file
diff --git a/src/features/sideBar/components/ModelMenu.vue b/src/features/sideBar/components/ModelMenu.vue
index ee14f307..85d455c7 100644
--- a/src/features/sideBar/components/ModelMenu.vue
+++ b/src/features/sideBar/components/ModelMenu.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
 // The beta use the ModelMenuV0 component
 //@ts-nocheck
-import ScreenNode from './ScreenNode.vue';
+import ScreenNode from './ScreenTemplate.vue';
 import { useEpocStore } from '../../../shared/stores';
 import { ref } from 'vue';
 
diff --git a/src/features/sideBar/components/ModelMenuV0.vue b/src/features/sideBar/components/ModelMenuV0.vue
index 01abe82e..9ded0a4a 100644
--- a/src/features/sideBar/components/ModelMenuV0.vue
+++ b/src/features/sideBar/components/ModelMenuV0.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import ScreenNode from './ScreenNode.vue';
+import ScreenTemplate from './ScreenTemplate.vue';
 import { useEditorStore } from '../../../shared/stores';
 import { Ref, ref } from 'vue';
 import { Screen, SideAction } from '../../../shared/interfaces';
@@ -44,7 +44,7 @@ const displayScreen = ref(true);
         </div>
         <div v-if="displayScreen" class="screens">
             <div class="col-left">
-                <ScreenNode 
+                <ScreenTemplate
                     v-for="(screen, index) of leftCol"
                     :key="index"
                     :title="screen.title"
@@ -53,7 +53,7 @@ const displayScreen = ref(true);
                 />
             </div>
             <div class="col-right">
-                <ScreenNode 
+                <ScreenTemplate
                     v-for="(screen, index) of rightCol"
                     :key="index"
                     :title="screen.title"
diff --git a/src/features/sideBar/components/ScreenNode.vue b/src/features/sideBar/components/ScreenTemplate.vue
similarity index 100%
rename from src/features/sideBar/components/ScreenNode.vue
rename to src/features/sideBar/components/ScreenTemplate.vue
diff --git a/src/global.scss b/src/global.scss
index 82d534ce..1405c551 100644
--- a/src/global.scss
+++ b/src/global.scss
@@ -220,6 +220,12 @@ hr {
       margin-top: .5rem;
   }
 
+  &.active {
+    border: 1px solid var(--editor-blue);
+    box-shadow: 0 1px 8px 0 var(--editor-blue-shadow);
+    background-color: var(--node-active);
+  }
+
   &-animate {
       padding: 1.5rem;
       transition: all .15s ease-in-out;
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index b09c370d..a6347d0a 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -269,4 +269,34 @@ const epocForm: Form = {
     ]
 };
 
-export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm];
\ No newline at end of file
+const screenForm: Form = { 
+    type: 'screen',
+    name: 'Écran',
+    icon: 'icon-ecran',
+    fields: [
+        {
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Titre',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+            ]
+        }
+    ],
+    buttons: [
+        {
+            label: 'Supprimer',
+            icon: 'icon-supprimer',
+            action: 'delete'
+        },
+        {
+            label: 'Copier le lien',
+            icon: 'icon-copie',
+            action: 'copy'
+        },
+    ]
+};
+
+export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm];
\ No newline at end of file
diff --git a/src/shared/stores/editorStore.ts b/src/shared/stores/editorStore.ts
index 994289e2..5c830a3c 100644
--- a/src/shared/stores/editorStore.ts
+++ b/src/shared/stores/editorStore.ts
@@ -5,6 +5,8 @@ import { toRaw } from 'vue';
 
 import { formsModel } from '../data/form.data';
 
+type uid = string;
+
 interface EditorState {
     recentProjects: ePocProject[];
     floatingMenu: boolean;
@@ -12,9 +14,8 @@ interface EditorState {
     formPanel: {
         isOpen: boolean;
         form: Form;
-        openedElement: NodeElement;
-        openedScreen: Screen;
     };
+    openedNodeId: uid | null;
     sideActions: SideAction[];
     questions: SideAction[];
     standardScreens: Screen[];
@@ -29,9 +30,8 @@ export const useEditorStore = defineStore('editor', {
         formPanel: {
             isOpen: false,
             form: null,
-            openedElement: null,
-            openedScreen: null
         },
+        openedNodeId: null,
         sideActions: actionItems,
         questions: questions,
         standardScreens: standardScreen,
@@ -63,18 +63,18 @@ export const useEditorStore = defineStore('editor', {
             this.floatingMenu = false;
             this.modelMenu = false;
         },
-        openFormPanel(element: NodeElement): void {
+        openFormPanel(id: string, form: Form): void {
             this.formPanel.isOpen = true;
-            this.formPanel.form = element.form;
-            this.formPanel.openedElement = element;
+            this.formPanel.form = form;
+            this.openedNodeId = id;
         },
         closeFormPanel(): void {
             this.formPanel.isOpen = false;
             this.formPanel.form = null;
-            this.formPanel.openedElement = null;
+            this.openedNodeId = null;
         },
         //generate id of format 'aaaaaaaa'-'aaaa'-'aaaa'-'aaaa'-'aaaaaaaaaaaa'
-        generateId(): string {
+        generateId(): uid {
             const s4 = () => {
                 return Math.floor((1 + Math.random()) * 0x10000)
                     .toString(16)
@@ -94,7 +94,7 @@ export const useEditorStore = defineStore('editor', {
             //         type: 'remove',
             //     }
             // ]);
-            console.log(this.formPanel.openedElement.parentId);
+            console.log(this.openedNodeId);
         },
         addInput(type: string, fieldIndex: number):void {
             const newInput: Input = {
-- 
GitLab


From b40bf0738bc2f5702fc29abe1c428dfdbb1ddedf Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 10:36:31 +0100
Subject: [PATCH 03/13] Remove title prop from screen node

---
 src/features/ePocFlow/ePocFlow.vue         | 2 +-
 src/features/ePocFlow/nodes/ScreenNode.vue | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/features/ePocFlow/ePocFlow.vue b/src/features/ePocFlow/ePocFlow.vue
index a4caf150..9f6e39a9 100644
--- a/src/features/ePocFlow/ePocFlow.vue
+++ b/src/features/ePocFlow/ePocFlow.vue
@@ -105,7 +105,7 @@ function addNode(position, actions: SideAction[]) {
         id: id,
         type: 'content',
         // Put animated: nodeIcons.length === 1 when implementing v2
-        data: { elements: elements, readyToDrop: false, animated: false, title: 'Screen', form: form },
+        data: { elements: elements, readyToDrop: false, animated: false, form: form },
         position,
         events: {
             click: () => {
diff --git a/src/features/ePocFlow/nodes/ScreenNode.vue b/src/features/ePocFlow/nodes/ScreenNode.vue
index bdfa2786..411f3311 100644
--- a/src/features/ePocFlow/nodes/ScreenNode.vue
+++ b/src/features/ePocFlow/nodes/ScreenNode.vue
@@ -15,7 +15,6 @@ const props = defineProps<{
         required: true;
         readyToDrop: boolean;
         animated: boolean;
-        title: string;
         elements: NodeElement[];
     }
 }>();
@@ -68,7 +67,7 @@ function openForm(element: NodeElement) {
 </script>
 
 <template>
-    <p contenteditable="true" class="node-title">{{ data.title }}</p>
+    <p contenteditable="true" class="node-title">Screen</p>
     <Handle type="target" :position="Position.Left" />
     <div
         :id="'node'+props.id"
-- 
GitLab


From 2f241236149c8588346a2c33cce194fbec141e35 Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 10:18:14 +0100
Subject: [PATCH 04/13] Issue #55 #75: Can click on a screen node to edit the
 screen

---
 src/features/ePocFlow/nodes/ScreenNode.vue    |   3 +-
 .../ScreenNode.vue~refs/remotes/origin/main   | 113 ++++++++++++++++++
 .../components/inputs/card/CardInput.vue      |   1 -
 3 files changed, 115 insertions(+), 2 deletions(-)
 create mode 100644 src/features/ePocFlow/nodes/ScreenNode.vue~refs/remotes/origin/main

diff --git a/src/features/ePocFlow/nodes/ScreenNode.vue b/src/features/ePocFlow/nodes/ScreenNode.vue
index 411f3311..bdfa2786 100644
--- a/src/features/ePocFlow/nodes/ScreenNode.vue
+++ b/src/features/ePocFlow/nodes/ScreenNode.vue
@@ -15,6 +15,7 @@ const props = defineProps<{
         required: true;
         readyToDrop: boolean;
         animated: boolean;
+        title: string;
         elements: NodeElement[];
     }
 }>();
@@ -67,7 +68,7 @@ function openForm(element: NodeElement) {
 </script>
 
 <template>
-    <p contenteditable="true" class="node-title">Screen</p>
+    <p contenteditable="true" class="node-title">{{ data.title }}</p>
     <Handle type="target" :position="Position.Left" />
     <div
         :id="'node'+props.id"
diff --git a/src/features/ePocFlow/nodes/ScreenNode.vue~refs/remotes/origin/main b/src/features/ePocFlow/nodes/ScreenNode.vue~refs/remotes/origin/main
new file mode 100644
index 00000000..411f3311
--- /dev/null
+++ b/src/features/ePocFlow/nodes/ScreenNode.vue~refs/remotes/origin/main
@@ -0,0 +1,113 @@
+<script setup lang="ts">
+import { Handle, useVueFlow } from '@vue-flow/core';
+import ContentButton from '../../../components/ContentButton.vue';
+import { onMounted } from 'vue';
+import { useEditorStore } from '../../../shared/stores';
+import { NodeElement } from '../../../shared/interfaces';
+import { Position } from '@vue-flow/core';
+
+const editorStore = useEditorStore();
+
+const props = defineProps<{
+    id: string;
+    data: {
+        type: object;
+        required: true;
+        readyToDrop: boolean;
+        animated: boolean;
+        elements: NodeElement[];
+    }
+}>();
+
+// This add an animation when the node is added to the flow
+onMounted(() => {
+    const node = document.querySelector('#node' + props.id);
+    node.classList.add('node');
+    if(props.data.animated) node.classList.add('node-creation-animation');
+});
+
+//TODO: Think about refactoring this
+let dragOverCount = 0;
+
+function dragOver(event) {
+    if(event.dataTransfer.types.length > 1) return;
+    dragOverCount ++;
+    if(dragOverCount > 25) {
+        dragOverCount = 0;
+        nodes.value.find(element => element.id === props.id).data.readyToDrop = true;
+        document.querySelector('#node'+props.id).classList.add('node-animate');
+
+        // To be sure the counter is set to 1 when ready to drop
+        counter = 1;
+    }
+}
+
+// This counter is used to avoid triggering dragLeave when not necessary
+let counter = 0;
+
+function dragLeave() {
+    counter --;
+    if (counter > 0) return;
+    nodes.value.find(element => element.id === props.id).data.readyToDrop = false;
+    document.querySelector('#node'+props.id).classList.remove('node-animate');
+    dragOverCount = 0;
+}
+
+function dragEnter(event) {
+    event.preventDefault();
+    counter ++;
+}
+
+const { nodes } = useVueFlow();
+
+function openForm(element: NodeElement) {
+    editorStore.openFormPanel(element.id, element.form);
+}
+
+</script>
+
+<template>
+    <p contenteditable="true" class="node-title">Screen</p>
+    <Handle type="target" :position="Position.Left" />
+    <div
+        :id="'node'+props.id"
+        :class=" { 'active': editorStore.openedNodeId ? editorStore.openedNodeId === props.id : false }"
+        class="node"
+        @dragover="dragOver"
+        @dragleave="dragLeave"
+        @dragenter="dragEnter"
+    >
+        <ContentButton
+            v-for="element of props.data.elements"
+            :key="element.action.icon"
+            :icon="element.action.icon"
+            :is-active="editorStore.openedNodeId ? editorStore.openedNodeId === element.id : false"
+            :is-draggable="false"
+            :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true }"
+            @click="openForm(element)"
+        />
+    </div>
+    <Handle type="source" :position="Position.Right" />
+</template>
+
+<style scoped lang="scss">
+
+.vue-flow__handle {
+    width: 12px;
+    height: 12px;
+    &-left {
+        left: -6px;
+    }
+    &-right {
+        right: -6px;
+    }
+}
+.node-title {
+    margin: .2rem;
+    padding: .2rem;
+    &:focus-visible {
+        outline: 1px solid var(--editor-blue);
+        border-radius: 4px;
+    }
+}
+</style>
\ No newline at end of file
diff --git a/src/features/forms/components/inputs/card/CardInput.vue b/src/features/forms/components/inputs/card/CardInput.vue
index c6e4005e..fdfd4d76 100644
--- a/src/features/forms/components/inputs/card/CardInput.vue
+++ b/src/features/forms/components/inputs/card/CardInput.vue
@@ -2,7 +2,6 @@
 <script setup lang="ts">
 import TextAreaInput from '../TextAreaInput.vue';
 import CheckBoxInput from '../CheckBoxInput.vue';
-import { ref } from 'vue';
 
 defineProps<{
     inputValue: string;
-- 
GitLab


From 833fe608bafcfc5de8ccfbedb839b7f5688f4709 Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 10:36:31 +0100
Subject: [PATCH 05/13] Remove title prop from screen node

---
 src/features/ePocFlow/nodes/ScreenNode.vue | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/features/ePocFlow/nodes/ScreenNode.vue b/src/features/ePocFlow/nodes/ScreenNode.vue
index bdfa2786..411f3311 100644
--- a/src/features/ePocFlow/nodes/ScreenNode.vue
+++ b/src/features/ePocFlow/nodes/ScreenNode.vue
@@ -15,7 +15,6 @@ const props = defineProps<{
         required: true;
         readyToDrop: boolean;
         animated: boolean;
-        title: string;
         elements: NodeElement[];
     }
 }>();
@@ -68,7 +67,7 @@ function openForm(element: NodeElement) {
 </script>
 
 <template>
-    <p contenteditable="true" class="node-title">{{ data.title }}</p>
+    <p contenteditable="true" class="node-title">Screen</p>
     <Handle type="target" :position="Position.Left" />
     <div
         :id="'node'+props.id"
-- 
GitLab


From 670774f7935627e39f0a935156b4d93ad522cc41 Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 11:02:56 +0100
Subject: [PATCH 06/13] Issue #12: Audio form & screen template

---
 package-lock.json                |  1 +
 src/shared/data/form.data.ts     | 61 +++++++++++++++++++++++++++++++-
 src/shared/stores/editorStore.ts | 13 +++++++
 3 files changed, 74 insertions(+), 1 deletion(-)

diff --git a/package-lock.json b/package-lock.json
index a8abf29c..dac98426 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
     "": {
       "name": "epoc-editor-v2",
       "version": "0.0.0",
+      "license": "CeCILL-B",
       "dependencies": {
         "@vue-flow/additional-components": "^1.3.3",
         "@vue-flow/core": "^1.6.0",
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index a6347d0a..84c207cf 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -299,4 +299,63 @@ const screenForm: Form = {
     ]
 };
 
-export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm];
\ No newline at end of file
+const audioForm: Form = {
+    type: 'audio',
+    name: 'Audio',
+    icon: 'icon-audio',
+    fields: [
+        {
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Titre',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'file',
+                    label: 'Piste audio',
+                    value: '',
+                    placeholder: 'Ajouter une piste audio',
+                    accept: 'audio/*'
+                },
+                {
+                    type: 'file',
+                    label: 'Transcription',
+                    value: '',
+                    placeholder: 'Ajouter une transcription',
+                    accept: 'text/*'
+                },
+                {
+                    type: 'file',
+                    label: 'Vignette',
+                    value: '',
+                    placeholder: 'Ajouter une vignette',
+                    accept: 'image/*'
+                },
+                {
+                    type: 'file',
+                    label: 'Sous-titres',
+                    value: '',
+                    placeholder: 'Ajouter des sous-titres',
+                    accept: '.vtt'
+                }
+            ]
+        }
+    ],
+    buttons: [
+        {
+            label: 'Supprimer',
+            icon: 'icon-supprimer',
+            action: 'delete'
+        },
+        {
+            label: 'Copier le lien',
+            icon: 'icon-copie',
+            action: 'copy'
+        },
+    ]
+};
+
+
+export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm];
\ No newline at end of file
diff --git a/src/shared/stores/editorStore.ts b/src/shared/stores/editorStore.ts
index 5c830a3c..9ef83449 100644
--- a/src/shared/stores/editorStore.ts
+++ b/src/shared/stores/editorStore.ts
@@ -131,6 +131,19 @@ const standardScreen: Screen[] = [
             }
         ]
     },
+    {
+        title: 'Ecran audio',
+        actions: [
+            {
+                icon: 'icon-audio',
+                type: 'audio'
+            },
+            {
+                icon: 'icon-texte',
+                type: 'text'
+            }
+        ]
+    }
 ];
 
 
-- 
GitLab


From 9cca55e57c1a04647681a582f0d03e6f19753e53 Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 11:05:10 +0100
Subject: [PATCH 07/13] merge solve

---
 src/shared/data/form.data.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index 84c207cf..c73251a2 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -358,4 +358,4 @@ const audioForm: Form = {
 };
 
 
-export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm];
\ No newline at end of file
+export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm];
-- 
GitLab


From 439e931fee0ba1df5d6181e465f496bd1e89103f Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 11:56:15 +0100
Subject: [PATCH 08/13] Issue #13 #4: Drag & Drop Form

---
 .../forms/components/inputs/GenericInput.vue  |   2 +-
 .../components/inputs/card/CardGroup.vue      |   4 +-
 .../components/inputs/card/CardInput.vue      |   4 +-
 .../components/inputs/card/SelectInput.vue    |  46 +++++
 .../{ => card/components}/CheckBoxInput.vue   |   0
 .../{ => card/components}/FileInput.vue       |   0
 src/shared/data/form.data.ts                  | 194 ++++++++++++------
 7 files changed, 189 insertions(+), 61 deletions(-)
 create mode 100644 src/features/forms/components/inputs/card/SelectInput.vue
 rename src/features/forms/components/inputs/{ => card/components}/CheckBoxInput.vue (100%)
 rename src/features/forms/components/inputs/{ => card/components}/FileInput.vue (100%)

diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue
index 75b03360..3e519748 100644
--- a/src/features/forms/components/inputs/GenericInput.vue
+++ b/src/features/forms/components/inputs/GenericInput.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import TextInput from './TextInput.vue';
 import TextAreaInput from './TextAreaInput.vue';
-import FileInput from './FileInput.vue';
+import FileInput from './card/components/FileInput.vue';
 
 defineProps<{
     type: string;
diff --git a/src/features/forms/components/inputs/card/CardGroup.vue b/src/features/forms/components/inputs/card/CardGroup.vue
index 00c1ce50..6351db42 100644
--- a/src/features/forms/components/inputs/card/CardGroup.vue
+++ b/src/features/forms/components/inputs/card/CardGroup.vue
@@ -23,7 +23,9 @@ const emit = defineEmits<{
 //? This way doesn't seem to be optimal
 const cardMap = new Map([
     ['check', 'Réponse'],
-    ['objective', 'Objectif']
+    ['objective', 'Objectif'],
+    ['category', 'Catégorie'],
+    ['dd', 'Réponse'],
 ]);
 
 </script>
diff --git a/src/features/forms/components/inputs/card/CardInput.vue b/src/features/forms/components/inputs/card/CardInput.vue
index fdfd4d76..bc471080 100644
--- a/src/features/forms/components/inputs/card/CardInput.vue
+++ b/src/features/forms/components/inputs/card/CardInput.vue
@@ -1,7 +1,8 @@
 
 <script setup lang="ts">
 import TextAreaInput from '../TextAreaInput.vue';
-import CheckBoxInput from '../CheckBoxInput.vue';
+import CheckBoxInput from './components/CheckBoxInput.vue';
+import SelectInput from './SelectInput.vue';
 
 defineProps<{
     inputValue: string;
@@ -47,6 +48,7 @@ const emit = defineEmits<{
                 />
             </div>
             <CheckBoxInput v-if="type === 'check'" />
+            <SelectInput v-if="type === 'dd'" :label="'À quelle catégorie appartient cette réponse ' + pos" />
         </div>
     </Transition>
 </template>
diff --git a/src/features/forms/components/inputs/card/SelectInput.vue b/src/features/forms/components/inputs/card/SelectInput.vue
new file mode 100644
index 00000000..7976e163
--- /dev/null
+++ b/src/features/forms/components/inputs/card/SelectInput.vue
@@ -0,0 +1,46 @@
+<script setup lang="ts">
+
+defineProps<{
+    label: string;
+}>();
+
+</script>
+
+<template>
+    <div class="select">
+        <label for="select-box">{{ label }}</label>
+        <select id="select-box" class="select-box">
+            <option value="">Sélectionnez</option>
+            <option value="1">Catégorie 1</option>
+            <option value="2">Catégorie 2</option>
+            <option value="3">Catégorie 3</option>
+        </select>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.select {
+    display: flex;
+    flex-direction: column;
+    margin-bottom: 1rem;
+    label {
+        margin-bottom: 0.5rem;
+    }
+    select {
+        appearance: none;
+        padding: .5rem;
+        border: 1px solid var(--border);
+        border-radius: 4px;
+        background-color: var(--item-background);
+        cursor: pointer;
+        font-size: 1rem;
+        color: var(--text);
+
+        background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgd2lkdGg9IjExcHgiIGhlaWdodD0iN3B4IiB2aWV3Qm94PSIwIDAgMTEuMCA3LjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxjbGlwUGF0aCBpZD0iaTAiPjxwYXRoIGQ9Ik0yNDE4LDAgTDI0MTgsMjQyNiBMMCwyNDI2IEwwLDAgTDI0MTgsMCBaIj48L3BhdGg+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImkxIj48cGF0aCBkPSJNOS4yMDE3MjIyNywwIEM5LjYxNjUwNDA2LDAgOS45OTA4OTcyMSwwLjI3MzU1MDk4NyAxMC4xNDk4ODU5LDAuNjk0MzM1OTM3IEMxMC4zMDg4NzQ2LDEuMTE1MTIwODkgMTAuMjQ5ODk0OSwxLjU5OTYwOTM3IDkuOTU0OTk2NjMsMS45MTk1MzE0NiBMNS44ODA5MDQ1NSw2LjQxOTUzMTQ2IEM1LjY1MzMxOTM0LDYuNjQxMDE1NDEgNS4zOTA0NzQ3Niw2Ljc1IDUuMTI3NjMwMTksNi43NSBDNC44NjQ3ODU2Miw2Ljc1IDQuNjAyNTgxNzgsNi42NDAxMzY3MiA0LjQwMjI0Mjg2LDYuNDIwNDEwMTYgTDAuMzI4MTUwMjkxLDEuOTIwNDEwMTYgQzAuMDA2MTQ2NTU1MTksMS41OTk2MDkzNyAtMC4wODE2OTQ5MjIzLDEuMTE0NDUzMDIgMC4wNzcxMDE1NTQ3LDAuNjk2MDkzODU3IEMwLjIzNTg5ODAzMiwwLjI3NzczNDY5NyAwLjYxMDIyNzEwNiwwIDEuMDI0Njg4NTMsMCBMOS4yMDE3MjIyNywwIFoiPjwvcGF0aD48L2NsaXBQYXRoPjwvZGVmcz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTMwNC4wIC0xMDYyLjApIj48ZyBjbGlwLXBhdGg9InVybCgjaTApIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5ODAuMCA5MC4wKSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjAuMCA3MjkuMCkiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMCA1MS4wKSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTUuMCAxNTAuMCkiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMCAyNC4wKSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjg5LjM3MjM2OTgwNjgwMSAxOC4wKSI+PGcgY2xpcC1wYXRoPSJ1cmwoI2kxKSI+PHBvbHlnb24gcG9pbnRzPSItMS4xMTAyMjMwMmUtMTYsMCAxMC4yMzYwMTA0LDAgMTAuMjM2MDEwNCw2Ljc1IC0xLjExMDIyMzAyZS0xNiw2Ljc1IC0xLjExMDIyMzAyZS0xNiwwIiBzdHJva2U9Im5vbmUiIGZpbGw9IiMzNTQyNTgiPjwvcG9seWdvbj48L2c+PC9nPjwvZz48L2c+PC9nPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=");
+        background-repeat: no-repeat;
+        background-position: right 0.7rem top 50%;
+        background-size: .8rem auto;
+    }
+    margin: 0 1rem 1rem 1rem;
+}
+</style>
\ No newline at end of file
diff --git a/src/features/forms/components/inputs/CheckBoxInput.vue b/src/features/forms/components/inputs/card/components/CheckBoxInput.vue
similarity index 100%
rename from src/features/forms/components/inputs/CheckBoxInput.vue
rename to src/features/forms/components/inputs/card/components/CheckBoxInput.vue
diff --git a/src/features/forms/components/inputs/FileInput.vue b/src/features/forms/components/inputs/card/components/FileInput.vue
similarity index 100%
rename from src/features/forms/components/inputs/FileInput.vue
rename to src/features/forms/components/inputs/card/components/FileInput.vue
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index c73251a2..6ea6e496 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -107,62 +107,6 @@ const videoForm: Form = {
     ]
 };
 
-const qcmForm: Form = {
-    type: 'qcm',
-    name: 'QCM',
-    icon: 'icon-qcm',
-    fields: [
-        {
-            name: 'Configuration de l\'activité',
-            index: 1,
-            inputs: [
-                {
-                    type: 'text',
-                    label: 'Titre',
-                    value: '',
-                    placeholder: 'Saisissez...'
-                },
-                {
-                    type: 'textarea',
-                    label: 'Énoncé',
-                    value: '',
-                    placeholder: 'Saisissez'
-                }
-            ]
-        },
-        {
-            name: 'Question',
-            index: 2,
-            inputs: [
-                {
-                    type: 'textarea',
-                    label: '',
-                    value: '',
-                    placeholder: 'Saisissez l\'intitulé de la question...'
-                }
-            ]
-        },
-        {
-            name: 'Réponses',
-            index: 3,
-            type: 'cardGroup',
-            inputType: 'check',
-            inputs: []
-        },
-        {
-            name: 'Explication',
-            index: 4,
-            inputs: [
-                {
-                    type: 'textarea',
-                    label: '',
-                    value: '',
-                    placeholder: 'Saisissez une explication'
-                }
-            ]
-        }
-    ]
-};
 
 const chapterForm: Form = {
     type: 'chapter',
@@ -244,7 +188,7 @@ const epocForm: Form = {
                     type: 'text',
                     label: 'ID de l\'ePoc',
                     value: 'id234567890',
-    
+                    
                 },
                 {
                     type: 'text',
@@ -357,5 +301,139 @@ const audioForm: Form = {
     ]
 };
 
+// Question forms
+
+const qcmForm: Form = {
+    type: 'qcm',
+    name: 'QCM',
+    icon: 'icon-qcm',
+    fields: [
+        {
+            name: 'Configuration de l\'activité',
+            index: 1,
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Titre',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'textarea',
+                    label: 'Énoncé',
+                    value: '',
+                    placeholder: 'Saisissez'
+                }
+            ]
+        },
+        {
+            name: 'Question',
+            index: 2,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez l\'intitulé de la question...'
+                }
+            ]
+        },
+        {
+            name: 'Réponses',
+            index: 3,
+            type: 'cardGroup',
+            inputType: 'check',
+            inputs: []
+        },
+        {
+            name: 'Explication',
+            index: 4,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez une explication'
+                }
+            ]
+        }
+    ]
+};
+
+const dragDropForm: Form = {
+    type: 'dragdrop',
+    name: 'Drag & Drop',
+    icon: 'icon-dragdrop',
+    fields: [
+        {
+            name: 'Configuration de l\'activité',
+            index: 1,
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Titre',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'textarea',
+                    label: 'Énoncé',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                }
+            ]
+        },
+        {
+            name: 'Question',
+            index: 2,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez l\'intitulé de la question...'
+                }
+            ]
+        },
+        {
+            name: 'Catégories de réponses proposées',
+            index: 3,
+            type: 'cardGroup',
+            inputType: 'category',
+            inputs: [],
+        },
+        {
+            name: 'Réponses proposées',
+            index: 4,
+            type: 'cardGroup',
+            inputType: 'dd',
+            inputs: [],
+        },
+        {
+            name: 'Explication',
+            index: 5,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez une explication'
+                }
+            ]
+        }
+    ],
+    buttons: [
+        {
+            label: 'Supprimer',
+            icon: 'icon-supprimer',
+            action: 'delete'
+        },
+        {
+            label: 'Copier le lien',
+            icon: 'icon-copie',
+            action: 'copy'
+        },
+    ]
+};
 
-export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm];
+export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm, dragDropForm];
-- 
GitLab


From d28b28556ffb04c0f882dde1c7870ee3f35a722a Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 12:07:17 +0100
Subject: [PATCH 09/13] =?UTF-8?q?Issue=C2=A0#5:=20Reorder=20Form?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../{card/components => }/FileInput.vue       |  0
 .../forms/components/inputs/GenericInput.vue  |  2 +-
 .../components/inputs/card/CardGroup.vue      |  1 +
 .../components/inputs/card/CardInput.vue      |  3 +-
 .../card/{ => components}/SelectInput.vue     |  6 +-
 src/shared/data/form.data.ts                  | 71 ++++++++++++++++++-
 6 files changed, 77 insertions(+), 6 deletions(-)
 rename src/features/forms/components/inputs/{card/components => }/FileInput.vue (100%)
 rename src/features/forms/components/inputs/card/{ => components}/SelectInput.vue (94%)

diff --git a/src/features/forms/components/inputs/card/components/FileInput.vue b/src/features/forms/components/inputs/FileInput.vue
similarity index 100%
rename from src/features/forms/components/inputs/card/components/FileInput.vue
rename to src/features/forms/components/inputs/FileInput.vue
diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue
index 3e519748..75b03360 100644
--- a/src/features/forms/components/inputs/GenericInput.vue
+++ b/src/features/forms/components/inputs/GenericInput.vue
@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import TextInput from './TextInput.vue';
 import TextAreaInput from './TextAreaInput.vue';
-import FileInput from './card/components/FileInput.vue';
+import FileInput from './FileInput.vue';
 
 defineProps<{
     type: string;
diff --git a/src/features/forms/components/inputs/card/CardGroup.vue b/src/features/forms/components/inputs/card/CardGroup.vue
index 6351db42..f3c4b36e 100644
--- a/src/features/forms/components/inputs/card/CardGroup.vue
+++ b/src/features/forms/components/inputs/card/CardGroup.vue
@@ -26,6 +26,7 @@ const cardMap = new Map([
     ['objective', 'Objectif'],
     ['category', 'Catégorie'],
     ['dd', 'Réponse'],
+    ['reorder', 'Réponse position']
 ]);
 
 </script>
diff --git a/src/features/forms/components/inputs/card/CardInput.vue b/src/features/forms/components/inputs/card/CardInput.vue
index bc471080..47705eec 100644
--- a/src/features/forms/components/inputs/card/CardInput.vue
+++ b/src/features/forms/components/inputs/card/CardInput.vue
@@ -2,7 +2,7 @@
 <script setup lang="ts">
 import TextAreaInput from '../TextAreaInput.vue';
 import CheckBoxInput from './components/CheckBoxInput.vue';
-import SelectInput from './SelectInput.vue';
+import SelectInput from './components/SelectInput.vue';
 
 defineProps<{
     inputValue: string;
@@ -49,6 +49,7 @@ const emit = defineEmits<{
             </div>
             <CheckBoxInput v-if="type === 'check'" />
             <SelectInput v-if="type === 'dd'" :label="'À quelle catégorie appartient cette réponse ' + pos" />
+            <SelectInput v-if="type === 'reorder'" label="Position affiché à l'écran avant réorganisation" />
         </div>
     </Transition>
 </template>
diff --git a/src/features/forms/components/inputs/card/SelectInput.vue b/src/features/forms/components/inputs/card/components/SelectInput.vue
similarity index 94%
rename from src/features/forms/components/inputs/card/SelectInput.vue
rename to src/features/forms/components/inputs/card/components/SelectInput.vue
index 7976e163..ae3bd60a 100644
--- a/src/features/forms/components/inputs/card/SelectInput.vue
+++ b/src/features/forms/components/inputs/card/components/SelectInput.vue
@@ -11,9 +11,9 @@ defineProps<{
         <label for="select-box">{{ label }}</label>
         <select id="select-box" class="select-box">
             <option value="">Sélectionnez</option>
-            <option value="1">Catégorie 1</option>
-            <option value="2">Catégorie 2</option>
-            <option value="3">Catégorie 3</option>
+            <option value="1">Position 1</option>
+            <option value="2">Position 2</option>
+            <option value="3">Position 3</option>
         </select>
     </div>
 </template>
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index 6ea6e496..c93d578b 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -436,4 +436,73 @@ const dragDropForm: Form = {
     ]
 };
 
-export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm, dragDropForm];
+const reorderForm: Form = {
+    type: 'reorder',
+    name: 'Reorder',
+    icon: 'icon-reorder',
+    fields: [
+        {
+            name: 'Configuration de l\'activité',
+            index: 1,
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Titre',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'textarea',
+                    label: 'Énoncé',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                }
+            ]
+        },
+        {
+            name: 'Question',
+            index: 2,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez l\'intitulé de la question...'
+                }
+            ]
+        },
+        {
+            name: 'Réponses',
+            index: 3,
+            type: 'cardGroup',
+            inputType: 'reorder',
+            inputs: [],
+        },
+        {
+            name: 'Explication',
+            index: 4,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez une explication...'
+                }
+            ]
+        }
+    ],
+    buttons: [
+        {
+            label: 'Supprimer',
+            icon: 'icon-supprimer',
+            action: 'delete'
+        },
+        {
+            label: 'Copier le lien',
+            icon: 'icon-copie',
+            action: 'copy'
+        },
+    ]
+};
+
+export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm, dragDropForm, reorderForm];
-- 
GitLab


From aae7aea769aad7f08f84ac254d55ec36e36c0a1d Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 13:59:47 +0100
Subject: [PATCH 10/13] Issue #11: Score input

---
 .../forms/components/inputs/GenericInput.vue  |  7 ++
 .../forms/components/inputs/ScoreInput.vue    | 82 +++++++++++++++++++
 src/shared/data/form.data.ts                  | 15 ++++
 3 files changed, 104 insertions(+)
 create mode 100644 src/features/forms/components/inputs/ScoreInput.vue

diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue
index 75b03360..7cfcc16e 100644
--- a/src/features/forms/components/inputs/GenericInput.vue
+++ b/src/features/forms/components/inputs/GenericInput.vue
@@ -2,6 +2,7 @@
 import TextInput from './TextInput.vue';
 import TextAreaInput from './TextAreaInput.vue';
 import FileInput from './FileInput.vue';
+import ScoreInput from './ScoreInput.vue';
 
 defineProps<{
     type: string;
@@ -49,4 +50,10 @@ const emit = defineEmits<{
         :placeholder="placeholder"
         @input="emit('update:modelValue', $event)"
     />
+    <ScoreInput
+        v-if="type === 'score'"
+        :label="label"
+        :input-value="inputValue"
+        @input="emit('input', $event)"
+    />
 </template>
\ No newline at end of file
diff --git a/src/features/forms/components/inputs/ScoreInput.vue b/src/features/forms/components/inputs/ScoreInput.vue
new file mode 100644
index 00000000..061a0a5f
--- /dev/null
+++ b/src/features/forms/components/inputs/ScoreInput.vue
@@ -0,0 +1,82 @@
+<script setup lang="ts">
+
+defineProps<{
+    inputValue: string;
+}>();
+
+const emit = defineEmits<{
+    (e: 'input', value: string): void;
+}>();
+
+function minus(inputValue: string) {
+    const value = parseInt(inputValue) - 1;
+    emit('input', value.toString());
+}
+
+function plus(inputValue: string) {
+    const value = parseInt(inputValue) + 1;
+    emit('input', value.toString());
+}
+
+</script>
+
+<template>
+    <label for="input-score">Score</label>
+    <div id="input-score" class="input-score">
+        <button @click="minus(inputValue)"><i class="icon-minus-circle"></i></button>
+        <input
+            type="number"
+            :value="inputValue"
+            @input="emit('input', ($event.target as HTMLInputElement).value)"
+        >
+        <button @click="plus(inputValue)"><i class="icon-plus-circle"></i></button>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.input-score {
+    background-color: var(--item-background);
+    border: 1px solid var(--border);
+    border-radius: 4px;
+    width: fit-content;
+    margin-bottom: 1.5rem;
+
+    input[type="number"] {
+        border: none;
+        margin: .5rem;
+        background-color: transparent;
+        font-size: 1.1rem;
+        outline: none;
+        color: var(--editor-grayblue);
+        width: 4rem;
+        text-align: center;
+        &::-webkit-outer-spin-button {
+            -webkit-appearance: none;
+            margin: 0;
+        }
+        &::-webkit-inner-spin-button {
+            -webkit-appearance: none;
+            margin: 0;
+        }
+    }
+    
+    button {
+        border: none;
+        background-color: transparent;
+        font-size: 1.4rem;
+        outline: none;
+        color: var(--editor-grayblue);
+        cursor: pointer;
+        height: 100%;
+    }
+
+    i {
+        display: block;
+    }
+}
+
+label {
+    margin-bottom: .5rem;
+    font-size: 1rem;
+}
+</style>
\ No newline at end of file
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index c93d578b..43cb6d26 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -323,6 +323,11 @@ const qcmForm: Form = {
                     label: 'Énoncé',
                     value: '',
                     placeholder: 'Saisissez'
+                },
+                {
+                    type: 'score',
+                    label: 'Score',
+                    value: '0',
                 }
             ]
         },
@@ -380,6 +385,11 @@ const dragDropForm: Form = {
                     label: 'Énoncé',
                     value: '',
                     placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'score',
+                    label: 'Score',
+                    value: '0',
                 }
             ]
         },
@@ -456,6 +466,11 @@ const reorderForm: Form = {
                     label: 'Énoncé',
                     value: '',
                     placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'score',
+                    label: 'Score',
+                    value: '0',
                 }
             ]
         },
-- 
GitLab


From 6b152c48ab4d42ea48e47c86188477874e115a89 Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 14:37:10 +0100
Subject: [PATCH 11/13] Issue #6: Swipe Form

---
 .../components/inputs/card/CardGroup.vue      |  3 +-
 .../components/inputs/card/CardInput.vue      |  2 +
 .../inputs/card/components/RadioInput.vue     | 82 ++++++++++++++++
 src/shared/data/form.data.ts                  | 94 ++++++++++++++++++-
 4 files changed, 179 insertions(+), 2 deletions(-)
 create mode 100644 src/features/forms/components/inputs/card/components/RadioInput.vue

diff --git a/src/features/forms/components/inputs/card/CardGroup.vue b/src/features/forms/components/inputs/card/CardGroup.vue
index f3c4b36e..61cccff7 100644
--- a/src/features/forms/components/inputs/card/CardGroup.vue
+++ b/src/features/forms/components/inputs/card/CardGroup.vue
@@ -26,7 +26,8 @@ const cardMap = new Map([
     ['objective', 'Objectif'],
     ['category', 'Catégorie'],
     ['dd', 'Réponse'],
-    ['reorder', 'Réponse position']
+    ['reorder', 'Réponse position'],
+    ['swipe', 'Carte'],
 ]);
 
 </script>
diff --git a/src/features/forms/components/inputs/card/CardInput.vue b/src/features/forms/components/inputs/card/CardInput.vue
index 47705eec..7710e4a3 100644
--- a/src/features/forms/components/inputs/card/CardInput.vue
+++ b/src/features/forms/components/inputs/card/CardInput.vue
@@ -3,6 +3,7 @@
 import TextAreaInput from '../TextAreaInput.vue';
 import CheckBoxInput from './components/CheckBoxInput.vue';
 import SelectInput from './components/SelectInput.vue';
+import RadioInput from './components/RadioInput.vue';
 
 defineProps<{
     inputValue: string;
@@ -50,6 +51,7 @@ const emit = defineEmits<{
             <CheckBoxInput v-if="type === 'check'" />
             <SelectInput v-if="type === 'dd'" :label="'À quelle catégorie appartient cette réponse ' + pos" />
             <SelectInput v-if="type === 'reorder'" label="Position affiché à l'écran avant réorganisation" />
+            <RadioInput :index="pos" v-if="type === 'swipe'" />
         </div>
     </Transition>
 </template>
diff --git a/src/features/forms/components/inputs/card/components/RadioInput.vue b/src/features/forms/components/inputs/card/components/RadioInput.vue
new file mode 100644
index 00000000..8db062d1
--- /dev/null
+++ b/src/features/forms/components/inputs/card/components/RadioInput.vue
@@ -0,0 +1,82 @@
+<script setup lang="ts">
+
+defineProps<{
+    index: number;
+}>();
+
+</script>
+
+<template>
+    <div class="radio">
+        <label class="group-label" for="radio-group">Réponse</label>
+        <div id="radio-group" class="radio-group">
+            <div class="radio-btn">
+                <input
+                    id="left"
+                    :name="'pos' + index"
+                    type="radio"
+                    class="radio-input"
+                >
+                <label for="left">Choix gauche</label>
+            </div>
+            <div class="radio-btn">
+                <input
+                    id="right"
+                    :name="'pos' + index"
+                    type="radio"
+                    class="radio-input"
+                >
+                <label for="right">Choix droite</label>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.radio {
+    margin: 0 1rem 1rem 1rem;
+
+    .radio-group {
+        margin-top: .5rem;
+        display: flex;
+        justify-content: space-between;
+
+        .radio-btn {
+            display: flex;
+            &:last-child {
+            margin-right: 0.5rem;
+            }
+
+            label {
+                margin-left: .5rem;
+            }
+            input[type="radio"] {
+                appearance: none;
+                width: 20px;
+                height: 20px;
+                border: 2px solid var(--border);
+                border-radius: 50%;
+                margin: 0;
+                cursor: pointer;
+                transform: translateY(0.075rem);
+                display: grid;
+                place-content: center;
+                
+                &::before {
+                    content: '';
+                    width: 12px;
+                    height: 12px;
+                    transform: scale(0);
+                    transition: .1s transform ease-in-out;
+                    border-radius: 50%;
+                    background-color: var(--editor-blue);
+                }
+
+                &:checked::before {
+                    transform: scale(1);
+                }
+            }
+        }
+    }
+}
+</style>
\ No newline at end of file
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index 43cb6d26..62a51719 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -520,4 +520,96 @@ const reorderForm: Form = {
     ]
 };
 
-export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm, dragDropForm, reorderForm];
+const swipeForm: Form = {
+    type: 'swipe',
+    name: 'Swipe',
+    icon: 'icon-swipe',
+    fields: [
+        {
+            name: 'Configuration de l\'activité',
+            index: 1,
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Titre',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'textarea',
+                    label: 'Énoncé',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'score',
+                    label: 'Score',
+                    value: '0',
+                }
+            ]
+        },
+        {
+            name: 'Question',
+            index: 2,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez l\'intitulé de la question...'
+                }
+            ]
+        },
+        {
+            name: 'Catégories de choix proposées',
+            index: 3,
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Choix gauche',
+                    value: '',
+                    placeholder: 'Saisissez une réponse...'
+                },
+                {
+                    type: 'text',
+                    label: 'Choix droite',
+                    value: '',
+                    placeholder: 'Saisissez une réponse...'
+                }
+            ]
+        },
+        {
+            name: 'Cartes',
+            index: 4,
+            type: 'cardGroup',
+            inputType: 'swipe',
+            inputs: [],
+        },
+        {
+            name: 'Explication',
+            index: 5,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez une explication...'
+                }
+            ]
+        }
+    ],
+    buttons: [
+        {
+            label: 'Supprimer',
+            icon: 'icon-supprimer',
+            action: 'delete'
+        },
+        {
+            label: 'Copier le lien',
+            icon: 'icon-copie',
+            action: 'copy'
+        },
+    ]
+};
+
+export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm, dragDropForm, reorderForm, swipeForm];
-- 
GitLab


From 1072af09373b2b59a42b5b3787c4434f2cce55c9 Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 14:48:55 +0100
Subject: [PATCH 12/13] Issue #7: Dropdown list

---
 .../components/inputs/card/CardGroup.vue      |  2 +
 .../components/inputs/card/CardInput.vue      |  3 +-
 src/shared/data/form.data.ts                  | 84 ++++++++++++++++++-
 3 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/src/features/forms/components/inputs/card/CardGroup.vue b/src/features/forms/components/inputs/card/CardGroup.vue
index 61cccff7..1ca1dd82 100644
--- a/src/features/forms/components/inputs/card/CardGroup.vue
+++ b/src/features/forms/components/inputs/card/CardGroup.vue
@@ -28,6 +28,8 @@ const cardMap = new Map([
     ['dd', 'Réponse'],
     ['reorder', 'Réponse position'],
     ['swipe', 'Carte'],
+    ['list-choice', 'Choix'],
+    ['list', 'Carte']
 ]);
 
 </script>
diff --git a/src/features/forms/components/inputs/card/CardInput.vue b/src/features/forms/components/inputs/card/CardInput.vue
index 7710e4a3..11930517 100644
--- a/src/features/forms/components/inputs/card/CardInput.vue
+++ b/src/features/forms/components/inputs/card/CardInput.vue
@@ -51,7 +51,8 @@ const emit = defineEmits<{
             <CheckBoxInput v-if="type === 'check'" />
             <SelectInput v-if="type === 'dd'" :label="'À quelle catégorie appartient cette réponse ' + pos" />
             <SelectInput v-if="type === 'reorder'" label="Position affiché à l'écran avant réorganisation" />
-            <RadioInput :index="pos" v-if="type === 'swipe'" />
+            <SelectInput v-if="type === 'list'" label="Réponse" />
+            <RadioInput v-if="type === 'swipe'" :index="pos" />
         </div>
     </Transition>
 </template>
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index 62a51719..9cda77d6 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -612,4 +612,86 @@ const swipeForm: Form = {
     ]
 };
 
-export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm, dragDropForm, reorderForm, swipeForm];
+const listForm: Form = {
+    type: 'list',
+    name: 'Listes déroulantes',
+    icon: 'icon-liste',
+    fields: [
+        {
+            name: 'Configuration de l\'activité',
+            index: 1,
+            inputs: [
+                {
+                    type: 'text',
+                    label: 'Titre',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'textarea',
+                    label: 'Énoncé',
+                    value: '',
+                    placeholder: 'Saisissez...'
+                },
+                {
+                    type: 'score',
+                    label: 'Score',
+                    value: '0',
+                }
+            ]
+        },
+        {
+            name: 'Question',
+            index: 2,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez l\'intitulé de la question...'
+                }
+            ]
+        },
+        {
+            name: 'Catégories de choix proposées',
+            index: 3,
+            type: 'cardGroup',
+            inputType: 'list-choice',
+            inputs: []
+        },
+        {
+            name: 'Cartes',
+            index: 4,
+            type: 'cardGroup',
+            inputType: 'list',
+            inputs: [],
+        },
+        {
+            name: 'Explication',
+            index: 5,
+            inputs: [
+                {
+                    type: 'textarea',
+                    label: '',
+                    value: '',
+                    placeholder: 'Saisissez une explication...'
+                }
+            ]
+        }
+    ],
+    buttons: [
+        {
+            label: 'Supprimer',
+            icon: 'icon-supprimer',
+            action: 'delete'
+        },
+        {
+            label: 'Copier le lien',
+            icon: 'icon-copie',
+            action: 'copy'
+        },
+    ]
+};
+
+
+export const formsModel: Form[] = [textForm, videoForm, qcmForm, chapterForm, epocForm, screenForm, audioForm, dragDropForm, reorderForm, swipeForm, listForm];
-- 
GitLab


From 64e046d312b0f2b899bb722e85eeed06efcac6cc Mon Sep 17 00:00:00 2001
From: NathanViaud <79544144+NathanViaud@users.noreply.github.com>
Date: Wed, 18 Jan 2023 17:25:35 +0100
Subject: [PATCH 13/13] Issue #1: Quill Editor

---
 package-lock.json                             | 274 +++++++++++++++---
 package.json                                  |   1 +
 .../forms/components/inputs/GenericInput.vue  |   8 +
 .../forms/components/inputs/QuillEditor.vue   |  97 +++++++
 src/global.scss                               |   5 +-
 src/shared/data/form.data.ts                  |  20 +-
 6 files changed, 349 insertions(+), 56 deletions(-)
 create mode 100644 src/features/forms/components/inputs/QuillEditor.vue

diff --git a/package-lock.json b/package-lock.json
index dac98426..0cbd01de 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
       "dependencies": {
         "@vue-flow/additional-components": "^1.3.3",
         "@vue-flow/core": "^1.6.0",
+        "@vueup/vue-quill": "^1.0.1",
         "pinia": "^2.0.28",
         "sass": "^1.56.2",
         "vue": "^3.2.45",
@@ -1789,6 +1790,33 @@
         "vue": "^3.0.1"
       }
     },
+    "node_modules/@vueup/vue-quill": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@vueup/vue-quill/-/vue-quill-1.0.1.tgz",
+      "integrity": "sha512-/VJHwUxH2BAms7gwIlNhHb0KfCqn/58onOK1H0ZZX9juEha4nrb32yZbv2xLsqLxf+JKsNbJgQ8Ne6VF8ACNGw==",
+      "dependencies": {
+        "quill": "^1.3.7",
+        "quill-delta": "^4.2.2"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.41"
+      }
+    },
+    "node_modules/@vueup/vue-quill/node_modules/fast-diff": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+      "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
+    },
+    "node_modules/@vueup/vue-quill/node_modules/quill-delta": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-4.2.2.tgz",
+      "integrity": "sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==",
+      "dependencies": {
+        "fast-diff": "1.2.0",
+        "lodash.clonedeep": "^4.5.0",
+        "lodash.isequal": "^4.5.0"
+      }
+    },
     "node_modules/@vueuse/core": {
       "version": "9.6.0",
       "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.6.0.tgz",
@@ -3117,7 +3145,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
       "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-      "dev": true,
       "dependencies": {
         "function-bind": "^1.1.1",
         "get-intrinsic": "^1.0.2"
@@ -3899,7 +3926,6 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
       "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
-      "dev": true,
       "dependencies": {
         "has-property-descriptors": "^1.0.0",
         "object-keys": "^1.1.1"
@@ -4866,6 +4892,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/eventemitter3": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
+      "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
+    },
     "node_modules/execa": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz",
@@ -4934,6 +4965,11 @@
         "webdriverio": "^8.0.0-alpha.505"
       }
     },
+    "node_modules/extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
     "node_modules/external-editor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -5008,6 +5044,11 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "dev": true
     },
+    "node_modules/fast-diff": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
+      "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
+    },
     "node_modules/fast-glob": {
       "version": "3.2.12",
       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@@ -5276,14 +5317,12 @@
     "node_modules/function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
     },
     "node_modules/functions-have-names": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
       "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-      "dev": true,
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -5322,7 +5361,6 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
       "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
-      "dev": true,
       "dependencies": {
         "function-bind": "^1.1.1",
         "has": "^1.0.3",
@@ -5626,7 +5664,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "dev": true,
       "dependencies": {
         "function-bind": "^1.1.1"
       },
@@ -5677,7 +5714,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
       "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
-      "dev": true,
       "dependencies": {
         "get-intrinsic": "^1.1.1"
       },
@@ -5689,7 +5725,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
       "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-      "dev": true,
       "engines": {
         "node": ">= 0.4"
       },
@@ -5701,7 +5736,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
       "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
-      "dev": true,
       "dependencies": {
         "has-symbols": "^1.0.2"
       },
@@ -6041,7 +6075,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
       "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-      "dev": true,
       "dependencies": {
         "call-bind": "^1.0.2",
         "has-tostringtag": "^1.0.0"
@@ -6138,7 +6171,6 @@
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
       "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-      "dev": true,
       "dependencies": {
         "has-tostringtag": "^1.0.0"
       },
@@ -6261,7 +6293,6 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
       "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
-      "dev": true,
       "dependencies": {
         "call-bind": "^1.0.2",
         "has-tostringtag": "^1.0.0"
@@ -6831,8 +6862,7 @@
     "node_modules/lodash.clonedeep": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
-      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
-      "dev": true
+      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
     },
     "node_modules/lodash.defaults": {
       "version": "4.2.0",
@@ -6858,6 +6888,11 @@
       "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
       "dev": true
     },
+    "node_modules/lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+    },
     "node_modules/lodash.isobject": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
@@ -7610,7 +7645,6 @@
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
       "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
-      "dev": true,
       "dependencies": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3"
@@ -7626,7 +7660,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
       "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-      "dev": true,
       "engines": {
         "node": ">= 0.4"
       }
@@ -7818,6 +7851,11 @@
         "node": ">=6"
       }
     },
+    "node_modules/parchment": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
+      "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -8298,6 +8336,72 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/quill": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
+      "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
+      "dependencies": {
+        "clone": "^2.1.1",
+        "deep-equal": "^1.0.1",
+        "eventemitter3": "^2.0.3",
+        "extend": "^3.0.2",
+        "parchment": "^1.1.4",
+        "quill-delta": "^3.6.2"
+      }
+    },
+    "node_modules/quill-delta": {
+      "version": "3.6.3",
+      "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
+      "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
+      "dependencies": {
+        "deep-equal": "^1.0.1",
+        "extend": "^3.0.2",
+        "fast-diff": "1.1.2"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/quill-delta/node_modules/deep-equal": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
+      "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+      "dependencies": {
+        "is-arguments": "^1.0.4",
+        "is-date-object": "^1.0.1",
+        "is-regex": "^1.0.4",
+        "object-is": "^1.0.1",
+        "object-keys": "^1.1.1",
+        "regexp.prototype.flags": "^1.2.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/quill/node_modules/clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/quill/node_modules/deep-equal": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
+      "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+      "dependencies": {
+        "is-arguments": "^1.0.4",
+        "is-date-object": "^1.0.1",
+        "is-regex": "^1.0.4",
+        "object-is": "^1.0.1",
+        "object-keys": "^1.1.1",
+        "regexp.prototype.flags": "^1.2.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -8460,7 +8564,6 @@
       "version": "1.4.3",
       "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
       "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
-      "dev": true,
       "dependencies": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3",
@@ -12624,6 +12727,32 @@
       "dev": true,
       "requires": {}
     },
+    "@vueup/vue-quill": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@vueup/vue-quill/-/vue-quill-1.0.1.tgz",
+      "integrity": "sha512-/VJHwUxH2BAms7gwIlNhHb0KfCqn/58onOK1H0ZZX9juEha4nrb32yZbv2xLsqLxf+JKsNbJgQ8Ne6VF8ACNGw==",
+      "requires": {
+        "quill": "^1.3.7",
+        "quill-delta": "^4.2.2"
+      },
+      "dependencies": {
+        "fast-diff": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+          "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
+        },
+        "quill-delta": {
+          "version": "4.2.2",
+          "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-4.2.2.tgz",
+          "integrity": "sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==",
+          "requires": {
+            "fast-diff": "1.2.0",
+            "lodash.clonedeep": "^4.5.0",
+            "lodash.isequal": "^4.5.0"
+          }
+        }
+      }
+    },
     "@vueuse/core": {
       "version": "9.6.0",
       "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.6.0.tgz",
@@ -13650,7 +13779,6 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
       "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1",
         "get-intrinsic": "^1.0.2"
@@ -14215,7 +14343,6 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
       "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
-      "dev": true,
       "requires": {
         "has-property-descriptors": "^1.0.0",
         "object-keys": "^1.1.1"
@@ -14963,6 +15090,11 @@
       "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
       "dev": true
     },
+    "eventemitter3": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
+      "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
+    },
     "execa": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz",
@@ -15013,6 +15145,11 @@
         "webdriverio": "^8.0.0-alpha.505"
       }
     },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
     "external-editor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -15069,6 +15206,11 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "dev": true
     },
+    "fast-diff": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
+      "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
+    },
     "fast-glob": {
       "version": "3.2.12",
       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@@ -15272,14 +15414,12 @@
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
     },
     "functions-have-names": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
-      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-      "dev": true
+      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
     },
     "gaze": {
       "version": "1.1.3",
@@ -15306,7 +15446,6 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
       "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1",
         "has": "^1.0.3",
@@ -15543,7 +15682,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1"
       }
@@ -15581,7 +15719,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
       "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
-      "dev": true,
       "requires": {
         "get-intrinsic": "^1.1.1"
       }
@@ -15589,14 +15726,12 @@
     "has-symbols": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-      "dev": true
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
     },
     "has-tostringtag": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
       "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
-      "dev": true,
       "requires": {
         "has-symbols": "^1.0.2"
       }
@@ -15830,7 +15965,6 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
       "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "has-tostringtag": "^1.0.0"
@@ -15897,7 +16031,6 @@
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
       "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-      "dev": true,
       "requires": {
         "has-tostringtag": "^1.0.0"
       }
@@ -15969,7 +16102,6 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
       "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "has-tostringtag": "^1.0.0"
@@ -16423,8 +16555,7 @@
     "lodash.clonedeep": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
-      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
-      "dev": true
+      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
     },
     "lodash.defaults": {
       "version": "4.2.0",
@@ -16450,6 +16581,11 @@
       "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
       "dev": true
     },
+    "lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+    },
     "lodash.isobject": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
@@ -16999,7 +17135,6 @@
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
       "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3"
@@ -17008,8 +17143,7 @@
     "object-keys": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
-      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-      "dev": true
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
     },
     "object.assign": {
       "version": "4.1.4",
@@ -17137,6 +17271,11 @@
       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
       "dev": true
     },
+    "parchment": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
+      "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
+    },
     "parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -17461,6 +17600,64 @@
       "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
       "dev": true
     },
+    "quill": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
+      "integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
+      "requires": {
+        "clone": "^2.1.1",
+        "deep-equal": "^1.0.1",
+        "eventemitter3": "^2.0.3",
+        "extend": "^3.0.2",
+        "parchment": "^1.1.4",
+        "quill-delta": "^3.6.2"
+      },
+      "dependencies": {
+        "clone": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+          "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
+        },
+        "deep-equal": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
+          "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+          "requires": {
+            "is-arguments": "^1.0.4",
+            "is-date-object": "^1.0.1",
+            "is-regex": "^1.0.4",
+            "object-is": "^1.0.1",
+            "object-keys": "^1.1.1",
+            "regexp.prototype.flags": "^1.2.0"
+          }
+        }
+      }
+    },
+    "quill-delta": {
+      "version": "3.6.3",
+      "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
+      "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
+      "requires": {
+        "deep-equal": "^1.0.1",
+        "extend": "^3.0.2",
+        "fast-diff": "1.1.2"
+      },
+      "dependencies": {
+        "deep-equal": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
+          "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+          "requires": {
+            "is-arguments": "^1.0.4",
+            "is-date-object": "^1.0.1",
+            "is-regex": "^1.0.4",
+            "object-is": "^1.0.1",
+            "object-keys": "^1.1.1",
+            "regexp.prototype.flags": "^1.2.0"
+          }
+        }
+      }
+    },
     "randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -17590,7 +17787,6 @@
       "version": "1.4.3",
       "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
       "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
-      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3",
diff --git a/package.json b/package.json
index 76d89b60..3a4cbac5 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
   "dependencies": {
     "@vue-flow/additional-components": "^1.3.3",
     "@vue-flow/core": "^1.6.0",
+    "@vueup/vue-quill": "^1.0.1",
     "pinia": "^2.0.28",
     "sass": "^1.56.2",
     "vue": "^3.2.45",
diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue
index 7cfcc16e..7b90cfcd 100644
--- a/src/features/forms/components/inputs/GenericInput.vue
+++ b/src/features/forms/components/inputs/GenericInput.vue
@@ -3,6 +3,7 @@ import TextInput from './TextInput.vue';
 import TextAreaInput from './TextAreaInput.vue';
 import FileInput from './FileInput.vue';
 import ScoreInput from './ScoreInput.vue';
+import QuillEditor from './QuillEditor.vue';
 
 defineProps<{
     type: string;
@@ -36,6 +37,13 @@ const emit = defineEmits<{
         :input-value="inputValue"
         @input="emit('input', $event)"
     />
+    <QuillEditor
+        v-if="type === 'ql-editor'"
+        :label="label"
+        :placeholder="placeholder"
+        :input-value="inputValue"
+        @input="emit('input', $event)"    
+    />  
     <TextAreaInput 
         v-if="type === 'textarea'"
         :label="label"
diff --git a/src/features/forms/components/inputs/QuillEditor.vue b/src/features/forms/components/inputs/QuillEditor.vue
new file mode 100644
index 00000000..8bf7bd5b
--- /dev/null
+++ b/src/features/forms/components/inputs/QuillEditor.vue
@@ -0,0 +1,97 @@
+<script setup lang="ts">
+import { QuillEditor } from '@vueup/vue-quill';
+import '@vueup/vue-quill/dist/vue-quill.snow.css';
+import { onMounted } from 'vue';
+
+const props = defineProps<{
+    label: string;
+    placeholder?: string;
+    inputValue: string;
+}>();
+
+const emit = defineEmits<{
+    (e: 'input', value: string): void;
+}>();
+
+const toolbar = [['bold', 'italic', 'underline'], [{ 'header': 1}, { 'header': 2 }], [{ 'list': 'ordered' }, { 'list': 'bullet' }]];
+
+function textChange() {
+    emit('input', document.querySelector('.ql-editor').innerHTML);
+}
+
+onMounted(() => {
+    document.querySelector('.ql-editor').innerHTML = props.inputValue;
+});
+
+</script>
+
+<template>
+    <QuillEditor
+        :toolbar="toolbar"
+        theme="snow"
+        :placeholder="placeholder"
+        @text-change="textChange"
+    />
+</template>
+
+<style lang="scss">
+
+.ql {
+    &-toolbar {
+        border: none !important;
+        &:hover {
+            border-radius: 8px;
+        }
+    }
+    &-container {
+        border: 1px solid var(--border) !important;
+        border-radius: 4px;
+        background-color: var(--item-background);
+        margin-bottom: 1.5rem;
+        &:focus-within {
+            border: 1px solid var(--editor-blue) !important;
+            box-shadow: 0 1px 8px 0 var(--editor-blue-shadow);
+        }
+    }
+    &-editor {
+        min-height: 10rem;
+        padding: .5rem;
+        font-size: 1rem;
+        color: var(--text);
+        width: 24rem;
+    }
+
+    &-active {
+        background-color: transparent !important;
+        &:hover {
+            background-color: #F3F4F6 !important;
+        }
+    }
+
+    &-formats {
+        button:hover {
+            border-radius: 4px;
+            background-color: #F3F4F6 !important;
+        }
+    }
+}
+
+.ql-active .ql-stroke {
+    stroke: var(--editor-blue) !important;
+}
+
+.ql-active .ql-fill {
+    fill: var(--editor-blue) !important;
+}
+
+.ql-active:hover {
+    background-color: var(--) !important;
+}
+
+.ql-blank::before {
+    color: var(--editor-grayblue) !important;
+    font-style: normal !important;
+    left: 0.5rem !important;
+}
+
+</style>
\ No newline at end of file
diff --git a/src/global.scss b/src/global.scss
index 1405c551..38e3450f 100644
--- a/src/global.scss
+++ b/src/global.scss
@@ -270,10 +270,7 @@ hr {
 
   &-textarea {
     resize: none;
-    min-height: 10rem;
-    &.input-card {
-      min-height: 5rem;
-    }
+    min-height: 5rem;
   }
   &:focus {
     outline: 1px solid var(--editor-blue);
diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts
index 9cda77d6..d06b3e94 100644
--- a/src/shared/data/form.data.ts
+++ b/src/shared/data/form.data.ts
@@ -14,7 +14,7 @@ const textForm: Form = {
                     placeholder: 'Saisissez...'
                 },
                 {
-                    type: 'textarea',
+                    type: 'ql-editor',
                     label: 'Résumé',
                     value: '',
                     placeholder: 'Saisissez un résumé...'
@@ -63,12 +63,6 @@ const videoForm: Form = {
                     value: '',
                     accept: 'video/*'
                 },
-                {
-                    type: 'textarea',
-                    label: 'Résumé',
-                    value: '',
-                    placeholder: 'Saisissez un résumé...'
-                },
                 {
                     type: 'file',
                     label: 'Transcription',
@@ -179,7 +173,7 @@ const epocForm: Form = {
                     accept: 'video/*'
                 },
                 {
-                    type: 'textarea',
+                    type: 'ql-editor',
                     label: 'Présentation',
                     value: '',
                     placeholder: 'Saisissez une présentation de l\'ePoc...'
@@ -355,7 +349,7 @@ const qcmForm: Form = {
             index: 4,
             inputs: [
                 {
-                    type: 'textarea',
+                    type: 'ql-editor',
                     label: '',
                     value: '',
                     placeholder: 'Saisissez une explication'
@@ -424,7 +418,7 @@ const dragDropForm: Form = {
             index: 5,
             inputs: [
                 {
-                    type: 'textarea',
+                    type: 'ql-editor',
                     label: '',
                     value: '',
                     placeholder: 'Saisissez une explication'
@@ -498,7 +492,7 @@ const reorderForm: Form = {
             index: 4,
             inputs: [
                 {
-                    type: 'textarea',
+                    type: 'ql-editor',
                     label: '',
                     value: '',
                     placeholder: 'Saisissez une explication...'
@@ -590,7 +584,7 @@ const swipeForm: Form = {
             index: 5,
             inputs: [
                 {
-                    type: 'textarea',
+                    type: 'ql-editor',
                     label: '',
                     value: '',
                     placeholder: 'Saisissez une explication...'
@@ -671,7 +665,7 @@ const listForm: Form = {
             index: 5,
             inputs: [
                 {
-                    type: 'textarea',
+                    type: 'ql-editor',
                     label: '',
                     value: '',
                     placeholder: 'Saisissez une explication...'
-- 
GitLab