@blockslides/vue-3 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +36 -0
- package/dist/Editor.d.ts +24 -0
- package/dist/EditorContent.d.ts +18 -0
- package/dist/FloatingMenu-BKkixozS.js +226 -0
- package/dist/FloatingMenu-By8Qi7tW.cjs +1 -0
- package/dist/NodeViewContent.d.ts +13 -0
- package/dist/NodeViewWrapper.d.ts +13 -0
- package/dist/VueMarkViewRenderer.d.ts +63 -0
- package/dist/VueNodeViewRenderer.d.ts +63 -0
- package/dist/VueRenderer.d.ts +35 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +718 -0
- package/dist/menus/BubbleMenu.d.ts +6 -0
- package/dist/menus/FloatingMenu.d.ts +6 -0
- package/dist/menus/index.d.ts +3 -0
- package/dist/menus.cjs +1 -0
- package/dist/menus.d.ts +1 -0
- package/dist/menus.js +6 -0
- package/dist/useEditor.d.ts +4 -0
- package/dist/useSlideEditor.d.ts +36 -0
- package/package.json +69 -0
- package/src/Editor.ts +93 -0
- package/src/EditorContent.ts +77 -0
- package/src/NodeViewContent.ts +21 -0
- package/src/NodeViewWrapper.ts +31 -0
- package/src/SlideEditor.vue +54 -0
- package/src/VueMarkViewRenderer.ts +130 -0
- package/src/VueNodeViewRenderer.ts +317 -0
- package/src/VueRenderer.ts +104 -0
- package/src/index.ts +12 -0
- package/src/menus/BubbleMenu.ts +111 -0
- package/src/menus/BubbleMenuPreset.vue +137 -0
- package/src/menus/FloatingMenu.ts +84 -0
- package/src/menus/index.ts +3 -0
- package/src/useEditor.ts +24 -0
- package/src/useSlideEditor.ts +255 -0
- package/src/vue-shims.d.ts +5 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { FloatingMenuPluginProps } from '@blockslides/extension-floating-menu'
|
|
2
|
+
import { FloatingMenuPlugin } from '@blockslides/extension-floating-menu'
|
|
3
|
+
import type { PropType } from 'vue'
|
|
4
|
+
import { defineComponent, h, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
5
|
+
|
|
6
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>
|
|
7
|
+
|
|
8
|
+
export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element'>
|
|
9
|
+
|
|
10
|
+
export const FloatingMenu = defineComponent({
|
|
11
|
+
name: 'FloatingMenu',
|
|
12
|
+
|
|
13
|
+
inheritAttrs: false,
|
|
14
|
+
|
|
15
|
+
props: {
|
|
16
|
+
pluginKey: {
|
|
17
|
+
// TODO: TypeScript breaks
|
|
18
|
+
// type: [String, Object as PropType<Exclude<FloatingMenuPluginProps['pluginKey'], string>>],
|
|
19
|
+
type: null,
|
|
20
|
+
default: 'floatingMenu',
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
editor: {
|
|
24
|
+
type: Object as PropType<FloatingMenuPluginProps['editor']>,
|
|
25
|
+
required: true,
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
options: {
|
|
29
|
+
type: Object as PropType<FloatingMenuPluginProps['options']>,
|
|
30
|
+
default: () => ({}),
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
appendTo: {
|
|
34
|
+
type: [Object, Function] as PropType<FloatingMenuPluginProps['appendTo']>,
|
|
35
|
+
default: undefined,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
shouldShow: {
|
|
39
|
+
type: Function as PropType<Exclude<Required<FloatingMenuPluginProps>['shouldShow'], null>>,
|
|
40
|
+
default: null,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
setup(props, { slots, attrs }) {
|
|
45
|
+
const root = ref<HTMLElement | null>(null)
|
|
46
|
+
|
|
47
|
+
onMounted(() => {
|
|
48
|
+
const { pluginKey, editor, options, appendTo, shouldShow } = props
|
|
49
|
+
|
|
50
|
+
const el = root.value
|
|
51
|
+
|
|
52
|
+
if (!el) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
el.style.visibility = 'hidden'
|
|
57
|
+
el.style.position = 'absolute'
|
|
58
|
+
|
|
59
|
+
// Remove element from DOM; plugin will re-parent it when shown
|
|
60
|
+
el.remove()
|
|
61
|
+
|
|
62
|
+
editor.registerPlugin(
|
|
63
|
+
FloatingMenuPlugin({
|
|
64
|
+
pluginKey,
|
|
65
|
+
editor,
|
|
66
|
+
element: el,
|
|
67
|
+
options,
|
|
68
|
+
appendTo,
|
|
69
|
+
shouldShow,
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
onBeforeUnmount(() => {
|
|
75
|
+
const { pluginKey, editor } = props
|
|
76
|
+
|
|
77
|
+
editor.unregisterPlugin(pluginKey)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Vue owns this element; attrs are applied reactively by Vue
|
|
81
|
+
// Plugin re-parents it when showing the menu
|
|
82
|
+
return () => h('div', { ref: root, ...attrs }, slots.default?.())
|
|
83
|
+
},
|
|
84
|
+
}) as any
|
package/src/useEditor.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EditorOptions } from '@blockslides/core'
|
|
2
|
+
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { Editor } from './Editor.js'
|
|
5
|
+
|
|
6
|
+
export const useEditor = (options: Partial<EditorOptions> = {}) => {
|
|
7
|
+
const editor = shallowRef<Editor>()
|
|
8
|
+
|
|
9
|
+
onMounted(() => {
|
|
10
|
+
editor.value = new Editor(options)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
onBeforeUnmount(() => {
|
|
14
|
+
// Cloning root node (and its children) to avoid content being lost by destroy
|
|
15
|
+
const nodes = editor.value?.view.dom?.parentNode
|
|
16
|
+
const newEl = nodes?.cloneNode(true) as HTMLElement
|
|
17
|
+
|
|
18
|
+
nodes?.parentNode?.replaceChild(newEl, nodes)
|
|
19
|
+
|
|
20
|
+
editor.value?.destroy()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return editor
|
|
24
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { computed, watch, toRefs } from 'vue'
|
|
2
|
+
import { templatesV1 } from '@blockslides/ai-context'
|
|
3
|
+
import type { AnyExtension, Editor, JSONContent } from '@blockslides/core'
|
|
4
|
+
import type { SlideOptions } from '@blockslides/extension-slide'
|
|
5
|
+
import { ExtensionKit, type ExtensionKitOptions } from '@blockslides/extension-kit'
|
|
6
|
+
|
|
7
|
+
import { useEditor } from './useEditor.js'
|
|
8
|
+
import type { EditorOptions } from '@blockslides/core'
|
|
9
|
+
|
|
10
|
+
type PresetTemplates = ReturnType<typeof templatesV1.listPresetTemplates>
|
|
11
|
+
|
|
12
|
+
export interface UseSlideEditorProps {
|
|
13
|
+
/**
|
|
14
|
+
* Initial content for the editor. If omitted, a single preset slide is used.
|
|
15
|
+
*/
|
|
16
|
+
content?: EditorOptions['content']
|
|
17
|
+
/**
|
|
18
|
+
* Called on every update with the current JSON document.
|
|
19
|
+
*/
|
|
20
|
+
onChange?: (doc: JSONContent, editor: Editor) => void
|
|
21
|
+
/**
|
|
22
|
+
* Additional extensions to append after the ExtensionKit bundle.
|
|
23
|
+
*/
|
|
24
|
+
extensions?: AnyExtension[]
|
|
25
|
+
/**
|
|
26
|
+
* Customize or disable pieces of ExtensionKit (e.g., bubbleMenu: false).
|
|
27
|
+
*/
|
|
28
|
+
extensionKitOptions?: ExtensionKitOptions
|
|
29
|
+
/**
|
|
30
|
+
* Optional preset list to power the add-slide button.
|
|
31
|
+
*/
|
|
32
|
+
presetTemplates?: PresetTemplates
|
|
33
|
+
/**
|
|
34
|
+
* Called once when an editor instance is ready.
|
|
35
|
+
*/
|
|
36
|
+
onEditorReady?: (editor: Editor) => void
|
|
37
|
+
/**
|
|
38
|
+
* Editor theme for UI styling
|
|
39
|
+
*/
|
|
40
|
+
theme?: EditorOptions['theme']
|
|
41
|
+
/**
|
|
42
|
+
* The editor's props
|
|
43
|
+
*/
|
|
44
|
+
editorProps?: EditorOptions['editorProps']
|
|
45
|
+
/**
|
|
46
|
+
* Called on every update
|
|
47
|
+
*/
|
|
48
|
+
onUpdate?: EditorOptions['onUpdate']
|
|
49
|
+
/**
|
|
50
|
+
* Additional editor options to pass through to the core editor.
|
|
51
|
+
* This allows passing any EditorOptions without Vue auto-initializing them.
|
|
52
|
+
*/
|
|
53
|
+
editorOptions?: Partial<EditorOptions>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const defaultSlide = () => ({
|
|
57
|
+
/**
|
|
58
|
+
* Placeholder slide if no content is provided.
|
|
59
|
+
*/
|
|
60
|
+
type: 'doc',
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: 'slide',
|
|
64
|
+
attrs: {
|
|
65
|
+
size: '16x9',
|
|
66
|
+
className: '',
|
|
67
|
+
id: 'slide-1',
|
|
68
|
+
backgroundMode: 'none',
|
|
69
|
+
backgroundColor: null,
|
|
70
|
+
backgroundImage: null,
|
|
71
|
+
backgroundOverlayColor: null,
|
|
72
|
+
backgroundOverlayOpacity: null,
|
|
73
|
+
},
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'column',
|
|
77
|
+
attrs: {
|
|
78
|
+
align: 'center',
|
|
79
|
+
padding: 'lg',
|
|
80
|
+
margin: null,
|
|
81
|
+
gap: 'md',
|
|
82
|
+
backgroundColor: '#ffffff',
|
|
83
|
+
backgroundImage: null,
|
|
84
|
+
borderRadius: null,
|
|
85
|
+
border: null,
|
|
86
|
+
fill: true,
|
|
87
|
+
width: null,
|
|
88
|
+
height: null,
|
|
89
|
+
justify: 'center',
|
|
90
|
+
},
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: 'heading',
|
|
94
|
+
attrs: {
|
|
95
|
+
align: null,
|
|
96
|
+
padding: null,
|
|
97
|
+
margin: null,
|
|
98
|
+
gap: null,
|
|
99
|
+
backgroundColor: null,
|
|
100
|
+
backgroundImage: null,
|
|
101
|
+
borderRadius: null,
|
|
102
|
+
border: null,
|
|
103
|
+
fill: null,
|
|
104
|
+
width: null,
|
|
105
|
+
height: null,
|
|
106
|
+
justify: null,
|
|
107
|
+
id: '1fc4664c-333d-4203-a3f1-3ad27a54c535',
|
|
108
|
+
'data-toc-id': '1fc4664c-333d-4203-a3f1-3ad27a54c535',
|
|
109
|
+
level: 1,
|
|
110
|
+
},
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: 'text',
|
|
114
|
+
text: 'Lorem ipsum dolor sit amet',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'paragraph',
|
|
120
|
+
attrs: {
|
|
121
|
+
align: null,
|
|
122
|
+
padding: null,
|
|
123
|
+
margin: null,
|
|
124
|
+
gap: null,
|
|
125
|
+
backgroundColor: null,
|
|
126
|
+
backgroundImage: null,
|
|
127
|
+
borderRadius: null,
|
|
128
|
+
border: null,
|
|
129
|
+
fill: null,
|
|
130
|
+
width: null,
|
|
131
|
+
height: null,
|
|
132
|
+
justify: null,
|
|
133
|
+
},
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: 'text',
|
|
137
|
+
text: 'Consectetur adipiscing elit. Sed do eiusmod tempor incididunt. ',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const defaultAddSlideButton = (presets: PresetTemplates) => ({
|
|
149
|
+
showPresets: true,
|
|
150
|
+
presets,
|
|
151
|
+
presetBackground: '#0f172a',
|
|
152
|
+
presetForeground: '#e5e7eb',
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const defaultSlideOptions: Partial<SlideOptions> = {
|
|
156
|
+
renderMode: 'dynamic',
|
|
157
|
+
hoverOutline: { color: '#3b82f6', width: '1.5px', offset: '4px' },
|
|
158
|
+
hoverOutlineCascade: false,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const useSlideEditor = (props: UseSlideEditorProps = {}) => {
|
|
162
|
+
const {
|
|
163
|
+
content,
|
|
164
|
+
onChange,
|
|
165
|
+
extensions,
|
|
166
|
+
extensionKitOptions,
|
|
167
|
+
presetTemplates,
|
|
168
|
+
onEditorReady,
|
|
169
|
+
theme = 'light',
|
|
170
|
+
editorProps,
|
|
171
|
+
onUpdate,
|
|
172
|
+
editorOptions = {}
|
|
173
|
+
} = props
|
|
174
|
+
/**
|
|
175
|
+
* Presets for add slide button.
|
|
176
|
+
*/
|
|
177
|
+
const presets = computed<PresetTemplates>(
|
|
178
|
+
() => presetTemplates ?? templatesV1.listPresetTemplates()
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const mergedExtensionKitOptions = computed<ExtensionKitOptions>(() => {
|
|
182
|
+
const addSlideButton =
|
|
183
|
+
extensionKitOptions?.addSlideButton === false
|
|
184
|
+
? false
|
|
185
|
+
: {
|
|
186
|
+
...defaultAddSlideButton(presets.value),
|
|
187
|
+
...(extensionKitOptions?.addSlideButton ?? {}),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const slide =
|
|
191
|
+
extensionKitOptions?.slide === false
|
|
192
|
+
? false
|
|
193
|
+
: {
|
|
194
|
+
...defaultSlideOptions,
|
|
195
|
+
...(extensionKitOptions?.slide ?? {}),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
...extensionKitOptions,
|
|
200
|
+
addSlideButton,
|
|
201
|
+
slide,
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const resolvedExtensions = computed<AnyExtension[]>(() => {
|
|
206
|
+
const kit = ExtensionKit.configure(mergedExtensionKitOptions.value)
|
|
207
|
+
return extensions ? [kit, ...extensions] : [kit]
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Initial content for the editor.
|
|
212
|
+
*/
|
|
213
|
+
const initialContent = content ?? defaultSlide()
|
|
214
|
+
|
|
215
|
+
console.log('resolvedExtensions', resolvedExtensions.value, resolvedExtensions);
|
|
216
|
+
console.log('editorOptions (as single prop):', editorOptions);
|
|
217
|
+
|
|
218
|
+
const editor = useEditor(
|
|
219
|
+
{
|
|
220
|
+
content: initialContent,
|
|
221
|
+
extensions: resolvedExtensions.value,
|
|
222
|
+
theme,
|
|
223
|
+
editorProps: {
|
|
224
|
+
attributes: {
|
|
225
|
+
autocomplete: 'off',
|
|
226
|
+
autocorrect: 'off',
|
|
227
|
+
autocapitalize: 'off',
|
|
228
|
+
class: 'min-h-full min-w-full',
|
|
229
|
+
...(editorProps?.attributes ?? {}),
|
|
230
|
+
},
|
|
231
|
+
...editorProps,
|
|
232
|
+
},
|
|
233
|
+
...editorOptions,
|
|
234
|
+
onUpdate: (ctx: any) => {
|
|
235
|
+
const json = ctx.editor.getJSON()
|
|
236
|
+
onChange?.(json, ctx.editor)
|
|
237
|
+
onUpdate?.(ctx)
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
watch(
|
|
243
|
+
editor,
|
|
244
|
+
newEditor => {
|
|
245
|
+
if (newEditor && !newEditor.isDestroyed) {
|
|
246
|
+
onEditorReady?.(newEditor)
|
|
247
|
+
} else {
|
|
248
|
+
console.log('[useSlideEditor] ❌ Editor not ready or destroyed')
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{ immediate: true }
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return { editor, presets }
|
|
255
|
+
}
|