Added alpha version of the editor app on the site

beta
anulax1225 ago%!(EXTRA string=3 days)
parent a8fb1ee866
commit e43d291bdf
  1. 1048
      package-lock.json
  2. 26
      package.json
  3. 299
      resources/css/tiptap.css
  4. 339
      resources/js/Components/Editor/BubbleMenu.vue
  5. 203
      resources/js/Components/Editor/Editor.vue
  6. 149
      resources/js/Components/Editor/FloatingMenu.vue
  7. 53
      resources/js/Components/Editor/Tabs.vue
  8. 93
      resources/js/Components/Editor/Tabs/BlockTab.vue
  9. 68
      resources/js/Components/Editor/Tabs/HistoryTab.vue
  10. 62
      resources/js/Components/Editor/Tabs/ListTab.vue
  11. 90
      resources/js/Components/Editor/Tabs/MediaTab.vue
  12. 241
      resources/js/Components/Editor/Tabs/TableTab.vue
  13. 187
      resources/js/Components/Editor/Tabs/TextTab.vue
  14. 11
      resources/js/Layouts/Layout.vue
  15. 8
      resources/js/Pages/Info.vue
  16. 2
      resources/views/app.blade.php

1048
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -19,6 +19,32 @@
"vue": "^3.4.0" "vue": "^3.4.0"
}, },
"dependencies": { "dependencies": {
"@tiptap/extension-bubble-menu": "^2.11.7",
"@tiptap/extension-character-count": "^2.11.7",
"@tiptap/extension-color": "^2.11.7",
"@tiptap/extension-floating-menu": "^2.11.7",
"@tiptap/extension-gapcursor": "^2.11.7",
"@tiptap/extension-highlight": "^2.11.7",
"@tiptap/extension-image": "^2.11.7",
"@tiptap/extension-link": "^2.11.7",
"@tiptap/extension-list-item": "^2.11.7",
"@tiptap/extension-mention": "^2.11.7",
"@tiptap/extension-placeholder": "^2.11.7",
"@tiptap/extension-subscript": "^2.11.7",
"@tiptap/extension-superscript": "^2.11.7",
"@tiptap/extension-table": "^2.11.7",
"@tiptap/extension-table-cell": "^2.11.7",
"@tiptap/extension-table-header": "^2.11.7",
"@tiptap/extension-table-row": "^2.11.7",
"@tiptap/extension-task-item": "^2.11.7",
"@tiptap/extension-task-list": "^2.11.7",
"@tiptap/extension-text-align": "^2.11.7",
"@tiptap/extension-text-style": "^2.11.7",
"@tiptap/extension-typography": "^2.11.7",
"@tiptap/extension-underline": "^2.11.7",
"@tiptap/extension-youtube": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7",
"@tiptap/vue-3": "^2.11.7",
"markdown": "^0.5.0" "markdown": "^0.5.0"
} }
} }

@ -0,0 +1,299 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.html-code {
display: block;
background-color: #f5f5f5;
color: #333;
padding: 15px;
border-radius: 5px;
font-family: 'Courier New', monospace;
white-space: pre;
overflow-x: auto;
}
/* Base editor container */
.tiptap-editor {
padding: 16px;
min-height: 200px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
font-size: 16px;
line-height: 1.5;
color: #333;
}
/* Focus state for the editor */
.tiptap-editor:focus {
outline: none;
}
/* Typography elements */
.tiptap-editor h1 {
font-size: 3rem;
font-weight: 700;
margin-top: 1.5rem;
margin-bottom: 1rem;
line-height: 1.3;
}
.tiptap-editor h2 {
font-size: 2.4rem;
font-weight: 700;
margin-top: 1.25rem;
margin-bottom: 0.75rem;
line-height: 1.35;
}
.tiptap-editor h3 {
font-size: 1.8rem;
font-weight: 600;
margin-top: 1rem;
margin-bottom: 0.5rem;
line-height: 1.4;
}
.tiptap-editor > p {
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
/* Lists */
.tiptap-editor ul,
.tiptap-editor ol {
padding-left: 1.5rem;
margin-top: 0.75rem;
margin-bottom: 0.75rem;
}
.tiptap-editor ul {
list-style: disc;
}
.tiptap-editor ul ul {
list-style: circle;
}
.tiptap-editor li {
margin-bottom: 0.25rem;
}
.tiptap-editor li > p {
margin: 0;
}
.tiptap-editor pre {
@apply bg-green-300/75 border-green-600/90 text-gray-800 p-4 rounded border-l-8 whitespace-pre-wrap;
}
.tiptap-editor code {
padding: 0 0 0 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
/* Code blocks
.tiptap-editor pre {
background-color: rgba(45, 148, 32, 0.788);
border-radius: 4px;
font-size: 0.875rem;
color: rgba(156, 250, 144, 0.788);
overflow-x: auto;
}
.tiptap-editor code {
border-radius: 3px;
padding: 0 0 0 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
font-size: 0.875em;
} */
/* Blockquotes */
.tiptap-editor blockquote {
border-left: 3px solid #ccc;
padding-left: 1rem;
margin-left: 0;
margin-right: 0;
font-style: italic;
color: #555;
}
.tiptap-editor .image-align-left { float: left; margin-right: 1em; }
.tiptap-editor .image-align-center { display: block; margin: 0 auto; }
.tiptap-editor .image-align-right { float: right; margin-left: 1em; }
.tiptap-editor .image-align-justify { width: 100%; }
.tiptap-editor .mention {
background-color: var(--purple-light);
border-radius: 0.4rem;
box-decoration-break: clone;
color: var(--purple);
padding: 0.1rem 0.3rem;
}
/* Links */
.tiptap-editor a {
color: #4dabf7;
text-decoration: underline;
transition: color 0.2s;
}
.tiptap-editor a:hover {
color: #1b91ff;
}
/* Tables */
.tiptap-editor table {
border-collapse: collapse;
margin: 1rem 0;
width: 100%;
overflow: hidden;
font-size: 0.9em;
}
.tiptap-editor table th {
background-color: #f5f7f9;
font-weight: 600;
text-align: left;
}
.tiptap-editor table th,
.tiptap-editor table td {
border: 1px solid #e0e0e0;
padding: 0.5rem 0.75rem;
}
.tiptap-editor table tr:nth-child(even) {
background-color: #f9f9f9;
}
/* Images */
.tiptap-editor img {
max-width: 100%;
height: auto;
margin: 1rem 0;
border-radius: 4px;
}
/* Horizontal rule */
.tiptap-editor hr {
border: none;
border-top: 1px solid #e0e0e0;
margin: 1.5rem 0;
}
/* Text alignment */
.tiptap-editor .text-left {
text-align: left;
}
.tiptap-editor .text-center {
text-align: center;
}
.tiptap-editor .text-right {
text-align: right;
}
.tiptap-editor .text-justify {
text-align: justify;
}
/* Text formatting */
.tiptap-editor strong,
.tiptap-editor b {
font-weight: 700;
}
.tiptap-editor em,
.tiptap-editor i {
font-style: italic;
}
.tiptap-editor u {
text-decoration: underline;
}
.tiptap-editor s,
.tiptap-editor strike,
.tiptap-editor del {
text-decoration: line-through;
}
/* Placeholders for empty editor */
.tiptap-editor p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
/* Task lists */
.tiptap-editor ul[data-type="taskList"] {
list-style: none;
padding: 0;
}
.tiptap-editor ul[data-type="taskList"] li {
display: flex;
align-items: start;
margin-bottom: 0.5rem;
}
.tiptap-editor ul[data-type="taskList"] li p {
margin: 0;
}
.tiptap-editor ul[data-type="taskList"] li > label {
margin-right: 0.5rem;
user-select: none;
}
.tiptap-editor ul[data-type="taskList"] li > div {
flex: 1;
}
/* Code highlighting */
.tiptap-editor .hljs-comment,
.tiptap-editor .hljs-quote {
color: #998;
font-style: italic;
}
.tiptap-editor .hljs-keyword,
.tiptap-editor .hljs-selector-tag,
.tiptap-editor .hljs-subst {
color: #333;
font-weight: bold;
}
.tiptap-editor .hljs-string,
.tiptap-editor .hljs-doctag {
color: #d14;
}
.tiptap-editor .hljs-title,
.tiptap-editor .hljs-section,
.tiptap-editor .hljs-selector-id {
color: #900;
font-weight: bold;
}
.tiptap-editor .hljs-number {
color: #099;
}
/* Placeholder text */
.tiptap-editor .is-empty::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
/* When editor is disabled */
.tiptap-editor.ProseMirror-disabled {
opacity: 0.6;
cursor: not-allowed;
}

@ -0,0 +1,339 @@
<script setup>
import { reactive, ref } from 'vue';
import { BubbleMenu } from '@tiptap/vue-3'
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
const commonColors = [
'#000000', '#343a40', '#495057', '#6c757d', '#adb5bd', '#ced4da', '#dee2e6', '#e9ecef', '#f8f9fa', '#ffffff',
'#c92a2a', '#a61e4d', '#862e9c', '#5f3dc4', '#364fc7', '#1864ab', '#0b7285', '#087f5b', '#2b8a3e', '#5c940d',
'#e67700', '#d9480f'
]
const addTable = () => {
editor
.chain()
.focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run()
}
const isTableActive = () => {
return editor && (
editor.isActive('table') ||
editor.isActive('tableRow') ||
editor.isActive('tableCell') ||
editor.isActive('tableHeader')
)
}
// Table operations
const addColumnBefore = () => {
editor.chain().focus().addColumnBefore().run()
}
const addColumnAfter = () => {
editor.chain().focus().addColumnAfter().run()
}
const deleteColumn = () => {
editor.chain().focus().deleteColumn().run()
}
const addRowBefore = () => {
editor.chain().focus().addRowBefore().run()
}
const addRowAfter = () => {
editor.chain().focus().addRowAfter().run()
}
const deleteRow = () => {
editor.chain().focus().deleteRow().run()
}
const deleteTable = () => {
editor.chain().focus().deleteTable().run()
}
const toggleHeaderColumn = () => {
editor.chain().focus().toggleHeaderColumn().run()
}
const toggleHeaderRow = () => {
editor.chain().focus().toggleHeaderRow().run()
}
const mergeCells = () => {
editor.chain().focus().mergeCells().run()
}
const splitCell = () => {
editor.chain().focus().splitCell().run()
}
</script>
<template>
<!-- Bubble Menu for Inline Formatting -->
<bubble-menu
v-if="editor"
:editor="editor"
:tippy-options="{ duration: 100, placement: 'bottom' }"
class="z-50 bg-white border border-gray-200 rounded-lg shadow-lg p-2 flex space-x-2 flex-wrap"
>
<div class="flex items-center space-x-1 p-1">
<!-- Text Formatting Buttons -->
<button
@click="editor.chain().focus().toggleBold().run()"
class="p-1.5 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('bold') }"
title="Bold"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 12h8a4 4 0 0 0 0-8H6v8z"></path>
<path d="M6 12h9a4 4 0 0 1 0 8H6v-8z"></path>
</svg>
</button>
<button
@click="editor.chain().focus().toggleItalic().run()"
class="p-1.5 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('italic') }"
title="Italic"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="19" y1="4" x2="10" y2="4"></line>
<line x1="14" y1="20" x2="5" y2="20"></line>
<line x1="15" y1="4" x2="9" y2="20"></line>
</svg>
</button>
<button
@click="editor.chain().focus().toggleUnderline().run()"
class="p-1.5 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('underline') }"
title="Underline"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 4v6a6 6 0 0 0 6 6 6 6 0 0 0 6-6V4"></path>
<line x1="4" y1="20" x2="20" y2="20"></line>
</svg>
</button>
<button
@click="editor.chain().focus().toggleStrike().run()"
class="p-1.5 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('strike') }"
title="Strikethrough"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"></line>
<path d="M16 6l-4 4-4-4"></path>
<path d="M8 18l4-4 4 4"></path>
</svg>
</button>
<button
@click="editor.chain().focus().toggleHighlight().run()"
class="p-1.5 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('highlight') }"
title="Highlight"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="4" y="4" width="16" height="16" rx="2"></rect>
<rect x="9" y="9" width="6" height="6" fill="currentColor"></rect>
</svg>
</button>
<div class="h-5 w-px bg-gray-300 mx-1"></div>
<!-- Link Buttons -->
<button
v-if="!editor.isActive('link')"
@click="setLink"
class="p-1.5 rounded hover:bg-gray-100"
title="Add Link"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
</button>
<div v-if="editor.isActive('link')" class="flex space-x-1">
<button
@click="setLink"
class="p-1.5 rounded hover:bg-gray-100 bg-gray-200"
title="Edit Link"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
</button>
<button
@click="removeLink"
class="p-1.5 rounded hover:bg-gray-100"
title="Remove Link"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
<line x1="2" y1="2" x2="22" y2="22"></line>
</svg>
</button>
</div>
<div class="h-5 w-px bg-gray-300 mx-1"></div>
<!-- Color Dropdown -->
<div class="relative group">
<button
class="p-1.5 rounded hover:bg-gray-100 flex items-center"
title="Text Color"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L18 11H6L12 2Z" fill="currentColor"></path>
<line x1="5" y1="19" x2="19" y2="19"></line>
</svg>
</button>
<div class="absolute hidden group-hover:grid grid-cols-6 gap-1 p-2 bg-white rounded-lg shadow-lg border border-gray-200 z-50 w-48 top-full left-0 mt-1">
<button
v-for="color in commonColors"
:key="color"
@click="setColor(color)"
class="w-6 h-6 rounded-full border border-gray-300"
:style="{ backgroundColor: color }"
:title="color"
></button>
</div>
</div>
<div class="h-5 w-px bg-gray-300 mx-1"></div>
<!-- Clear formatting -->
<button
@click="editor.chain().focus().unsetAllMarks().run()"
class="p-1.5 rounded hover:bg-gray-100"
title="Clear Formatting"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 19l-7-7 7-7"></path>
<path d="M19 19V5"></path>
</svg>
</button>
</div>
<!-- Show table menu if table is active -->
<div v-if="isTableActive()" class="flex items-center space-x-1 border-l border-gray-300 pl-2">
<button
@click="addColumnBefore"
class="p-1.5 rounded hover:bg-gray-100"
title="Add Column Before"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="9" y1="3" x2="9" y2="21"></line>
<path d="M6 12h6"></path>
<path d="M9 9v6"></path>
</svg>
</button>
<button
@click="addColumnAfter"
class="p-1.5 rounded hover:bg-gray-100"
title="Add Column After"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="15" y1="3" x2="15" y2="21"></line>
<path d="M12 12h6"></path>
<path d="M15 9v6"></path>
</svg>
</button>
<button
@click="addRowBefore"
class="p-1.5 rounded hover:bg-gray-100"
title="Add Row Before"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="3" y1="9" x2="21" y2="9"></line>
<path d="M12 6v6"></path>
<path d="M9 9h6"></path>
</svg>
</button>
<button
@click="addRowAfter"
class="p-1.5 rounded hover:bg-gray-100"
title="Add Row After"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="3" y1="15" x2="21" y2="15"></line>
<path d="M12 12v6"></path>
<path d="M9 15h6"></path>
</svg>
</button>
<div class="h-5 w-px bg-gray-300 mx-1"></div>
<button
@click="deleteRow"
class="p-1.5 rounded hover:bg-gray-100"
title="Delete Row"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="3" y1="9" x2="21" y2="9"></line>
<line x1="9" y1="15" x2="15" y2="15"></line>
</svg>
</button>
<button
@click="deleteColumn"
class="p-1.5 rounded hover:bg-gray-100"
title="Delete Column"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="9" y1="3" x2="9" y2="21"></line>
<line x1="15" y1="9" x2="15" y2="15"></line>
</svg>
</button>
<button
@click="mergeCells"
class="p-1.5 rounded hover:bg-gray-100"
title="Merge Cells"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M9 3v18"></path>
<path d="M15 3v18"></path>
<path d="M3 9h18"></path>
<path d="M3 15h18"></path>
<rect x="9" y="9" width="6" height="6" fill="currentColor" opacity="0.2"></rect>
</svg>
</button>
<button
@click="splitCell"
class="p-1.5 rounded hover:bg-gray-100"
title="Split Cell"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<path d="M12 3v18"></path>
<path d="M3 12h18"></path>
</svg>
</button>
</div>
</bubble-menu>
</template>

@ -0,0 +1,203 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, defineProps, defineEmits } from 'vue'
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import TextAlign from '@tiptap/extension-text-align'
import Underline from '@tiptap/extension-underline'
import Highlight from '@tiptap/extension-highlight'
import TextStyle from '@tiptap/extension-text-style'
import Mention from '@tiptap/extension-mention'
import Color from '@tiptap/extension-color'
import Link from '@tiptap/extension-link'
import Placeholder from '@tiptap/extension-placeholder'
import Typography from '@tiptap/extension-typography'
import CharacterCount from '@tiptap/extension-character-count'
import Image from '@tiptap/extension-image'
import TaskList from '@tiptap/extension-task-list'
import TaskItem from '@tiptap/extension-task-item'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import Tabs from './Tabs.vue'
import TextTab from './Tabs/TextTab.vue'
import BlockTab from './Tabs/BlockTab.vue'
import ListTab from './Tabs/ListTab.vue'
import MediaTab from './Tabs/MediaTab.vue'
import TableTab from './Tabs/TableTab.vue'
import HistoryTab from './Tabs/HistoryTab.vue'
import BubbleMenu from './BubbleMenu.vue'
import FloatingMenu from './FloatingMenu.vue'
const ImageAlign = Image.extend({
addAttributes() {
return {
...this.parent?.(),
alignment: {
default: 'left',
renderHTML: attributes => {
return {
class: `image-align-${attributes.alignment}`,
}
},
},
}
},
})
// Props and emits
const props = defineProps({
initialContent: {
type: String,
default: `
<h2>
Présentation du groupe
</h2>
<p>
Notre groupe compte aujourdhui près de 40 membres. Ils et elles sont réparti·es répartis dans quatre différentes branches :
</p>
<ul>
<li>
<b>Branche Louveteaux (7 11 ans) :</b>
<p>
Dès 7 ans, un enfant peut participer à nos activités en tant que louveteaux ou louvette.Réparti·es en sizaines formant ainsi une meute, les louveteaux et les louvettes découvrent le scoutisme à travers lhistoire du livre de la jungle et réalisent des jeux, bricolages ou autres activités adaptées à leur âge.
</p>
</li>
<li>
<b>Branche Eclais (11 15 ans) :</b>
<p>
A partir de 11 ans, les jeunes de notre groupe font partie de la branche éclais. Ils et elles sont séparé·es en patrouilles qui forment une troupe. Durant les activités préparées et encadrées par les responsables, ils et elles apprennent à devenir autonome en approfondissant certains domaines comme les techniques de corde, topographie, cuisine, etc.
Les plus grand·es éclais ont également la possibilité dorganiser eux·elles-mêmes des activités ou même des petits camps pour leurs patrouilles. Les responsables sont pour leur expliquer comment planifier ces activités et encadrer des enfants plus jeunes.
</p>
</li>
</ul>
<p>
Envie de faire découvrir le scoutisme à votre enfant ? Pas de problème! Il est possible de rejoindre notre groupe à nimporte quel moment de lannée.
Il suffit de venir à nos locaux durant lune de nos activités. Nous recommandons aux enfants de venir essayer nos activités pendant plusieurs séances avant de décider sil souhaite sinscrire au groupe ou non.
Nhésitez pas à nous contacter si vous souhaitez avoir plus dinformations concernant notre groupe ou nos séances.
</p>
`
}
})
const emit = defineEmits(['update'])
// Editor instance
const editor = ref(null)
const activeTab = ref('text')
// Initialize editor
onMounted(() => {
editor.value = new Editor({
content: props.initialContent,
editorProps: {
attributes: {
class: 'tiptap-editor',
},
},
extensions: [
StarterKit.configure({
heading: { levels: [1, 2, 3, 4] }
}),
Underline,
Mention.configure({
HTMLAttributes: {
class: 'mention',
},
suggestion: {
items: ({ query }) => {
return [
"Tax", "Padda", "Example"
]
}
},
}),
Highlight.configure({
multicolor: true
}),
TextStyle,
TextAlign.configure({
types: ['heading', 'paragraph'],
alignments: ['left', 'center', 'right', 'justify'],
defaultAlignment: 'left',
}),
Color,
CharacterCount,
//Gapcursor,
Link.configure({
openOnClick: false,
autolink: true
}),
Placeholder.configure({
placeholder: 'Texte...'
}),
Typography,
ImageAlign.configure({
inline: true,
allowBase64: true
}),
TaskList,
TaskItem.configure({
nested: true
}),
Table.configure({
resizable: true
}),
TableRow,
TableCell,
TableHeader
],
onUpdate: ({ editor }) => {
emit('update', {
html: editor.getHTML(),
json: editor.getJSON()
})
}
})
emit('update', {
html: editor.value.getHTML(),
json: editor.value.getJSON()
})
})
// Clean up
onBeforeUnmount(() => {
if (editor.value) {
editor.value.destroy()
}
})
</script>
<template>
<div v-if="editor" class="notion-editor-container relative">
<!-- Toolbar based on active tab -->
<div class="sticky top-0 right-0 left-0 z-10 bg-white border-b border-gray-200 py-2 flex space-x-2 flex-wrap">
<Tabs @active="(tab) => activeTab = tab"/>
<TextTab v-show="activeTab == 'text'" :editor="editor" />
<BlockTab v-show="activeTab == 'blocks'" :editor="editor"/>
<ListTab v-show="activeTab == 'lists'" :editor="editor"/>
<MediaTab v-show="activeTab == 'media'" :editor="editor"/>
<TableTab v-show="activeTab == 'tables'" :editor="editor"/>
<HistoryTab v-show="activeTab == 'history'" :editor="editor"/>
</div>
<BubbleMenu :editor="editor" />
<!-- Floating Menu for Block Types -->
<FloatingMenu :editor="editor" />
<!-- Editor Content -->
<editor-content
:editor="editor"
:class="'tiptap-editor min-h-[500px] h-full bg-white shadow-md'"
/>
<div class="sticky bottom-0 right-0 left-0 z-10
bg-white border-t border-gray-200 p-2 flex space-x-2
text-sm text-gray-700">
<p>{{ editor.storage.characterCount.words() }} words</p>
<p>|</p>
<p>{{ editor.storage.characterCount.characters() }} characters</p>
</div>
</div>
</template>
<style>
</style>

@ -0,0 +1,149 @@
<script setup>
import { reactive, ref } from 'vue';
import { FloatingMenu } from '@tiptap/vue-3'
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
const addImage = () => {
const url = window.prompt('Enter image URL')
if (url) {
editor
.chain()
.focus()
.setImage({ src: url })
.run()
}
}
const addTable = () => {
editor
.chain()
.focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run()
}
</script>
<template>
<floating-menu
v-if="editor"
:editor="editor"
:tippy-options="{ duration: 100, placement: 'bottom-start' }"
class="z-50 bg-white border border-gray-200 rounded-lg shadow-lg p-2 flex flex-col space-y-1 min-w-[200px]"
>
<button
@click="editor.chain().focus().setParagraph().run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('paragraph') }"
>
<span class="mr-2"></span>
<span>Paragraph</span>
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('heading', { level: 1 }) }"
>
<span class="mr-2 font-bold text-lg">H1</span>
<span>Heading 1</span>
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('heading', { level: 2 }) }"
>
<span class="mr-2 font-bold">H2</span>
<span>Heading 2</span>
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('heading', { level: 3 }) }"
>
<span class="mr-2 font-bold text-sm">H3</span>
<span>Heading 3</span>
</button>
<div class="h-px bg-gray-200 my-1"></div>
<button
@click="editor.chain().focus().toggleBulletList().run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('bulletList') }"
>
<span class="mr-2"></span>
<span>Bullet List</span>
</button>
<button
@click="editor.chain().focus().toggleOrderedList().run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('orderedList') }"
>
<span class="mr-2">1.</span>
<span>Numbered List</span>
</button>
<button
@click="editor.chain().focus().toggleTaskList().run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('taskList') }"
>
<span class="mr-2"></span>
<span>Task List</span>
</button>
<div class="h-px bg-gray-200 my-1"></div>
<button
@click="editor.chain().focus().toggleBlockquote().run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('blockquote') }"
>
<span class="mr-2">"</span>
<span>Quote</span>
</button>
<button
@click="editor.chain().focus().toggleCodeBlock().run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
:class="{ 'bg-gray-100': editor.isActive('codeBlock') }"
>
<span class="mr-2">{"}</span>
<span>Code Block</span>
</button>
<div class="h-px bg-gray-200 my-1"></div>
<button
@click="editor.chain().focus().setHorizontalRule().run()"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
>
<span class="mr-2"></span>
<span>Horizontal Rule</span>
</button>
<button
@click="addTable"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
>
<span class="mr-2"></span>
<span>Table</span>
</button>
<button
@click="addImage"
class="flex items-center px-3 py-1.5 rounded hover:bg-gray-100 text-left text-sm"
>
<span class="mr-2">🖼</span>
<span>Image</span>
</button>
</floating-menu>
</template>

@ -0,0 +1,53 @@
<script setup>
import { ref } from 'vue';
const emits = defineEmits(["active"]);
const active = ref("text");
// Set active tab
const setActiveTab = (tab) => {
active.value = tab;
emits('active', tab)
}
</script>
<template>
<!-- Tab Menu -->
<div class="w-full flex border-b border-gray-200 mb-1">
<button
@click="setActiveTab('text')"
class="px-4 py-2 font-medium text-sm transition-all"
:class="active === 'text' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500 hover:text-gray-700'">
Text Formatting
</button>
<button
@click="setActiveTab('blocks')"
class="px-4 py-2 font-medium text-sm transition-all"
:class="active === 'blocks' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500 hover:text-gray-700'">
Block Elements
</button>
<button
@click="setActiveTab('lists')"
class="px-4 py-2 font-medium text-sm transition-all"
:class="active === 'lists' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500 hover:text-gray-700'">
Lists
</button>
<button
@click="setActiveTab('tables')"
class="px-4 py-2 font-medium text-sm transition-all"
:class="active === 'tables' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500 hover:text-gray-700'">
Tables
</button>
<button
@click="setActiveTab('media')"
class="px-4 py-2 font-medium text-sm transition-all"
:class="active === 'media' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500 hover:text-gray-700'">
Media
</button>
<button
@click="setActiveTab('history')"
class="px-4 py-2 font-medium text-sm transition-all"
:class="active === 'history' ? 'border-b-2 border-blue-500 text-blue-600' : 'text-gray-500 hover:text-gray-700'">
History
</button>
</div>
</template>

@ -0,0 +1,93 @@
<script setup>
import { reactive, ref } from 'vue';
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
</script>
<template>
<!-- Block Elements Tab -->
<div class="flex items-center space-x-2 flex-wrap">
<button
@click="editor.chain().focus().setParagraph().run()"
class="px-2 py-1 text-sm rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('paragraph') }"
title="Paragraph"
>
<span class="font-medium"></span>
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
class="px-2 py-1 text-sm rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('heading', { level: 1 }) }"
title="Heading 1"
>
<span class="font-medium">H1</span>
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
class="px-2 py-1 text-sm rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('heading', { level: 2 }) }"
title="Heading 2"
>
<span class="font-medium">H2</span>
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
class="px-2 py-1 text-sm rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('heading', { level: 3 }) }"
title="Heading 3"
>
<span class="font-medium">H3</span>
</button>
<button
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
class="px-2 py-1 text-sm rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('heading', { level: 4 }) }"
title="Heading 3"
>
<span class="font-medium">H4</span>
</button>
<button
@click="editor.chain().focus().toggleBlockquote().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('blockquote') }"
title="Blockquote"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
</button>
<button
@click="editor.chain().focus().setHorizontalRule().run()"
class="p-1 rounded hover:bg-gray-100"
title="Horizontal Rule"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
<button
@click="editor.chain().focus().toggleCodeBlock().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('codeBlock') }"
title="Code Block"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
</button>
</div>
</template>

@ -0,0 +1,68 @@
<script setup>
import { reactive, ref } from 'vue';
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
const undo = () => {
editor.chain().focus().undo().run()
}
const redo = () => {
editor.chain().focus().redo().run()
}
// Public method to clear content
const clearContent = () => {
editor.commands.clearContent()
}
</script>
<template>
<!-- History Tab -->
<div class="flex items-center space-x-2 flex-wrap">
<button
@click="undo"
class="p-1 rounded hover:bg-gray-100 flex items-center"
title="Undo"
:disabled="!editor.can().undo()"
:class="{ 'opacity-50 cursor-not-allowed': !editor.can().undo() }"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M3 10h10a8 8 0 0 1 8 8v2M3 10l6 6M3 10l6-6"></path>
</svg>
<span class="ml-1 text-sm">Undo</span>
</button>
<button
@click="redo"
class="p-1 rounded hover:bg-gray-100 flex items-center"
title="Redo"
:disabled="!editor.can().redo()"
:class="{ 'opacity-50 cursor-not-allowed': !editor.can().redo() }"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 10H11a8 8 0 0 0-8 8v2M21 10l-6 6M21 10l-6-6"></path>
</svg>
<span class="ml-1 text-sm">Redo</span>
</button>
<div class="border-r border-gray-300 h-6 mx-1"></div>
<button
@click="clearContent"
class="p-1 rounded hover:bg-gray-100 flex items-center text-red-500"
title="Clear Content"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M18 6L6 18"></path>
<path d="M6 6l12 12"></path>
</svg>
<span class="ml-1 text-sm">Clear All</span>
</button>
</div>
</template>

@ -0,0 +1,62 @@
<script setup>
import { reactive, ref } from 'vue';
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
</script>
<template>
<!-- Lists Tab -->
<div class="flex items-center space-x-2 flex-wrap">
<button
@click="editor.chain().focus().toggleBulletList().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('bulletList') }"
title="Bullet List"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="8" y1="6" x2="21" y2="6"></line>
<line x1="8" y1="12" x2="21" y2="12"></line>
<line x1="8" y1="18" x2="21" y2="18"></line>
<line x1="3" y1="6" x2="3.01" y2="6"></line>
<line x1="3" y1="12" x2="3.01" y2="12"></line>
<line x1="3" y1="18" x2="3.01" y2="18"></line>
</svg>
</button>
<button
@click="editor.chain().focus().toggleOrderedList().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('orderedList') }"
title="Numbered List"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="10" y1="6" x2="21" y2="6"></line>
<line x1="10" y1="12" x2="21" y2="12"></line>
<line x1="10" y1="18" x2="21" y2="18"></line>
<path d="M4 6h1v4"></path>
<path d="M4 10h2"></path>
<path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"></path>
</svg>
</button>
<button
@click="editor.chain().focus().toggleTaskList().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('taskList') }"
title="Task List"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="5" width="6" height="6" rx="1"></rect>
<path d="M3 17h6"></path>
<path d="M13 5h8"></path>
<path d="M13 11h8"></path>
<path d="M13 17h8"></path>
</svg>
</button>
</div>
</template>

@ -0,0 +1,90 @@
<script setup>
import { reactive, ref } from 'vue';
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
const setLink = () => {
const previousUrl = editor?.getAttributes('link').href
const url = window.prompt('Enter the URL', previousUrl)
if (url === null) {
return
}
if (url === '') {
editor.chain().focus().extendMarkRange('link').unsetLink().run()
return
}
editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
}
const removeLink = () => {
editor.chain().focus().extendMarkRange('link').unsetLink().run()
}
const addImage = () => {
const url = window.prompt('Enter image URL')
if (url) {
editor
.chain()
.focus()
.setImage({ src: url })
.run()
}
}
</script>
<template>
<!-- Media Tab -->
<div class="flex items-center space-x-2 flex-wrap">
<button
@click="setLink"
class="p-1 rounded hover:bg-gray-100 flex items-center"
:class="{ 'bg-gray-200': editor.isActive('link') }"
title="Add Link"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>
<span class="ml-1 text-sm">Link</span>
</button>
<button
@click="addImage"
class="p-1 rounded hover:bg-gray-100 flex items-center"
title="Add Image from URL"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
<span class="ml-1 text-sm">URL Image</span>
</button>
<label class="p-1 rounded hover:bg-gray-100 flex items-center cursor-pointer" title="Upload Image">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"></path>
<line x1="16" y1="5" x2="22" y2="5"></line>
<line x1="19" y1="2" x2="19" y2="8"></line>
<rect x="3" y="10" width="18" height="12" rx="2"></rect>
<circle cx="8.5" cy="15.5" r="1.5"></circle>
<polyline points="21 18 16 13 8 21"></polyline>
</svg>
<span class="ml-1 text-sm">Upload</span>
<input
type="file"
@change="uploadImage"
accept="image/*"
class="hidden"
/>
</label>
</div>
</template>

@ -0,0 +1,241 @@
<script setup>
import { reactive, ref } from 'vue';
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
const addTable = () => {
editor
.chain()
.focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run()
}
const isTableActive = () => {
return editor && (
editor.isActive('table') ||
editor.isActive('tableRow') ||
editor.isActive('tableCell') ||
editor.isActive('tableHeader')
)
}
// Table operations
const addColumnBefore = () => {
editor.chain().focus().addColumnBefore().run()
}
const addColumnAfter = () => {
editor.chain().focus().addColumnAfter().run()
}
const deleteColumn = () => {
editor.chain().focus().deleteColumn().run()
}
const addRowBefore = () => {
editor.chain().focus().addRowBefore().run()
}
const addRowAfter = () => {
editor.chain().focus().addRowAfter().run()
}
const deleteRow = () => {
editor.chain().focus().deleteRow().run()
}
const deleteTable = () => {
editor.chain().focus().deleteTable().run()
}
const toggleHeaderColumn = () => {
editor.chain().focus().toggleHeaderColumn().run()
}
const toggleHeaderRow = () => {
editor.chain().focus().toggleHeaderRow().run()
}
const mergeCells = () => {
editor.chain().focus().mergeCells().run()
}
const splitCell = () => {
editor.chain().focus().splitCell().run()
}
</script>
<template>
<!-- Tables Tab -->
<div class="flex items-center space-x-2 flex-wrap">
<button
@click="addTable"
class="p-1 rounded hover:bg-gray-100 flex items-center"
title="Insert Table"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<line x1="3" y1="9" x2="21" y2="9"></line>
<line x1="3" y1="15" x2="21" y2="15"></line>
<line x1="9" y1="3" x2="9" y2="21"></line>
<line x1="15" y1="3" x2="15" y2="21"></line>
</svg>
<span class="ml-1 text-sm">New Table</span>
</button>
<div class="border-r border-gray-300 h-6 mx-1"></div>
<button
@click="addColumnBefore"
class="p-1 rounded hover:bg-gray-100"
title="Add Column Before"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="9" y1="3" x2="9" y2="21"></line>
<line x1="6" y1="12" x2="12" y2="12"></line>
<line x1="9" y1="9" x2="9" y2="15"></line>
</svg>
</button>
<button
@click="addColumnAfter"
class="p-1 rounded hover:bg-gray-100"
title="Add Column After"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="15" y1="3" x2="15" y2="21"></line>
<line x1="12" y1="12" x2="18" y2="12"></line>
<line x1="15" y1="9" x2="15" y2="15"></line>
</svg>
</button>
<button
@click="deleteColumn"
class="p-1 rounded hover:bg-gray-100"
title="Delete Column"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="15" y1="3" x2="15" y2="21"></line>
<line x1="9" y1="3" x2="9" y2="21"></line>
<line x1="12" y1="9" x2="12" y2="15"></line>
</svg>
</button>
<div class="border-r border-gray-300 h-6 mx-1"></div>
<button
@click="addRowBefore"
class="p-1 rounded hover:bg-gray-100"
title="Add Row Before"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="3" y1="9" x2="21" y2="9"></line>
<line x1="12" y1="6" x2="12" y2="12"></line>
<line x1="9" y1="9" x2="15" y2="9"></line>
</svg>
</button>
<button
@click="addRowAfter"
class="p-1 rounded hover:bg-gray-100"
title="Add Row After"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="3" y1="15" x2="21" y2="15"></line>
<line x1="12" y1="12" x2="12" y2="18"></line>
<line x1="9" y1="15" x2="15" y2="15"></line>
</svg>
</button>
<button
@click="deleteRow"
class="p-1 rounded hover:bg-gray-100"
title="Delete Row"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="3" y1="15" x2="21" y2="15"></line>
<line x1="3" y1="9" x2="21" y2="9"></line>
<line x1="12" y1="12" x2="12" y2="12"></line>
</svg>
</button>
<div class="border-r border-gray-300 h-6 mx-1"></div>
<button
@click="toggleHeaderRow"
class="p-1 rounded hover:bg-gray-100"
title="Toggle Header Row"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="3" y1="9" x2="21" y2="9"></line>
<path d="M6 6h12"></path>
</svg>
</button>
<button
@click="toggleHeaderColumn"
class="p-1 rounded hover:bg-gray-100"
title="Toggle Header Column"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="9" y1="3" x2="9" y2="21"></line>
<path d="M6 6v12"></path>
</svg>
</button>
<div class="border-r border-gray-300 h-6 mx-1"></div>
<button
@click="mergeCells"
class="p-1 rounded hover:bg-gray-100"
title="Merge Cells"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="9" y1="3" x2="9" y2="21" stroke-dasharray="4"></line>
</svg>
</button>
<button
@click="splitCell"
class="p-1 rounded hover:bg-gray-100"
title="Split Cell"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
<line x1="9" y1="3" x2="9" y2="21"></line>
<line x1="15" y1="3" x2="15" y2="21"></line>
</svg>
</button>
<div class="border-r border-gray-300 h-6 mx-1"></div>
<button
@click="deleteTable"
class="p-1 rounded hover:bg-gray-100"
title="Delete Table"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 6h18"></path>
<path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6"></path>
<path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</div>
</template>

@ -0,0 +1,187 @@
<script setup>
import { reactive, ref } from 'vue';
const props = defineProps({
editor: {
type: Object,
required: true,
}
});
const editor = reactive(props.editor);
const textColor = ref(null);
const highColor = ref(null);
const color = ref("#000000");
const highlight = ref("#ffff00");
const setColor = (c) => {
editor.chain().focus().setColor(c).run()
editor.getAttributes("textStyle").color = c;
}
const setAlign = (alignment) => {
if (editor.isActive('image')) {
editor.chain()
.updateAttributes('image', { alignment })
.run()
}
editor.chain()
.setTextAlign(alignment)
.run()
}
console.log(editor, editor.state)
</script>
<template>
<div class="flex items-center space-x-2 flex-wrap">
<button
@click="editor.chain().focus().toggleBold().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('bold') }"
title="Bold"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M6 12h8a4 4 0 0 0 0-8H6v8z"></path>
<path d="M6 12h9a4 4 0 0 1 0 8H6v-8z"></path>
</svg>
</button>
<button
@click="editor.chain().focus().toggleItalic().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('italic') }"
title="Italic"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M19 4h-9v2h3.5l-3.5 8h-3v2h9v-2h-3.5l3.5-8h3z"></path>
</svg>
</button>
<button
@click="editor.chain().focus().toggleUnderline().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('underline') }"
title="Underline"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M6 4v6a6 6 0 0 0 6 6 6 6 0 0 0 6-6V4"></path>
<line x1="4" y1="20" x2="20" y2="20"></line>
</svg>
</button>
<button
@click="editor.chain().focus().toggleStrike().run()"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive('strike') }"
title="Strikethrough"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M17 9V5H7v4"></path>
<path d="M16 15v4H8v-4"></path>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
<div class="flex items-center justify-center">
<button
@click="!editor.state.selection.empty ? editor.chain().focus().toggleHighlight({color: highlight}).run() : highColor.click()"
class="px-1 rounded"
:style="'background-color:'+highlight"
title="Highlight"
>A</button>
<input
type="color"
@input="(e) => highlight = e.target.value"
ref="highColor"
class="w-8 h-8 p-0 border-none cursor-pointer hidden"
title="Text Color"
/>
</div>
<div class="flex items-center space-x-1">
<label
@click="!editor.state.selection.empty || editor.state.isFocused ? setColor(color) : textColor.click()"
class="text-lg text-medium" :style="'color: ' + color">A</label>
<input
type="color"
@input="(e) => color = e.target.value"
ref="textColor"
class="w-8 h-8 p-0 border-none cursor-pointer hidden"
title="Text Color"
/>
</div>
<button
@click="editor.chain().focus().clearNodes().run()"
class="p-1 rounded hover:bg-gray-100"
title="Clear Formatting"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M12 20h9"></path>
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
<line x1="2" y1="2" x2="22" y2="22"></line>
</svg>
</button>
<button
@click="setAlign('left')"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive({ textAlign: 'left' }) }"
title="Align Left"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="17" y1="10" x2="3" y2="10"></line>
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="21" y1="14" x2="3" y2="14"></line>
<line x1="17" y1="18" x2="3" y2="18"></line>
</svg>
</button>
<button
@click="setAlign('center')"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive({ textAlign: 'center' }) }"
title="Align Center"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="18" y1="10" x2="6" y2="10"></line>
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="21" y1="14" x2="3" y2="14"></line>
<line x1="18" y1="18" x2="6" y2="18"></line>
</svg>
</button>
<button
@click="setAlign('right')"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive({ textAlign: 'right' }) }"
title="Align Right"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="21" y1="10" x2="7" y2="10"></line>
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="21" y1="14" x2="3" y2="14"></line>
<line x1="21" y1="18" x2="7" y2="18"></line>
</svg>
</button>
<button
@click="setAlign('justify')"
class="p-1 rounded hover:bg-gray-100"
:class="{ 'bg-gray-200': editor.isActive({ textAlign: 'justify' }) }"
title="Justify"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="21" y1="10" x2="3" y2="10"></line>
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="21" y1="14" x2="3" y2="14"></line>
<line x1="21" y1="18" x2="3" y2="18"></line>
</svg>
</button>
<div class="border-l border-gray-300 h-8 mx-2"></div>
<p class="text-sm text-gray-600">
Tip: Select text blocks or images before applying alignment.
</p>
</div>
</template>

@ -5,6 +5,15 @@ import Dropdown from '@/Components/Dropdown.vue';
import DropdownLink from '@/Components/DropdownLink.vue'; import DropdownLink from '@/Components/DropdownLink.vue';
import { useSlots } from 'vue'; import { useSlots } from 'vue';
import ResponsiveNavLink from '@/Components/ResponsiveNavLink.vue'; import ResponsiveNavLink from '@/Components/ResponsiveNavLink.vue';
const props = defineProps({
footer: {
type: Boolean,
default: true,
}
})
const slots = useSlots(); const slots = useSlots();
const pic = usePage().props.user ? usePage().props.user.pic : ""; const pic = usePage().props.user ? usePage().props.user.pic : "";
usePage().props.user, usePage().props; usePage().props.user, usePage().props;
@ -126,7 +135,7 @@ const toggleMenu = () => {
</div> </div>
</div> </div>
<slot name="content"/> <slot name="content"/>
<footer class="w-full desktop:px-[17.5%] laptop:px-[12.5%] px-2 bg-gray-50 text-gray-900 flex items-center justify-between"> <footer v-if="footer" class="w-full desktop:px-[17.5%] laptop:px-[12.5%] px-2 bg-gray-50 text-gray-900 flex items-center justify-between">
<p class="italic text-gray-600 laptop:text-lg text-xs">&copy; 2024 Propriété de l'association Scout Bas-Lac</p> <p class="italic text-gray-600 laptop:text-lg text-xs">&copy; 2024 Propriété de l'association Scout Bas-Lac</p>
<div class="flex my-8"> <div class="flex my-8">
<div class="group laptop:mx-2 mx-1"> <div class="group laptop:mx-2 mx-1">

@ -1,15 +1,15 @@
<script setup> <script setup>
import { Head, Link } from '@inertiajs/vue3'; import { Head, Link } from '@inertiajs/vue3';
import Layout from '@/Layouts/Layout.vue'; import Layout from '@/Layouts/Layout.vue';
import MarkdownEditor from '@/Components/MarkdownEditor.vue'; import Editor from '@/Components/Editor/Editor.vue';
</script> </script>
<template> <template>
<Head title="Home"/> <Head title="Home"/>
<Layout> <Layout :footer="false">
<template #content> <template #content>
<div class="w-full h-screen flex items-center justify-center px-[13.5%] py-[10%]"> <div class="w-full h-screen flex justify-center px-[13.5%]">
<MarkdownEditor/> <Editor @update="(data) => {}" />
</div> </div>
</template> </template>
</Layout> </Layout>

@ -10,7 +10,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
@vite("resources/css/tiptap.css")
<!-- Scripts --> <!-- Scripts -->
@routes @routes

Loading…
Cancel
Save