Skip to content

Commit ef06b6d

Browse files
committed
wip improve editor
1 parent 28c5c53 commit ef06b6d

9 files changed

Lines changed: 308 additions & 297 deletions

File tree

resources/js/components/ui/dialog/DialogContent.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import {
1111
} from 'reka-ui'
1212
import { X } from 'lucide-vue-next'
1313
import { cn } from '@/utils/cn'
14-
import { useParentHTMLDialogElement } from "@/composables/useParentDialogElement";
1514
16-
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
15+
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'], closeButton?:boolean }>()
1716
const emits = defineEmits<DialogContentEmits>()
1817
1918
const delegatedProps = computed(() => {
@@ -23,11 +22,10 @@ const delegatedProps = computed(() => {
2322
})
2423
2524
const forwarded = useForwardPropsEmits(delegatedProps, emits);
26-
const parentHTMLDialogElement = useParentHTMLDialogElement();
2725
</script>
2826

2927
<template>
30-
<DialogPortal :to="parentHTMLDialogElement ?? undefined">
28+
<DialogPortal>
3129
<DialogOverlay
3230
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
3331
/>

resources/js/components/ui/dialog/DialogScrollContent.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
} from 'reka-ui'
1212
import { X } from 'lucide-vue-next'
1313
import { cn } from '@/utils/cn'
14-
import { useParentHTMLDialogElement } from "@/composables/useParentDialogElement";
1514
1615
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
1716
const emits = defineEmits<DialogContentEmits>()
@@ -24,7 +23,6 @@
2423
2524
const forwarded = useForwardPropsEmits(delegatedProps, emits)
2625
const overlay = useTemplateRef<InstanceType<typeof DialogOverlay>>('overlay');
27-
const parentHTMLDialogElement = useParentHTMLDialogElement();
2826
defineExpose({
2927
scrollToTop: () => {
3028
overlay.value.$el.scrollTo(0, 0);
@@ -33,7 +31,7 @@
3331
</script>
3432

3533
<template>
36-
<DialogPortal :to="parentHTMLDialogElement ?? undefined">
34+
<DialogPortal>
3735
<DialogOverlay
3836
class="fixed inset-0 z-50 grid grid-cols-1 place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
3937
ref="overlay"
Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,19 @@
1-
import { computed, ComputedRef, inject, nextTick, onMounted, provide, ref, Ref } from "vue";
1+
import { computed, nextTick, onMounted, ref } from "vue";
22
import { injectDialogRootContext } from "reka-ui";
33

4-
export function provideParentDialogElement(dialog: Ref<HTMLElement>) {
5-
provide<ComputedRef<HTMLDialogElement | undefined>>(
6-
'parentDialog',
7-
computed(() =>
8-
dialog.value instanceof HTMLDialogElement
9-
? dialog.value
10-
: undefined
11-
)
12-
);
13-
}
144

15-
export function useParentHTMLDialogElement() {
16-
return inject<ComputedRef<HTMLDialogElement | undefined>>('parentDialog', undefined);
17-
}
185

6+
/**
7+
* Ensure that poppers + dialogs are a child of another parent dialog element for correct positioning / z-index
8+
*/
199
export function useParentDialogElement() {
20-
const parentHTMLDialogElement = useParentHTMLDialogElement();
2110
const rekaDialogContext = injectDialogRootContext(null);
11+
const rekaDialogElement = ref<HTMLElement | undefined>();
2212

23-
if(rekaDialogContext) {
24-
const rekaDialogElement = ref<HTMLElement | undefined>();
25-
26-
onMounted(async () => {
27-
await nextTick();
28-
rekaDialogElement.value = rekaDialogContext?.contentElement.value?.parentElement;
29-
});
30-
31-
return computed(() => parentHTMLDialogElement.value ?? rekaDialogElement.value);
32-
}
13+
onMounted(async () => {
14+
await nextTick();
15+
rekaDialogElement.value = rekaDialogContext?.contentElement.value?.parentElement;
16+
});
3317

34-
return parentHTMLDialogElement;
18+
return computed(() => rekaDialogElement.value);
3519
}

resources/js/form/components/fields/editor/Editor.vue

Lines changed: 206 additions & 241 deletions
Large diffs are not rendered by default.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script setup lang="ts">
2+
import { Dialog, DialogScrollContent } from "@/components/ui/dialog";
3+
import { cn } from "@/utils/cn";
4+
import { DialogContent, DialogOverlay, DialogPortal } from "reka-ui";
5+
6+
const isFullscreen = defineModel<boolean>('fullscreen');
7+
8+
function onBackdropPointerDown(pointerDownEvent: PointerEvent) {
9+
window.addEventListener('pointerup', (pointerUpEvent: PointerEvent) => {
10+
if(pointerDownEvent.target === pointerUpEvent.target) {
11+
isFullscreen.value = false;
12+
}
13+
}, { once: true });
14+
}
15+
</script>
16+
17+
<template>
18+
<template v-if="isFullscreen">
19+
<Dialog v-model:open="isFullscreen">
20+
<DialogPortal>
21+
<DialogOverlay
22+
class="fixed inset-0 z-50 grid grid-cols-1 grid-rows-1 justify-items-center py-4 sm:px-4 overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
23+
>
24+
<DialogContent
25+
:class="
26+
cn(
27+
'relative z-50 grid grid-cols-1 grid-rows-1 w-full max-w-7xl gap-4 border bg-background shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg',
28+
)"
29+
@pointer-down-outside.prevent="onBackdropPointerDown($event.detail.originalEvent)"
30+
>
31+
<slot />
32+
</DialogContent>
33+
</DialogOverlay>
34+
</DialogPortal>
35+
</Dialog>
36+
</template>
37+
<template v-else>
38+
<slot />
39+
</template>
40+
</template>

resources/js/form/components/fields/editor/NodeDragHandle.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { GripVertical } from "lucide-vue-next";
44
</script>
55

66
<template>
7-
<div class="z-10 absolute grid place-content-center opacity-0 right-0 top-1/2 translate-x-1/2 -translate-y-1/2 h-4 w-3 rounded-sm border bg-border duration-300 transition-opacity cursor-grab hover:bg-foreground hover:border-foreground hover:text-background in-[[data-node-view-wrapper]:hover]:opacity-100"
7+
<div class="z-5 absolute grid place-content-center opacity-0 right-0 top-1/2 translate-x-1/2 -translate-y-1/2 h-4 w-3 rounded-sm border bg-border duration-300 transition-opacity cursor-grab hover:bg-foreground hover:border-foreground hover:text-background in-[[data-node-view-wrapper]:hover]:opacity-100"
88
data-drag-handle
99
>
1010
<div class="absolute -inset-x-2 -inset-y-3"></div>

resources/js/form/components/fields/editor/extensions/Clipboard.ts

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@ import { Command, Extension } from "@tiptap/core";
22
import { Plugin } from '@tiptap/pm/state';
33
import { DOMParser } from '@tiptap/pm/model';
44
import { __ } from "@/utils/i18n";
5+
import { EditorView } from "@tiptap/pm/view";
6+
7+
function dispatchCopy(view: EditorView) {
8+
const clipboardData = new DataTransfer();
9+
const event = new ClipboardEvent('copy', {
10+
bubbles: true,
11+
cancelable: true,
12+
clipboardData,
13+
});
14+
15+
view.dom.dispatchEvent(event);
16+
17+
const clipboardItem = new ClipboardItem({
18+
'text/html': clipboardData.getData('text/html'),
19+
'text/plain': clipboardData.getData('text/plain'),
20+
});
21+
22+
navigator.clipboard.write([clipboardItem]).then(() => {
23+
}).catch(err => {
24+
alert(__('sharp::errors.failed_to_write_to_clipboard'));
25+
});
26+
}
527

628
export const Clipboard = Extension.create({
729
name: 'clipboard',
@@ -36,6 +58,28 @@ export const Clipboard = Extension.create({
3658
}
3759
return html;
3860
},
61+
62+
handleKeyDown(view, event) {
63+
// fix bug when copy isn't working in chrome https://github.com/ProseMirror/prosemirror/issues/884
64+
if((event.metaKey || event.ctrlKey) && event.key === 'c') {
65+
let copied = false;
66+
view.dom.addEventListener('copy', () => {
67+
copied = true;
68+
}, { once: true });
69+
setTimeout(() => {
70+
if(!copied) {
71+
dispatchCopy(view);
72+
}
73+
}, 50);
74+
75+
}
76+
},
77+
78+
handleDOMEvents: {
79+
copy(view, event) {
80+
console.log(event);
81+
}
82+
}
3983
},
4084
})
4185
]
@@ -45,24 +89,7 @@ export const Clipboard = Extension.create({
4589
copyNode: (pos: number): Command => ({ editor, dispatch }) => {
4690
if(dispatch) {
4791
editor.commands.setNodeSelection(pos);
48-
const clipboardData = new DataTransfer();
49-
const event = new ClipboardEvent('copy', {
50-
bubbles: true,
51-
cancelable: true,
52-
clipboardData,
53-
});
54-
55-
editor.view.dom.dispatchEvent(event);
56-
57-
const clipboardItem = new ClipboardItem({
58-
'text/html': clipboardData.getData('text/html'),
59-
'text/plain': clipboardData.getData('text/plain'),
60-
});
61-
62-
navigator.clipboard.write([clipboardItem]).then(() => {
63-
}).catch(err => {
64-
alert(__('sharp::errors.failed_to_write_to_clipboard'));
65-
});
92+
dispatchCopy(editor.view);
6693
}
6794

6895
return true;

resources/js/form/components/fields/editor/extensions/upload/UploadNode.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import { FormUploadFieldData } from "@/types";
99
import { ExtensionNodeProps } from "@/form/components/fields/editor/types";
1010
import { useParentEditor } from "@/form/components/fields/editor/useParentEditor";
11-
import { useEditorNode } from "@/form/components/fields/editor/useEditorNode";
1211
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
12+
import NodeDragHandle from "@/form/components/fields/editor/NodeDragHandle.vue";
1313
1414
const props = defineProps<ExtensionNodeProps<typeof UploadExtension, UploadNodeAttributes>>();
1515
@@ -66,7 +66,7 @@
6666

6767
<template>
6868
<NodeRenderer
69-
class="block my-4 first:mt-0 last:mb-0 border rounded-md p-4 outline-none"
69+
class="relative block my-4 first:mt-0 last:mb-0 border rounded-md p-4 outline-none"
7070
:class="{ 'group-focus/editor:border-primary': props.selected }"
7171
:node="node"
7272
>
@@ -98,5 +98,6 @@
9898
</DropdownMenuItem>
9999
</template>
100100
</Upload>
101+
<NodeDragHandle />
101102
</NodeRenderer>
102103
</template>

resources/js/form/components/fields/editor/useParentEditor.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ export type ParentEditor = {
1616
embedModal: Ref<InstanceType<typeof EditorEmbedModal>>
1717
uploadManager: ContentUploadManager<Form>,
1818
uploadModal: Ref<InstanceType<typeof EditorUploadModal>>,
19-
isMounted: Ref<boolean>,
20-
isUnmounting: Ref<boolean>,
2119
};
2220

2321
export function useParentEditor() {

0 commit comments

Comments
 (0)