diff --git a/resources/css/content.css b/resources/css/content.css index c696be22c..8e9851e19 100644 --- a/resources/css/content.css +++ b/resources/css/content.css @@ -61,9 +61,14 @@ @apply rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm; } > :where(pre) { - @apply mb-[1.25em] !whitespace-pre overflow-x-auto; + @apply relative grid grid-cols-1 grid-rows-1 mb-[1.25em] !whitespace-pre overflow-x-auto; + &[data-language]::after { + @apply row-start-1 col-start-1 sticky left-0 p-2 content-[attr(data-language)] pointer-events-none text-muted-foreground text-xs; + } > :where(code) { - @apply block py-[.625em] px-[1.125em] bg-muted rounded-md [font-size:inherit]; + @apply block row-start-1 col-start-1 min-w-max in-data-language:pt-[calc(1em+1.25rem)] py-[1em] px-[1.25em] bg-muted rounded-md [font-size:inherit] + transition-colors in-data-has-dropdown-open:in-data-textselected:outline-primary/50 outline outline-transparent -outline-offset-3 + ; } } > :where(hr) { diff --git a/resources/js/components/ui/dropdown-menu/DropdownMenu.vue b/resources/js/components/ui/dropdown-menu/DropdownMenu.vue index 5266c37cd..46a0c66e8 100644 --- a/resources/js/components/ui/dropdown-menu/DropdownMenu.vue +++ b/resources/js/components/ui/dropdown-menu/DropdownMenu.vue @@ -8,7 +8,7 @@ const forwarded = useForwardPropsEmits(props, emits) - - + + diff --git a/resources/js/components/ui/popover/PopoverAnchor.vue b/resources/js/components/ui/popover/PopoverAnchor.vue new file mode 100644 index 000000000..ae91e1279 --- /dev/null +++ b/resources/js/components/ui/popover/PopoverAnchor.vue @@ -0,0 +1,15 @@ + + + + + + + diff --git a/resources/js/components/ui/popover/index.ts b/resources/js/components/ui/popover/index.ts index 495d55a80..1a435c58d 100644 --- a/resources/js/components/ui/popover/index.ts +++ b/resources/js/components/ui/popover/index.ts @@ -1,3 +1,4 @@ export { default as Popover } from './Popover.vue' +export { default as PopoverAnchor } from './PopoverAnchor.vue' export { default as PopoverTrigger } from './PopoverTrigger.vue' export { default as PopoverContent } from './PopoverContent.vue' diff --git a/resources/js/components/ui/toggle/index.ts b/resources/js/components/ui/toggle/index.ts index 7e35be2f8..e270db622 100644 --- a/resources/js/components/ui/toggle/index.ts +++ b/resources/js/components/ui/toggle/index.ts @@ -3,7 +3,7 @@ import { type VariantProps, cva } from 'class-variance-authority' export { default as Toggle } from './Toggle.vue' export const toggleVariants = cva( - 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground', + 'inline-flex gap-2 items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground aria-pressed:bg-accent aria-pressed:text-accent-foreground', { variants: { variant: { diff --git a/resources/js/form/components/fields/editor/Editor.vue b/resources/js/form/components/fields/editor/Editor.vue index 8dc4b151a..48b42cd62 100644 --- a/resources/js/form/components/fields/editor/Editor.vue +++ b/resources/js/form/components/fields/editor/Editor.vue @@ -61,6 +61,7 @@ import { getTextInputReplacementsExtension } from "@/form/components/fields/editor/extensions/TextInputReplacements"; + import CodeBlockDropdown from "@/form/components/fields/editor/toolbar/CodeBlockDropdown.vue"; const emit = defineEmits>(); const props = defineProps>(); @@ -77,6 +78,7 @@ const el = useTemplateRef('el'); const embedModal = ref>(); const linkDropdown = ref>(); + const codeBlockDropdown = ref>(); provide('editor', { props, @@ -254,6 +256,7 @@ ? 'border-none rounded-none bg-transparent' : 'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background' )" + :data-has-dropdown-open="codeBlockDropdown?.open ? true : null" :data-fullscreen="isFullscreen ? true : null" ref="el" > @@ -281,6 +284,9 @@ + + + { + if (node.type.name === CodeBlock.name && node.attrs.language) { + decos.push( + Decoration.node(pos, pos + node.nodeSize, { + 'data-language': node.attrs.language + }) + ); + } + }); + + return DecorationSet.create(state.doc, decos); + } + }, + }) + ] + } +}) diff --git a/resources/js/form/components/fields/editor/extensions/index.ts b/resources/js/form/components/fields/editor/extensions/index.ts index 7c0ed32b1..89b6aac7e 100644 --- a/resources/js/form/components/fields/editor/extensions/index.ts +++ b/resources/js/form/components/fields/editor/extensions/index.ts @@ -18,7 +18,6 @@ import { TableRow } from '@tiptap/extension-table-row'; import { TableHeader } from '@tiptap/extension-table-header'; import { TableCell } from '@tiptap/extension-table-cell'; import { Highlight } from '@tiptap/extension-highlight'; -import { CodeBlock } from '@tiptap/extension-code-block'; import { Superscript } from '@tiptap/extension-superscript'; import { OrderedList } from '@tiptap/extension-ordered-list'; import { Link } from '@tiptap/extension-link'; @@ -28,7 +27,7 @@ import { Iframe } from './iframe/Iframe'; import { Clipboard } from './Clipboard'; import { Small } from './Small'; import { FormEditorFieldData, FormEditorToolbarButton } from "@/types"; - +import { CodeBlock } from "@/form/components/fields/editor/extensions/CodeBlock"; export function getExtensions(field: FormEditorFieldData) { const toolbarHas = (buttonName: FormEditorToolbarButton | FormEditorToolbarButton[]) => diff --git a/resources/js/form/components/fields/editor/toolbar/CodeBlockDropdown.vue b/resources/js/form/components/fields/editor/toolbar/CodeBlockDropdown.vue new file mode 100644 index 000000000..289d306d7 --- /dev/null +++ b/resources/js/form/components/fields/editor/toolbar/CodeBlockDropdown.vue @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + {{ __('sharp::modals.ok_button') }} + + + + {{ __('sharp::form.editor.dialogs.code_block.toggle_off') }} + + + + diff --git a/resources/js/form/components/fields/editor/toolbar/LinkDropdown.vue b/resources/js/form/components/fields/editor/toolbar/LinkDropdown.vue index e45153dd3..a0e8d17ae 100644 --- a/resources/js/form/components/fields/editor/toolbar/LinkDropdown.vue +++ b/resources/js/form/components/fields/editor/toolbar/LinkDropdown.vue @@ -79,7 +79,7 @@ } defineExpose({ - open: () => { open.value = true; onOpen() }, + open, }); @@ -91,8 +91,7 @@ > - + 'Insert Iframe (embed)', 'editor.dialogs.iframe.update_title' => 'Update Iframe (embed)', 'editor.dialogs.iframe.invalid_message' => 'Iframe code is invalid', + 'editor.dialogs.code_block.language_label' => 'Language', + 'editor.dialogs.code_block.toggle_off' => 'Convert to paragraph', 'editor.dialogs.embed.submit_button_insert' => 'Insert', 'editor.dialogs.embed.submit_button_update' => 'Update', diff --git a/resources/lang/fr/form.php b/resources/lang/fr/form.php index a4b32a4fd..316c9a21d 100644 --- a/resources/lang/fr/form.php +++ b/resources/lang/fr/form.php @@ -92,6 +92,9 @@ 'editor.dialogs.iframe.update_title' => 'Modifier l’Iframe (video, audio...)', 'editor.dialogs.iframe.invalid_message' => 'Le code de l’Iframe est invalide', + 'editor.dialogs.code_block.language_label' => 'Langage', + 'editor.dialogs.code_block.toggle_off' => 'Convertir en paragraphe', + 'editor.dialogs.embed.submit_button_insert' => 'Insérer', 'editor.dialogs.embed.submit_button_update' => 'Mettre à jour',