@blockslides/vue-3-prebuilts 0.1.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/BubbleMenuPreset.vue_vue_type_script_setup_true_lang-B5Ihs2fW.js +101 -0
- package/dist/BubbleMenuPreset.vue_vue_type_script_setup_true_lang-Tkjxu33S.cjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +211 -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 +132 -0
- package/dist/useSlideEditor.d.ts +53 -0
- package/package.json +75 -0
- package/src/SlideEditor.vue +54 -0
- package/src/index.ts +9 -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/useSlideEditor.ts +255 -0
- package/src/vue-shims.d.ts +5 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 BlockSlides
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Acknowledgments
|
|
26
|
+
|
|
27
|
+
This project includes code and patterns adapted from [Tiptap](https://tiptap.dev),
|
|
28
|
+
an open-source headless editor framework for web artisans.
|
|
29
|
+
|
|
30
|
+
**Tiptap License:**
|
|
31
|
+
|
|
32
|
+
- Copyright (c) 2025, Tiptap GmbH
|
|
33
|
+
- Licensed under MIT License
|
|
34
|
+
- Original source: https://github.com/ueberdosis/tiptap
|
|
35
|
+
|
|
36
|
+
See `_tiptap/LICENSE.md` for the full Tiptap license.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { defineComponent as T, ref as S, onMounted as E, watch as y, onBeforeUnmount as _, openBlock as A, createElementBlock as D, createCommentVNode as C } from "vue";
|
|
2
|
+
import { BubbleMenuPlugin as N } from "@blockslides/extension-bubble-menu";
|
|
3
|
+
import { NodeSelection as x } from "@blockslides/pm/state";
|
|
4
|
+
import { buildMenuElement as L, DEFAULT_ALIGNMENTS as b, DEFAULT_FONT_SIZES as v, DEFAULT_FONTS as B, DEFAULT_HIGHLIGHT_PALETTE as I, DEFAULT_COLOR_PALETTE as R, DEFAULT_ITEMS as z } from "@blockslides/extension-bubble-menu-preset";
|
|
5
|
+
import { isTextSelection as F } from "@blockslides/core";
|
|
6
|
+
const M = {
|
|
7
|
+
key: 0,
|
|
8
|
+
style: { display: "none" }
|
|
9
|
+
}, m = "bubbleMenuPreset", j = /* @__PURE__ */ T({
|
|
10
|
+
__name: "BubbleMenuPreset",
|
|
11
|
+
props: {
|
|
12
|
+
editor: {},
|
|
13
|
+
updateDelay: {},
|
|
14
|
+
resizeDelay: {},
|
|
15
|
+
appendTo: {},
|
|
16
|
+
shouldShow: {},
|
|
17
|
+
getReferencedVirtualElement: {},
|
|
18
|
+
options: {},
|
|
19
|
+
items: {},
|
|
20
|
+
className: {},
|
|
21
|
+
injectStyles: { type: Boolean, default: !0 },
|
|
22
|
+
textColors: {},
|
|
23
|
+
highlightColors: {},
|
|
24
|
+
fonts: {},
|
|
25
|
+
fontSizes: {},
|
|
26
|
+
alignments: {},
|
|
27
|
+
onTextAction: {},
|
|
28
|
+
onImageReplace: {}
|
|
29
|
+
},
|
|
30
|
+
setup(p) {
|
|
31
|
+
const e = p, n = S(null);
|
|
32
|
+
let o;
|
|
33
|
+
const s = () => {
|
|
34
|
+
const t = e.editor;
|
|
35
|
+
if (!t || t.isDestroyed)
|
|
36
|
+
return;
|
|
37
|
+
o && (o(), o = void 0);
|
|
38
|
+
const { element: i, cleanup: d } = L(t, {
|
|
39
|
+
items: e.items ?? z,
|
|
40
|
+
className: e.className ?? "",
|
|
41
|
+
injectStyles: e.injectStyles !== !1,
|
|
42
|
+
textColors: e.textColors ?? R,
|
|
43
|
+
highlightColors: e.highlightColors ?? I,
|
|
44
|
+
fonts: e.fonts ?? B,
|
|
45
|
+
fontSizes: e.fontSizes ?? v,
|
|
46
|
+
alignments: e.alignments ?? b,
|
|
47
|
+
onTextAction: e.onTextAction,
|
|
48
|
+
onImageReplace: e.onImageReplace
|
|
49
|
+
});
|
|
50
|
+
n.value = i, o = d;
|
|
51
|
+
const f = ({ state: h, editor: a }) => {
|
|
52
|
+
var u, c;
|
|
53
|
+
const l = h.selection, r = l instanceof x && ["image", "imageBlock"].includes((c = (u = l.node) == null ? void 0 : u.type) == null ? void 0 : c.name) || a.isActive("image") || a.isActive("imageBlock");
|
|
54
|
+
return !!(r || F(l) && !l.empty && !r);
|
|
55
|
+
}, g = N({
|
|
56
|
+
editor: t,
|
|
57
|
+
element: i,
|
|
58
|
+
updateDelay: e.updateDelay,
|
|
59
|
+
resizeDelay: e.resizeDelay,
|
|
60
|
+
appendTo: e.appendTo,
|
|
61
|
+
pluginKey: m,
|
|
62
|
+
shouldShow: e.shouldShow ?? f,
|
|
63
|
+
getReferencedVirtualElement: e.getReferencedVirtualElement,
|
|
64
|
+
options: e.options
|
|
65
|
+
});
|
|
66
|
+
t.registerPlugin(g);
|
|
67
|
+
};
|
|
68
|
+
return E(() => {
|
|
69
|
+
s();
|
|
70
|
+
}), y(
|
|
71
|
+
() => [
|
|
72
|
+
e.editor,
|
|
73
|
+
e.updateDelay,
|
|
74
|
+
e.resizeDelay,
|
|
75
|
+
e.appendTo,
|
|
76
|
+
e.shouldShow,
|
|
77
|
+
e.getReferencedVirtualElement,
|
|
78
|
+
e.options,
|
|
79
|
+
e.items,
|
|
80
|
+
e.className,
|
|
81
|
+
e.injectStyles,
|
|
82
|
+
e.textColors,
|
|
83
|
+
e.highlightColors,
|
|
84
|
+
e.fonts,
|
|
85
|
+
e.fontSizes,
|
|
86
|
+
e.alignments,
|
|
87
|
+
e.onTextAction,
|
|
88
|
+
e.onImageReplace
|
|
89
|
+
],
|
|
90
|
+
() => {
|
|
91
|
+
s();
|
|
92
|
+
}
|
|
93
|
+
), _(() => {
|
|
94
|
+
var t;
|
|
95
|
+
e.editor && e.editor.unregisterPlugin(m), o && o(), (t = n.value) != null && t.parentNode && n.value.parentNode.removeChild(n.value);
|
|
96
|
+
}), (t, i) => n.value ? (A(), D("div", M)) : C("", !0);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
export {
|
|
100
|
+
j as _
|
|
101
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const n=require("vue"),E=require("@blockslides/extension-bubble-menu"),y=require("@blockslides/pm/state"),o=require("@blockslides/extension-bubble-menu-preset"),_=require("@blockslides/core"),A={key:0,style:{display:"none"}},p="bubbleMenuPreset",D=n.defineComponent({__name:"BubbleMenuPreset",props:{editor:{},updateDelay:{},resizeDelay:{},appendTo:{},shouldShow:{},getReferencedVirtualElement:{},options:{},items:{},className:{},injectStyles:{type:Boolean,default:!0},textColors:{},highlightColors:{},fonts:{},fontSizes:{},alignments:{},onTextAction:{},onImageReplace:{}},setup(g){const e=g,l=n.ref(null);let s;const r=()=>{const t=e.editor;if(!t||t.isDestroyed)return;s&&(s(),s=void 0);const{element:a,cleanup:f}=o.buildMenuElement(t,{items:e.items??o.DEFAULT_ITEMS,className:e.className??"",injectStyles:e.injectStyles!==!1,textColors:e.textColors??o.DEFAULT_COLOR_PALETTE,highlightColors:e.highlightColors??o.DEFAULT_HIGHLIGHT_PALETTE,fonts:e.fonts??o.DEFAULT_FONTS,fontSizes:e.fontSizes??o.DEFAULT_FONT_SIZES,alignments:e.alignments??o.DEFAULT_ALIGNMENTS,onTextAction:e.onTextAction,onImageReplace:e.onImageReplace});l.value=a,s=f;const h=({state:S,editor:u})=>{var d,m;const i=S.selection,c=i instanceof y.NodeSelection&&["image","imageBlock"].includes((m=(d=i.node)==null?void 0:d.type)==null?void 0:m.name)||u.isActive("image")||u.isActive("imageBlock");return!!(c||_.isTextSelection(i)&&!i.empty&&!c)},T=E.BubbleMenuPlugin({editor:t,element:a,updateDelay:e.updateDelay,resizeDelay:e.resizeDelay,appendTo:e.appendTo,pluginKey:p,shouldShow:e.shouldShow??h,getReferencedVirtualElement:e.getReferencedVirtualElement,options:e.options});t.registerPlugin(T)};return n.onMounted(()=>{r()}),n.watch(()=>[e.editor,e.updateDelay,e.resizeDelay,e.appendTo,e.shouldShow,e.getReferencedVirtualElement,e.options,e.items,e.className,e.injectStyles,e.textColors,e.highlightColors,e.fonts,e.fontSizes,e.alignments,e.onTextAction,e.onImageReplace],()=>{r()}),n.onBeforeUnmount(()=>{var t;e.editor&&e.editor.unregisterPlugin(p),s&&s(),(t=l.value)!=null&&t.parentNode&&l.value.parentNode.removeChild(l.value)}),(t,a)=>l.value?(n.openBlock(),n.createElementBlock("div",A)):n.createCommentVNode("",!0)}});exports._sfc_main=D;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),c=require("@blockslides/vue-3"),v=require("./BubbleMenuPreset.vue_vue_type_script_setup_true_lang-Tkjxu33S.cjs"),S=require("@blockslides/ai-context"),O=require("@blockslides/extension-kit"),h=require("@blockslides/core"),C=()=>({type:"doc",content:[{type:"slide",attrs:{size:"16x9",className:"",id:"slide-1",backgroundMode:"none",backgroundColor:null,backgroundImage:null,backgroundOverlayColor:null,backgroundOverlayOpacity:null},content:[{type:"column",attrs:{align:"center",padding:"lg",margin:null,gap:"md",backgroundColor:"#ffffff",backgroundImage:null,borderRadius:null,border:null,fill:!0,width:null,height:null,justify:"center"},content:[{type:"heading",attrs:{align:null,padding:null,margin:null,gap:null,backgroundColor:null,backgroundImage:null,borderRadius:null,border:null,fill:null,width:null,height:null,justify:null,id:"1fc4664c-333d-4203-a3f1-3ad27a54c535","data-toc-id":"1fc4664c-333d-4203-a3f1-3ad27a54c535",level:1},content:[{type:"text",text:"Lorem ipsum dolor sit amet"}]},{type:"paragraph",attrs:{align:null,padding:null,margin:null,gap:null,backgroundColor:null,backgroundImage:null,borderRadius:null,border:null,fill:null,width:null,height:null,justify:null},content:[{type:"text",text:"Consectetur adipiscing elit. Sed do eiusmod tempor incididunt. "}]}]}]}]}),E=t=>({showPresets:!0,presets:t,presetBackground:"#0f172a",presetForeground:"#e5e7eb"}),j={renderMode:"dynamic",hoverOutline:{color:"#3b82f6",width:"1.5px",offset:"4px"},hoverOutlineCascade:!1},_=(t={})=>{const{content:r,onChange:s,extensions:a,extensionKitOptions:l,presetTemplates:p,onEditorReady:o,theme:d="light",editorProps:u,onUpdate:i,editorOptions:m={}}=t,g=e.computed(()=>p??S.templatesV1.listPresetTemplates()),P=e.computed(()=>{const n=(l==null?void 0:l.addSlideButton)===!1?!1:{...E(g.value),...(l==null?void 0:l.addSlideButton)??{}},b=(l==null?void 0:l.slide)===!1?!1:{...j,...(l==null?void 0:l.slide)??{}};return{...l,addSlideButton:n,slide:b}}),f=e.computed(()=>{const n=O.ExtensionKit.configure(P.value);return a?[n,...a]:[n]}),k=r??C();console.log("resolvedExtensions",f.value,f),console.log("editorOptions (as single prop):",m);const y=c.useEditor({content:k,extensions:f.value,theme:d,editorProps:{attributes:{autocomplete:"off",autocorrect:"off",autocapitalize:"off",class:"min-h-full min-w-full",...(u==null?void 0:u.attributes)??{}},...u},...m,onUpdate:n=>{const b=n.editor.getJSON();s==null||s(b,n.editor),i==null||i(n)}});return e.watch(y,n=>{n&&!n.isDestroyed?o==null||o(n):console.log("[useSlideEditor] ❌ Editor not ready or destroyed")},{immediate:!0}),{editor:y,presets:g}},B={class:"bs-viewport"},M=e.defineComponent({__name:"SlideEditor",props:{bubbleMenuPreset:{type:[Boolean,Object],default:!0},className:{},style:{},content:{},onChange:{},extensions:{},extensionKitOptions:{},presetTemplates:{},onEditorReady:{},theme:{},editorProps:{},onUpdate:{},editorOptions:{}},setup(t){const r=t,{bubbleMenuPreset:s,className:a,style:l,...p}=r,{editor:o}=_(p),d=e.computed(()=>r.bubbleMenuPreset===!1?null:r.bubbleMenuPreset===!0?{}:r.bubbleMenuPreset);return(u,i)=>e.unref(o)?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(e.unref(a)),style:e.normalizeStyle(e.unref(l))},[e.createElementVNode("div",B,[e.createVNode(e.unref(c.EditorContent),{editor:e.unref(o)},null,8,["editor"]),d.value?(e.openBlock(),e.createBlock(v._sfc_main,e.mergeProps({key:0,editor:e.unref(o)},d.value),null,16,["editor"])):e.createCommentVNode("",!0)])],6)):e.createCommentVNode("",!0)}});exports.BubbleMenuPreset=v._sfc_main;exports.SlideEditor=M;exports.useSlideEditor=_;Object.keys(c).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>c[t]})});Object.keys(h).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>h[t]})});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { computed as c, watch as C, defineComponent as P, unref as n, openBlock as h, createElementBlock as E, normalizeStyle as _, normalizeClass as x, createElementVNode as B, createVNode as M, createBlock as N, mergeProps as w, createCommentVNode as k } from "vue";
|
|
2
|
+
import { useEditor as O, EditorContent as j } from "@blockslides/vue-3";
|
|
3
|
+
export * from "@blockslides/vue-3";
|
|
4
|
+
import { _ as z } from "./BubbleMenuPreset.vue_vue_type_script_setup_true_lang-B5Ihs2fW.js";
|
|
5
|
+
import { templatesV1 as I } from "@blockslides/ai-context";
|
|
6
|
+
import { ExtensionKit as V } from "@blockslides/extension-kit";
|
|
7
|
+
export * from "@blockslides/core";
|
|
8
|
+
const R = () => ({
|
|
9
|
+
/**
|
|
10
|
+
* Placeholder slide if no content is provided.
|
|
11
|
+
*/
|
|
12
|
+
type: "doc",
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: "slide",
|
|
16
|
+
attrs: {
|
|
17
|
+
size: "16x9",
|
|
18
|
+
className: "",
|
|
19
|
+
id: "slide-1",
|
|
20
|
+
backgroundMode: "none",
|
|
21
|
+
backgroundColor: null,
|
|
22
|
+
backgroundImage: null,
|
|
23
|
+
backgroundOverlayColor: null,
|
|
24
|
+
backgroundOverlayOpacity: null
|
|
25
|
+
},
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "column",
|
|
29
|
+
attrs: {
|
|
30
|
+
align: "center",
|
|
31
|
+
padding: "lg",
|
|
32
|
+
margin: null,
|
|
33
|
+
gap: "md",
|
|
34
|
+
backgroundColor: "#ffffff",
|
|
35
|
+
backgroundImage: null,
|
|
36
|
+
borderRadius: null,
|
|
37
|
+
border: null,
|
|
38
|
+
fill: !0,
|
|
39
|
+
width: null,
|
|
40
|
+
height: null,
|
|
41
|
+
justify: "center"
|
|
42
|
+
},
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: "heading",
|
|
46
|
+
attrs: {
|
|
47
|
+
align: null,
|
|
48
|
+
padding: null,
|
|
49
|
+
margin: null,
|
|
50
|
+
gap: null,
|
|
51
|
+
backgroundColor: null,
|
|
52
|
+
backgroundImage: null,
|
|
53
|
+
borderRadius: null,
|
|
54
|
+
border: null,
|
|
55
|
+
fill: null,
|
|
56
|
+
width: null,
|
|
57
|
+
height: null,
|
|
58
|
+
justify: null,
|
|
59
|
+
id: "1fc4664c-333d-4203-a3f1-3ad27a54c535",
|
|
60
|
+
"data-toc-id": "1fc4664c-333d-4203-a3f1-3ad27a54c535",
|
|
61
|
+
level: 1
|
|
62
|
+
},
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: "Lorem ipsum dolor sit amet"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: "paragraph",
|
|
72
|
+
attrs: {
|
|
73
|
+
align: null,
|
|
74
|
+
padding: null,
|
|
75
|
+
margin: null,
|
|
76
|
+
gap: null,
|
|
77
|
+
backgroundColor: null,
|
|
78
|
+
backgroundImage: null,
|
|
79
|
+
borderRadius: null,
|
|
80
|
+
border: null,
|
|
81
|
+
fill: null,
|
|
82
|
+
width: null,
|
|
83
|
+
height: null,
|
|
84
|
+
justify: null
|
|
85
|
+
},
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: "Consectetur adipiscing elit. Sed do eiusmod tempor incididunt. "
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}), T = (r) => ({
|
|
99
|
+
showPresets: !0,
|
|
100
|
+
presets: r,
|
|
101
|
+
presetBackground: "#0f172a",
|
|
102
|
+
presetForeground: "#e5e7eb"
|
|
103
|
+
}), A = {
|
|
104
|
+
renderMode: "dynamic",
|
|
105
|
+
hoverOutline: { color: "#3b82f6", width: "1.5px", offset: "4px" },
|
|
106
|
+
hoverOutlineCascade: !1
|
|
107
|
+
}, D = (r = {}) => {
|
|
108
|
+
const {
|
|
109
|
+
content: o,
|
|
110
|
+
onChange: u,
|
|
111
|
+
extensions: a,
|
|
112
|
+
extensionKitOptions: e,
|
|
113
|
+
presetTemplates: p,
|
|
114
|
+
onEditorReady: l,
|
|
115
|
+
theme: d = "light",
|
|
116
|
+
editorProps: s,
|
|
117
|
+
onUpdate: i,
|
|
118
|
+
editorOptions: g = {}
|
|
119
|
+
} = r, b = c(
|
|
120
|
+
() => p ?? I.listPresetTemplates()
|
|
121
|
+
), v = c(() => {
|
|
122
|
+
const t = (e == null ? void 0 : e.addSlideButton) === !1 ? !1 : {
|
|
123
|
+
...T(b.value),
|
|
124
|
+
...(e == null ? void 0 : e.addSlideButton) ?? {}
|
|
125
|
+
}, f = (e == null ? void 0 : e.slide) === !1 ? !1 : {
|
|
126
|
+
...A,
|
|
127
|
+
...(e == null ? void 0 : e.slide) ?? {}
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
...e,
|
|
131
|
+
addSlideButton: t,
|
|
132
|
+
slide: f
|
|
133
|
+
};
|
|
134
|
+
}), m = c(() => {
|
|
135
|
+
const t = V.configure(v.value);
|
|
136
|
+
return a ? [t, ...a] : [t];
|
|
137
|
+
}), S = o ?? R();
|
|
138
|
+
console.log("resolvedExtensions", m.value, m), console.log("editorOptions (as single prop):", g);
|
|
139
|
+
const y = O(
|
|
140
|
+
{
|
|
141
|
+
content: S,
|
|
142
|
+
extensions: m.value,
|
|
143
|
+
theme: d,
|
|
144
|
+
editorProps: {
|
|
145
|
+
attributes: {
|
|
146
|
+
autocomplete: "off",
|
|
147
|
+
autocorrect: "off",
|
|
148
|
+
autocapitalize: "off",
|
|
149
|
+
class: "min-h-full min-w-full",
|
|
150
|
+
...(s == null ? void 0 : s.attributes) ?? {}
|
|
151
|
+
},
|
|
152
|
+
...s
|
|
153
|
+
},
|
|
154
|
+
...g,
|
|
155
|
+
onUpdate: (t) => {
|
|
156
|
+
const f = t.editor.getJSON();
|
|
157
|
+
u == null || u(f, t.editor), i == null || i(t);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
return C(
|
|
162
|
+
y,
|
|
163
|
+
(t) => {
|
|
164
|
+
t && !t.isDestroyed ? l == null || l(t) : console.log("[useSlideEditor] ❌ Editor not ready or destroyed");
|
|
165
|
+
},
|
|
166
|
+
{ immediate: !0 }
|
|
167
|
+
), { editor: y, presets: b };
|
|
168
|
+
}, F = { class: "bs-viewport" }, G = /* @__PURE__ */ P({
|
|
169
|
+
__name: "SlideEditor",
|
|
170
|
+
props: {
|
|
171
|
+
bubbleMenuPreset: { type: [Boolean, Object], default: !0 },
|
|
172
|
+
className: {},
|
|
173
|
+
style: {},
|
|
174
|
+
content: {},
|
|
175
|
+
onChange: {},
|
|
176
|
+
extensions: {},
|
|
177
|
+
extensionKitOptions: {},
|
|
178
|
+
presetTemplates: {},
|
|
179
|
+
onEditorReady: {},
|
|
180
|
+
theme: {},
|
|
181
|
+
editorProps: {},
|
|
182
|
+
onUpdate: {},
|
|
183
|
+
editorOptions: {}
|
|
184
|
+
},
|
|
185
|
+
setup(r) {
|
|
186
|
+
const o = r, {
|
|
187
|
+
bubbleMenuPreset: u,
|
|
188
|
+
className: a,
|
|
189
|
+
style: e,
|
|
190
|
+
...p
|
|
191
|
+
} = o, { editor: l } = D(p), d = c(() => o.bubbleMenuPreset === !1 ? null : o.bubbleMenuPreset === !0 ? {} : o.bubbleMenuPreset);
|
|
192
|
+
return (s, i) => n(l) ? (h(), E("div", {
|
|
193
|
+
key: 0,
|
|
194
|
+
class: x(n(a)),
|
|
195
|
+
style: _(n(e))
|
|
196
|
+
}, [
|
|
197
|
+
B("div", F, [
|
|
198
|
+
M(n(j), { editor: n(l) }, null, 8, ["editor"]),
|
|
199
|
+
d.value ? (h(), N(z, w({
|
|
200
|
+
key: 0,
|
|
201
|
+
editor: n(l)
|
|
202
|
+
}, d.value), null, 16, ["editor"])) : k("", !0)
|
|
203
|
+
])
|
|
204
|
+
], 6)) : k("", !0);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
export {
|
|
208
|
+
z as BubbleMenuPreset,
|
|
209
|
+
G as SlideEditor,
|
|
210
|
+
D as useSlideEditor
|
|
211
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BubbleMenuPluginProps } from '../../../extension-bubble-menu/src';
|
|
2
|
+
|
|
3
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
4
|
+
export type BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>;
|
|
5
|
+
export declare const BubbleMenu: any;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FloatingMenuPluginProps } from '../../../extension-floating-menu/src';
|
|
2
|
+
|
|
3
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
4
|
+
export type FloatingMenuProps = Omit<Optional<FloatingMenuPluginProps, 'pluginKey'>, 'element'>;
|
|
5
|
+
export declare const FloatingMenu: any;
|
|
6
|
+
export {};
|
package/dist/menus.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("@blockslides/extension-bubble-menu"),t=require("vue"),f=require("@blockslides/extension-floating-menu"),g=require("./BubbleMenuPreset.vue_vue_type_script_setup_true_lang-Tkjxu33S.cjs"),v=t.defineComponent({name:"BubbleMenu",inheritAttrs:!1,props:{pluginKey:{type:[String,Object],default:"bubbleMenu"},editor:{type:Object,required:!0},updateDelay:{type:Number,default:void 0},resizeDelay:{type:Number,default:void 0},options:{type:Object,default:()=>({})},appendTo:{type:[Object,Function],default:void 0},shouldShow:{type:Function,default:null},getReferencedVirtualElement:{type:Function,default:void 0}},setup(o,{slots:u,attrs:d}){const l=t.ref(null);return t.onMounted(()=>{const{editor:e,options:n,pluginKey:a,resizeDelay:p,appendTo:s,shouldShow:i,getReferencedVirtualElement:c,updateDelay:b}=o,r=l.value;r&&(r.style.visibility="hidden",r.style.position="absolute",r.remove(),t.nextTick(()=>{e.registerPlugin(y.BubbleMenuPlugin({editor:e,element:r,options:n,pluginKey:a,resizeDelay:p,appendTo:s,shouldShow:i,getReferencedVirtualElement:c,updateDelay:b}))}))}),t.onBeforeUnmount(()=>{const{pluginKey:e,editor:n}=o;n.unregisterPlugin(e)}),()=>{var e;return t.h("div",{ref:l,...d},(e=u.default)==null?void 0:e.call(u))}}}),M=t.defineComponent({name:"FloatingMenu",inheritAttrs:!1,props:{pluginKey:{type:null,default:"floatingMenu"},editor:{type:Object,required:!0},options:{type:Object,default:()=>({})},appendTo:{type:[Object,Function],default:void 0},shouldShow:{type:Function,default:null}},setup(o,{slots:u,attrs:d}){const l=t.ref(null);return t.onMounted(()=>{const{pluginKey:e,editor:n,options:a,appendTo:p,shouldShow:s}=o,i=l.value;i&&(i.style.visibility="hidden",i.style.position="absolute",i.remove(),n.registerPlugin(f.FloatingMenuPlugin({pluginKey:e,editor:n,element:i,options:a,appendTo:p,shouldShow:s})))}),t.onBeforeUnmount(()=>{const{pluginKey:e,editor:n}=o;n.unregisterPlugin(e)}),()=>{var e;return t.h("div",{ref:l,...d},(e=u.default)==null?void 0:e.call(u))}}});exports.BubbleMenuPreset=g._sfc_main;exports.BubbleMenu=v;exports.FloatingMenu=M;
|
package/dist/menus.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './menus/index'
|
package/dist/menus.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { BubbleMenuPlugin as h } from "@blockslides/extension-bubble-menu";
|
|
2
|
+
import { defineComponent as f, ref as y, onMounted as s, nextTick as v, onBeforeUnmount as c, h as b } from "vue";
|
|
3
|
+
import { FloatingMenuPlugin as M } from "@blockslides/extension-floating-menu";
|
|
4
|
+
import { _ as T } from "./BubbleMenuPreset.vue_vue_type_script_setup_true_lang-B5Ihs2fW.js";
|
|
5
|
+
const P = f({
|
|
6
|
+
name: "BubbleMenu",
|
|
7
|
+
inheritAttrs: !1,
|
|
8
|
+
props: {
|
|
9
|
+
pluginKey: {
|
|
10
|
+
type: [String, Object],
|
|
11
|
+
default: "bubbleMenu"
|
|
12
|
+
},
|
|
13
|
+
editor: {
|
|
14
|
+
type: Object,
|
|
15
|
+
required: !0
|
|
16
|
+
},
|
|
17
|
+
updateDelay: {
|
|
18
|
+
type: Number,
|
|
19
|
+
default: void 0
|
|
20
|
+
},
|
|
21
|
+
resizeDelay: {
|
|
22
|
+
type: Number,
|
|
23
|
+
default: void 0
|
|
24
|
+
},
|
|
25
|
+
options: {
|
|
26
|
+
type: Object,
|
|
27
|
+
default: () => ({})
|
|
28
|
+
},
|
|
29
|
+
appendTo: {
|
|
30
|
+
type: [Object, Function],
|
|
31
|
+
default: void 0
|
|
32
|
+
},
|
|
33
|
+
shouldShow: {
|
|
34
|
+
type: Function,
|
|
35
|
+
default: null
|
|
36
|
+
},
|
|
37
|
+
getReferencedVirtualElement: {
|
|
38
|
+
type: Function,
|
|
39
|
+
default: void 0
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
setup(i, { slots: n, attrs: r }) {
|
|
43
|
+
const o = y(null);
|
|
44
|
+
return s(() => {
|
|
45
|
+
const {
|
|
46
|
+
editor: e,
|
|
47
|
+
options: t,
|
|
48
|
+
pluginKey: d,
|
|
49
|
+
resizeDelay: p,
|
|
50
|
+
appendTo: a,
|
|
51
|
+
shouldShow: u,
|
|
52
|
+
getReferencedVirtualElement: g,
|
|
53
|
+
updateDelay: m
|
|
54
|
+
} = i, l = o.value;
|
|
55
|
+
l && (l.style.visibility = "hidden", l.style.position = "absolute", l.remove(), v(() => {
|
|
56
|
+
e.registerPlugin(
|
|
57
|
+
h({
|
|
58
|
+
editor: e,
|
|
59
|
+
element: l,
|
|
60
|
+
options: t,
|
|
61
|
+
pluginKey: d,
|
|
62
|
+
resizeDelay: p,
|
|
63
|
+
appendTo: a,
|
|
64
|
+
shouldShow: u,
|
|
65
|
+
getReferencedVirtualElement: g,
|
|
66
|
+
updateDelay: m
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
}));
|
|
70
|
+
}), c(() => {
|
|
71
|
+
const { pluginKey: e, editor: t } = i;
|
|
72
|
+
t.unregisterPlugin(e);
|
|
73
|
+
}), () => {
|
|
74
|
+
var e;
|
|
75
|
+
return b("div", { ref: o, ...r }, (e = n.default) == null ? void 0 : e.call(n));
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}), K = f({
|
|
79
|
+
name: "FloatingMenu",
|
|
80
|
+
inheritAttrs: !1,
|
|
81
|
+
props: {
|
|
82
|
+
pluginKey: {
|
|
83
|
+
// TODO: TypeScript breaks
|
|
84
|
+
// type: [String, Object as PropType<Exclude<FloatingMenuPluginProps['pluginKey'], string>>],
|
|
85
|
+
type: null,
|
|
86
|
+
default: "floatingMenu"
|
|
87
|
+
},
|
|
88
|
+
editor: {
|
|
89
|
+
type: Object,
|
|
90
|
+
required: !0
|
|
91
|
+
},
|
|
92
|
+
options: {
|
|
93
|
+
type: Object,
|
|
94
|
+
default: () => ({})
|
|
95
|
+
},
|
|
96
|
+
appendTo: {
|
|
97
|
+
type: [Object, Function],
|
|
98
|
+
default: void 0
|
|
99
|
+
},
|
|
100
|
+
shouldShow: {
|
|
101
|
+
type: Function,
|
|
102
|
+
default: null
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
setup(i, { slots: n, attrs: r }) {
|
|
106
|
+
const o = y(null);
|
|
107
|
+
return s(() => {
|
|
108
|
+
const { pluginKey: e, editor: t, options: d, appendTo: p, shouldShow: a } = i, u = o.value;
|
|
109
|
+
u && (u.style.visibility = "hidden", u.style.position = "absolute", u.remove(), t.registerPlugin(
|
|
110
|
+
M({
|
|
111
|
+
pluginKey: e,
|
|
112
|
+
editor: t,
|
|
113
|
+
element: u,
|
|
114
|
+
options: d,
|
|
115
|
+
appendTo: p,
|
|
116
|
+
shouldShow: a
|
|
117
|
+
})
|
|
118
|
+
));
|
|
119
|
+
}), c(() => {
|
|
120
|
+
const { pluginKey: e, editor: t } = i;
|
|
121
|
+
t.unregisterPlugin(e);
|
|
122
|
+
}), () => {
|
|
123
|
+
var e;
|
|
124
|
+
return b("div", { ref: o, ...r }, (e = n.default) == null ? void 0 : e.call(n));
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
export {
|
|
129
|
+
P as BubbleMenu,
|
|
130
|
+
T as BubbleMenuPreset,
|
|
131
|
+
K as FloatingMenu
|
|
132
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { templatesV1 } from '../../ai-context/src';
|
|
2
|
+
import { AnyExtension, Editor, JSONContent, EditorOptions } from '../../core/src';
|
|
3
|
+
import { ExtensionKitOptions } from '../../extension-kit/src';
|
|
4
|
+
|
|
5
|
+
type PresetTemplates = ReturnType<typeof templatesV1.listPresetTemplates>;
|
|
6
|
+
export interface UseSlideEditorProps {
|
|
7
|
+
/**
|
|
8
|
+
* Initial content for the editor. If omitted, a single preset slide is used.
|
|
9
|
+
*/
|
|
10
|
+
content?: EditorOptions['content'];
|
|
11
|
+
/**
|
|
12
|
+
* Called on every update with the current JSON document.
|
|
13
|
+
*/
|
|
14
|
+
onChange?: (doc: JSONContent, editor: Editor) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Additional extensions to append after the ExtensionKit bundle.
|
|
17
|
+
*/
|
|
18
|
+
extensions?: AnyExtension[];
|
|
19
|
+
/**
|
|
20
|
+
* Customize or disable pieces of ExtensionKit (e.g., bubbleMenu: false).
|
|
21
|
+
*/
|
|
22
|
+
extensionKitOptions?: ExtensionKitOptions;
|
|
23
|
+
/**
|
|
24
|
+
* Optional preset list to power the add-slide button.
|
|
25
|
+
*/
|
|
26
|
+
presetTemplates?: PresetTemplates;
|
|
27
|
+
/**
|
|
28
|
+
* Called once when an editor instance is ready.
|
|
29
|
+
*/
|
|
30
|
+
onEditorReady?: (editor: Editor) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Editor theme for UI styling
|
|
33
|
+
*/
|
|
34
|
+
theme?: EditorOptions['theme'];
|
|
35
|
+
/**
|
|
36
|
+
* The editor's props
|
|
37
|
+
*/
|
|
38
|
+
editorProps?: EditorOptions['editorProps'];
|
|
39
|
+
/**
|
|
40
|
+
* Called on every update
|
|
41
|
+
*/
|
|
42
|
+
onUpdate?: EditorOptions['onUpdate'];
|
|
43
|
+
/**
|
|
44
|
+
* Additional editor options to pass through to the core editor.
|
|
45
|
+
* This allows passing any EditorOptions without Vue auto-initializing them.
|
|
46
|
+
*/
|
|
47
|
+
editorOptions?: Partial<EditorOptions>;
|
|
48
|
+
}
|
|
49
|
+
export declare const useSlideEditor: (props?: UseSlideEditorProps) => {
|
|
50
|
+
editor: import('vue').ShallowRef<import('../../vue-3/src').Editor | undefined, import('../../vue-3/src').Editor | undefined>;
|
|
51
|
+
presets: import('vue').ComputedRef<templatesV1.PresetTemplate[]>;
|
|
52
|
+
};
|
|
53
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blockslides/vue-3-prebuilts",
|
|
3
|
+
"description": "Pre-built Vue 3 components for blockslides - includes SlideEditor, useSlideEditor, and menu components",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"homepage": "https://github.com/keivanmojmali/blockslides",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"blockslides",
|
|
8
|
+
"blockslides vue components",
|
|
9
|
+
"editor",
|
|
10
|
+
"wysiwyg",
|
|
11
|
+
"slide editor",
|
|
12
|
+
"prebuilt components"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": {
|
|
18
|
+
"import": "./dist/index.d.ts",
|
|
19
|
+
"require": "./dist/index.d.cts"
|
|
20
|
+
},
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./menus": {
|
|
25
|
+
"types": {
|
|
26
|
+
"import": "./dist/menus/index.d.ts",
|
|
27
|
+
"require": "./dist/menus/index.d.cts"
|
|
28
|
+
},
|
|
29
|
+
"import": "./dist/menus/index.js",
|
|
30
|
+
"require": "./dist/menus/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "dist/index.cjs",
|
|
34
|
+
"module": "dist/index.js",
|
|
35
|
+
"types": "dist/index.d.ts",
|
|
36
|
+
"type": "module",
|
|
37
|
+
"files": [
|
|
38
|
+
"src",
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@blockslides/vue-3": "^0.4.1",
|
|
43
|
+
"@blockslides/extension-kit": "^0.7.3",
|
|
44
|
+
"@blockslides/ai-context": "^0.3.1",
|
|
45
|
+
"@blockslides/extension-bubble-menu-preset": "^0.3.1",
|
|
46
|
+
"@blockslides/extension-floating-menu": "^0.1.1",
|
|
47
|
+
"@blockslides/extension-bubble-menu": "^0.1.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@floating-ui/dom": "^1.0.0",
|
|
51
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
52
|
+
"vite": "^5.0.0",
|
|
53
|
+
"vite-plugin-dts": "^3.0.0",
|
|
54
|
+
"vue": "^3.5.13",
|
|
55
|
+
"@blockslides/core": "^0.3.3",
|
|
56
|
+
"@blockslides/pm": "^0.1.1"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@floating-ui/dom": "^1.0.0",
|
|
60
|
+
"vue": "^3.0.0",
|
|
61
|
+
"@blockslides/core": "^0.3.3",
|
|
62
|
+
"@blockslides/pm": "^0.1.1"
|
|
63
|
+
},
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "https://github.com/keivanmojmali/blockslides",
|
|
67
|
+
"directory": "packages/vue-3-prebuilts"
|
|
68
|
+
},
|
|
69
|
+
"sideEffects": false,
|
|
70
|
+
"author": "keivanmojmali",
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "vite build",
|
|
73
|
+
"lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, watch, onMounted } from 'vue'
|
|
3
|
+
import { EditorContent } from '@blockslides/vue-3'
|
|
4
|
+
import BubbleMenuPreset from './menus/BubbleMenuPreset.vue'
|
|
5
|
+
import { useSlideEditor, type UseSlideEditorProps } from './useSlideEditor'
|
|
6
|
+
import type { Editor } from '@blockslides/core'
|
|
7
|
+
|
|
8
|
+
export interface BubbleMenuPresetProps {
|
|
9
|
+
editor: Editor
|
|
10
|
+
[key: string]: any
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SlideEditorProps extends UseSlideEditorProps {
|
|
14
|
+
/**
|
|
15
|
+
* Toggle or customize the built-in BubbleMenuPreset.
|
|
16
|
+
* - true (default): render with defaults
|
|
17
|
+
* - false: disable entirely
|
|
18
|
+
* - object: pass through to BubbleMenuPreset
|
|
19
|
+
*/
|
|
20
|
+
bubbleMenuPreset?: boolean | BubbleMenuPresetProps
|
|
21
|
+
className?: string
|
|
22
|
+
style?: any
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<SlideEditorProps>(), {
|
|
26
|
+
bubbleMenuPreset: true,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
bubbleMenuPreset,
|
|
31
|
+
className,
|
|
32
|
+
style,
|
|
33
|
+
...hookProps
|
|
34
|
+
} = props
|
|
35
|
+
|
|
36
|
+
const { editor } = useSlideEditor(hookProps)
|
|
37
|
+
|
|
38
|
+
const bubbleMenuProps = computed(() => {
|
|
39
|
+
if (props.bubbleMenuPreset === false) return null
|
|
40
|
+
if (props.bubbleMenuPreset === true) return {}
|
|
41
|
+
return props.bubbleMenuPreset
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div v-if="editor" :class="className" :style="style">
|
|
48
|
+
<div class="bs-viewport">
|
|
49
|
+
<EditorContent :editor="editor" />
|
|
50
|
+
<BubbleMenuPreset v-if="bubbleMenuProps" :editor="editor" v-bind="bubbleMenuProps" />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as SlideEditor } from './SlideEditor.vue'
|
|
2
|
+
export * from './useSlideEditor.js'
|
|
3
|
+
export { default as BubbleMenuPreset } from './menus/BubbleMenuPreset.vue'
|
|
4
|
+
|
|
5
|
+
// Re-export everything from @blockslides/vue-3 core
|
|
6
|
+
export * from '@blockslides/vue-3'
|
|
7
|
+
|
|
8
|
+
// Re-export everything from @blockslides/core for convenience
|
|
9
|
+
export * from '@blockslides/core'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { BubbleMenuPluginProps } from '@blockslides/extension-bubble-menu'
|
|
2
|
+
import { BubbleMenuPlugin } from '@blockslides/extension-bubble-menu'
|
|
3
|
+
import type { PropType } from 'vue'
|
|
4
|
+
import { defineComponent, h, nextTick, 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 BubbleMenuProps = Omit<Optional<BubbleMenuPluginProps, 'pluginKey'>, 'element'>
|
|
9
|
+
|
|
10
|
+
export const BubbleMenu = defineComponent({
|
|
11
|
+
name: 'BubbleMenu',
|
|
12
|
+
|
|
13
|
+
inheritAttrs: false,
|
|
14
|
+
|
|
15
|
+
props: {
|
|
16
|
+
pluginKey: {
|
|
17
|
+
type: [String, Object] as PropType<BubbleMenuPluginProps['pluginKey']>,
|
|
18
|
+
default: 'bubbleMenu',
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
editor: {
|
|
22
|
+
type: Object as PropType<BubbleMenuPluginProps['editor']>,
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
updateDelay: {
|
|
27
|
+
type: Number as PropType<BubbleMenuPluginProps['updateDelay']>,
|
|
28
|
+
default: undefined,
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
resizeDelay: {
|
|
32
|
+
type: Number as PropType<BubbleMenuPluginProps['resizeDelay']>,
|
|
33
|
+
default: undefined,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
options: {
|
|
37
|
+
type: Object as PropType<BubbleMenuPluginProps['options']>,
|
|
38
|
+
default: () => ({}),
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
appendTo: {
|
|
42
|
+
type: [Object, Function] as PropType<BubbleMenuPluginProps['appendTo']>,
|
|
43
|
+
default: undefined,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
shouldShow: {
|
|
47
|
+
type: Function as PropType<Exclude<Required<BubbleMenuPluginProps>['shouldShow'], null>>,
|
|
48
|
+
default: null,
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
getReferencedVirtualElement: {
|
|
52
|
+
type: Function as PropType<Exclude<Required<BubbleMenuPluginProps>['getReferencedVirtualElement'], null>>,
|
|
53
|
+
default: undefined,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
setup(props, { slots, attrs }) {
|
|
58
|
+
const root = ref<HTMLElement | null>(null)
|
|
59
|
+
|
|
60
|
+
onMounted(() => {
|
|
61
|
+
const {
|
|
62
|
+
editor,
|
|
63
|
+
options,
|
|
64
|
+
pluginKey,
|
|
65
|
+
resizeDelay,
|
|
66
|
+
appendTo,
|
|
67
|
+
shouldShow,
|
|
68
|
+
getReferencedVirtualElement,
|
|
69
|
+
updateDelay,
|
|
70
|
+
} = props
|
|
71
|
+
|
|
72
|
+
const el = root.value
|
|
73
|
+
|
|
74
|
+
if (!el) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
el.style.visibility = 'hidden'
|
|
79
|
+
el.style.position = 'absolute'
|
|
80
|
+
|
|
81
|
+
// Remove element from DOM; plugin will re-parent it when shown
|
|
82
|
+
el.remove()
|
|
83
|
+
|
|
84
|
+
nextTick(() => {
|
|
85
|
+
editor.registerPlugin(
|
|
86
|
+
BubbleMenuPlugin({
|
|
87
|
+
editor,
|
|
88
|
+
element: el,
|
|
89
|
+
options,
|
|
90
|
+
pluginKey,
|
|
91
|
+
resizeDelay,
|
|
92
|
+
appendTo,
|
|
93
|
+
shouldShow,
|
|
94
|
+
getReferencedVirtualElement,
|
|
95
|
+
updateDelay,
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
onBeforeUnmount(() => {
|
|
102
|
+
const { pluginKey, editor } = props
|
|
103
|
+
|
|
104
|
+
editor.unregisterPlugin(pluginKey)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Vue owns this element; attrs are applied reactively by Vue
|
|
108
|
+
// Plugin re-parents it when showing the menu
|
|
109
|
+
return () => h('div', { ref: root, ...attrs }, slots.default?.())
|
|
110
|
+
},
|
|
111
|
+
}) as any
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
3
|
+
import { BubbleMenuPlugin, type BubbleMenuPluginProps } from '@blockslides/extension-bubble-menu'
|
|
4
|
+
import { NodeSelection } from '@blockslides/pm/state'
|
|
5
|
+
import {
|
|
6
|
+
type BubbleMenuPresetOptions,
|
|
7
|
+
buildMenuElement,
|
|
8
|
+
DEFAULT_ITEMS,
|
|
9
|
+
DEFAULT_COLOR_PALETTE,
|
|
10
|
+
DEFAULT_HIGHLIGHT_PALETTE,
|
|
11
|
+
DEFAULT_FONTS,
|
|
12
|
+
DEFAULT_FONT_SIZES,
|
|
13
|
+
DEFAULT_ALIGNMENTS,
|
|
14
|
+
} from '@blockslides/extension-bubble-menu-preset'
|
|
15
|
+
import { isTextSelection, type Editor } from '@blockslides/core'
|
|
16
|
+
|
|
17
|
+
export interface BubbleMenuPresetProps extends Omit<BubbleMenuPresetOptions, 'element' | 'pluginKey'> {
|
|
18
|
+
editor: Editor
|
|
19
|
+
updateDelay?: number
|
|
20
|
+
resizeDelay?: number
|
|
21
|
+
appendTo?: HTMLElement | (() => HTMLElement)
|
|
22
|
+
shouldShow?: BubbleMenuPluginProps['shouldShow']
|
|
23
|
+
getReferencedVirtualElement?: () => any
|
|
24
|
+
options?: any
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const props = withDefaults(defineProps<BubbleMenuPresetProps>(), {
|
|
28
|
+
injectStyles: true,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const pluginKey = 'bubbleMenuPreset'
|
|
32
|
+
const menuEl = ref<HTMLElement | null>(null)
|
|
33
|
+
let cleanup: (() => void) | undefined
|
|
34
|
+
|
|
35
|
+
const setupBubbleMenu = () => {
|
|
36
|
+
const attachToEditor = props.editor
|
|
37
|
+
|
|
38
|
+
if (!attachToEditor || (attachToEditor as any).isDestroyed) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Cleanup previous instance if any
|
|
43
|
+
if (cleanup) {
|
|
44
|
+
cleanup()
|
|
45
|
+
cleanup = undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { element, cleanup: elementCleanup } = buildMenuElement(attachToEditor, {
|
|
49
|
+
items: props.items ?? DEFAULT_ITEMS,
|
|
50
|
+
className: props.className ?? '',
|
|
51
|
+
injectStyles: props.injectStyles !== false,
|
|
52
|
+
textColors: props.textColors ?? DEFAULT_COLOR_PALETTE,
|
|
53
|
+
highlightColors: props.highlightColors ?? DEFAULT_HIGHLIGHT_PALETTE,
|
|
54
|
+
fonts: props.fonts ?? DEFAULT_FONTS,
|
|
55
|
+
fontSizes: props.fontSizes ?? DEFAULT_FONT_SIZES,
|
|
56
|
+
alignments: props.alignments ?? DEFAULT_ALIGNMENTS,
|
|
57
|
+
onTextAction: props.onTextAction,
|
|
58
|
+
onImageReplace: props.onImageReplace,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
menuEl.value = element
|
|
62
|
+
cleanup = elementCleanup
|
|
63
|
+
|
|
64
|
+
const defaultShouldShow: Exclude<BubbleMenuPluginProps['shouldShow'], null> = ({ state, editor }) => {
|
|
65
|
+
const sel = state.selection
|
|
66
|
+
const imageSelection =
|
|
67
|
+
(sel instanceof NodeSelection && ['image', 'imageBlock'].includes((sel as any).node?.type?.name)) ||
|
|
68
|
+
editor.isActive('image') ||
|
|
69
|
+
editor.isActive('imageBlock')
|
|
70
|
+
|
|
71
|
+
if (imageSelection) return true
|
|
72
|
+
if (isTextSelection(sel) && !sel.empty && !imageSelection) return true
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const plugin = BubbleMenuPlugin({
|
|
77
|
+
editor: attachToEditor,
|
|
78
|
+
element,
|
|
79
|
+
updateDelay: props.updateDelay,
|
|
80
|
+
resizeDelay: props.resizeDelay,
|
|
81
|
+
appendTo: props.appendTo,
|
|
82
|
+
pluginKey,
|
|
83
|
+
shouldShow: props.shouldShow ?? defaultShouldShow,
|
|
84
|
+
getReferencedVirtualElement: props.getReferencedVirtualElement,
|
|
85
|
+
options: props.options,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
attachToEditor.registerPlugin(plugin)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onMounted(() => {
|
|
92
|
+
setupBubbleMenu()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Watch for prop changes and rebuild
|
|
96
|
+
watch(
|
|
97
|
+
() => [
|
|
98
|
+
props.editor,
|
|
99
|
+
props.updateDelay,
|
|
100
|
+
props.resizeDelay,
|
|
101
|
+
props.appendTo,
|
|
102
|
+
props.shouldShow,
|
|
103
|
+
props.getReferencedVirtualElement,
|
|
104
|
+
props.options,
|
|
105
|
+
props.items,
|
|
106
|
+
props.className,
|
|
107
|
+
props.injectStyles,
|
|
108
|
+
props.textColors,
|
|
109
|
+
props.highlightColors,
|
|
110
|
+
props.fonts,
|
|
111
|
+
props.fontSizes,
|
|
112
|
+
props.alignments,
|
|
113
|
+
props.onTextAction,
|
|
114
|
+
props.onImageReplace,
|
|
115
|
+
],
|
|
116
|
+
() => {
|
|
117
|
+
setupBubbleMenu()
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
onBeforeUnmount(() => {
|
|
122
|
+
if (props.editor) {
|
|
123
|
+
props.editor.unregisterPlugin(pluginKey)
|
|
124
|
+
}
|
|
125
|
+
if (cleanup) {
|
|
126
|
+
cleanup()
|
|
127
|
+
}
|
|
128
|
+
if (menuEl.value?.parentNode) {
|
|
129
|
+
menuEl.value.parentNode.removeChild(menuEl.value)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<template>
|
|
135
|
+
<!-- Vue doesn't need portal here as buildMenuElement handles DOM placement -->
|
|
136
|
+
<div v-if="menuEl" style="display: none"></div>
|
|
137
|
+
</template>
|
|
@@ -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
|
|
@@ -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 '@blockslides/vue-3'
|
|
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
|
+
}
|