@blockslides/extension-add-slide-button 0.1.0 → 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/dist/index.cjs +275 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +275 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/add-slide-button.ts +327 -31
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Extension } from '@blockslides/core';
|
|
2
2
|
import { EditorView } from 'prosemirror-view';
|
|
3
3
|
|
|
4
|
+
interface PresetTemplateOption {
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
build: () => any;
|
|
9
|
+
}
|
|
4
10
|
interface AddSlideButtonOptions {
|
|
5
11
|
/**
|
|
6
12
|
* Whether to inject CSS styles for the button.
|
|
@@ -27,6 +33,30 @@ interface AddSlideButtonOptions {
|
|
|
27
33
|
* @example '+' | 'Add Slide' | '➕' | '<svg>...</svg>'
|
|
28
34
|
*/
|
|
29
35
|
content: string;
|
|
36
|
+
/**
|
|
37
|
+
* Optional template chooser that renders a second button and modal.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
showPresets?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Background color for the preset modal content area.
|
|
43
|
+
* @default '#ffffff'
|
|
44
|
+
*/
|
|
45
|
+
presetBackground?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Text/icon color for the preset modal content area.
|
|
48
|
+
* @default '#000000'
|
|
49
|
+
*/
|
|
50
|
+
presetForeground?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Presets to show in the modal when showPresets is true.
|
|
53
|
+
*/
|
|
54
|
+
presets?: PresetTemplateOption[];
|
|
55
|
+
/**
|
|
56
|
+
* Content for the template button (emoji or HTML).
|
|
57
|
+
* @default '✨'
|
|
58
|
+
*/
|
|
59
|
+
templateButtonContent?: string;
|
|
30
60
|
/**
|
|
31
61
|
* Custom click handler for the button.
|
|
32
62
|
* If not provided, will insert a new slide at the clicked position.
|
|
@@ -40,4 +70,4 @@ interface AddSlideButtonOptions {
|
|
|
40
70
|
}
|
|
41
71
|
declare const AddSlideButton: Extension<AddSlideButtonOptions, any>;
|
|
42
72
|
|
|
43
|
-
export { AddSlideButton, type AddSlideButtonOptions, AddSlideButton as default };
|
|
73
|
+
export { AddSlideButton, type AddSlideButtonOptions, type PresetTemplateOption, AddSlideButton as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Extension } from '@blockslides/core';
|
|
2
2
|
import { EditorView } from 'prosemirror-view';
|
|
3
3
|
|
|
4
|
+
interface PresetTemplateOption {
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
build: () => any;
|
|
9
|
+
}
|
|
4
10
|
interface AddSlideButtonOptions {
|
|
5
11
|
/**
|
|
6
12
|
* Whether to inject CSS styles for the button.
|
|
@@ -27,6 +33,30 @@ interface AddSlideButtonOptions {
|
|
|
27
33
|
* @example '+' | 'Add Slide' | '➕' | '<svg>...</svg>'
|
|
28
34
|
*/
|
|
29
35
|
content: string;
|
|
36
|
+
/**
|
|
37
|
+
* Optional template chooser that renders a second button and modal.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
showPresets?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Background color for the preset modal content area.
|
|
43
|
+
* @default '#ffffff'
|
|
44
|
+
*/
|
|
45
|
+
presetBackground?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Text/icon color for the preset modal content area.
|
|
48
|
+
* @default '#000000'
|
|
49
|
+
*/
|
|
50
|
+
presetForeground?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Presets to show in the modal when showPresets is true.
|
|
53
|
+
*/
|
|
54
|
+
presets?: PresetTemplateOption[];
|
|
55
|
+
/**
|
|
56
|
+
* Content for the template button (emoji or HTML).
|
|
57
|
+
* @default '✨'
|
|
58
|
+
*/
|
|
59
|
+
templateButtonContent?: string;
|
|
30
60
|
/**
|
|
31
61
|
* Custom click handler for the button.
|
|
32
62
|
* If not provided, will insert a new slide at the clicked position.
|
|
@@ -40,4 +70,4 @@ interface AddSlideButtonOptions {
|
|
|
40
70
|
}
|
|
41
71
|
declare const AddSlideButton: Extension<AddSlideButtonOptions, any>;
|
|
42
72
|
|
|
43
|
-
export { AddSlideButton, type AddSlideButtonOptions, AddSlideButton as default };
|
|
73
|
+
export { AddSlideButton, type AddSlideButtonOptions, type PresetTemplateOption, AddSlideButton as default };
|
package/dist/index.js
CHANGED
|
@@ -3070,6 +3070,7 @@ var PluginKey = class {
|
|
|
3070
3070
|
// src/add-slide-button.ts
|
|
3071
3071
|
var SlideNodeView = class {
|
|
3072
3072
|
constructor(node, view, getPos, options) {
|
|
3073
|
+
var _a;
|
|
3073
3074
|
this.node = node;
|
|
3074
3075
|
this.view = view;
|
|
3075
3076
|
this.getPos = getPos;
|
|
@@ -3095,21 +3096,33 @@ var SlideNodeView = class {
|
|
|
3095
3096
|
this.contentDOM.className = "slide";
|
|
3096
3097
|
this.contentDOM.setAttribute("data-node-type", "slide");
|
|
3097
3098
|
}
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3099
|
+
const createButton = (html) => {
|
|
3100
|
+
const btn = document.createElement("button");
|
|
3101
|
+
btn.className = "add-slide-button";
|
|
3102
|
+
btn.innerHTML = html;
|
|
3103
|
+
btn.setAttribute("type", "button");
|
|
3104
|
+
btn.contentEditable = "false";
|
|
3105
|
+
if (options.buttonStyle) {
|
|
3106
|
+
Object.entries(options.buttonStyle).forEach(([key, value]) => {
|
|
3107
|
+
const camelKey = key.replace(
|
|
3108
|
+
/-([a-z])/g,
|
|
3109
|
+
(_, letter) => letter.toUpperCase()
|
|
3110
|
+
);
|
|
3111
|
+
btn.style[camelKey] = value;
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
return btn;
|
|
3115
|
+
};
|
|
3116
|
+
const insertEmptySlide = (pos) => {
|
|
3117
|
+
const schema = this.view.state.schema;
|
|
3118
|
+
const slideType = schema.nodes.slide;
|
|
3119
|
+
const paragraphType = schema.nodes.paragraph;
|
|
3120
|
+
const slideContent = paragraphType.create();
|
|
3121
|
+
const slide = slideType.create(null, slideContent);
|
|
3122
|
+
const tr = this.view.state.tr.insert(pos + this.node.nodeSize, slide);
|
|
3123
|
+
this.view.dispatch(tr);
|
|
3124
|
+
};
|
|
3125
|
+
const handlePlusClick = (event) => {
|
|
3113
3126
|
event.preventDefault();
|
|
3114
3127
|
event.stopPropagation();
|
|
3115
3128
|
const pos = this.getPos();
|
|
@@ -3126,17 +3139,117 @@ var SlideNodeView = class {
|
|
|
3126
3139
|
event
|
|
3127
3140
|
});
|
|
3128
3141
|
} else {
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
this.view.dispatch(tr);
|
|
3142
|
+
insertEmptySlide(pos);
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
const openPresetModal = (pos) => {
|
|
3146
|
+
if (this.presetModal) {
|
|
3147
|
+
this.presetModal.remove();
|
|
3136
3148
|
}
|
|
3149
|
+
const overlay = document.createElement("div");
|
|
3150
|
+
overlay.className = "add-slide-preset-modal";
|
|
3151
|
+
const dialog = document.createElement("div");
|
|
3152
|
+
dialog.className = "add-slide-preset-dialog";
|
|
3153
|
+
if (options.presetBackground) {
|
|
3154
|
+
dialog.style.setProperty("--add-slide-preset-bg", options.presetBackground);
|
|
3155
|
+
}
|
|
3156
|
+
if (options.presetForeground) {
|
|
3157
|
+
dialog.style.setProperty("--add-slide-preset-fg", options.presetForeground);
|
|
3158
|
+
}
|
|
3159
|
+
const search = document.createElement("input");
|
|
3160
|
+
search.className = "add-slide-preset-search";
|
|
3161
|
+
search.type = "search";
|
|
3162
|
+
search.placeholder = "Choose a template";
|
|
3163
|
+
dialog.appendChild(search);
|
|
3164
|
+
const list = document.createElement("div");
|
|
3165
|
+
list.className = "add-slide-preset-list";
|
|
3166
|
+
const items = [];
|
|
3167
|
+
(options.presets || []).forEach((preset) => {
|
|
3168
|
+
var _a2;
|
|
3169
|
+
const item = document.createElement("button");
|
|
3170
|
+
item.className = "add-slide-preset-item";
|
|
3171
|
+
item.setAttribute("type", "button");
|
|
3172
|
+
item.innerHTML = `
|
|
3173
|
+
<span class="add-slide-preset-icon">${(_a2 = preset.icon) != null ? _a2 : ""}</span>
|
|
3174
|
+
<span class="add-slide-preset-label">${preset.label}</span>
|
|
3175
|
+
`;
|
|
3176
|
+
item.onclick = (event) => {
|
|
3177
|
+
event.preventDefault();
|
|
3178
|
+
event.stopPropagation();
|
|
3179
|
+
try {
|
|
3180
|
+
const nodeJSON = preset.build();
|
|
3181
|
+
const newSlide = this.view.state.schema.nodeFromJSON(nodeJSON);
|
|
3182
|
+
const tr = this.view.state.tr.insert(
|
|
3183
|
+
pos + this.node.nodeSize,
|
|
3184
|
+
newSlide
|
|
3185
|
+
);
|
|
3186
|
+
this.view.dispatch(tr);
|
|
3187
|
+
} catch (err) {
|
|
3188
|
+
console.error("Failed to insert preset slide", err);
|
|
3189
|
+
} finally {
|
|
3190
|
+
overlay.remove();
|
|
3191
|
+
this.presetModal = null;
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
list.appendChild(item);
|
|
3195
|
+
items.push(item);
|
|
3196
|
+
});
|
|
3197
|
+
search.oninput = () => {
|
|
3198
|
+
const term = search.value.toLowerCase();
|
|
3199
|
+
items.forEach((item) => {
|
|
3200
|
+
var _a2, _b;
|
|
3201
|
+
const label = (_b = (_a2 = item.querySelector(".add-slide-preset-label")) == null ? void 0 : _a2.textContent) == null ? void 0 : _b.toLowerCase();
|
|
3202
|
+
item.style.display = !term || (label == null ? void 0 : label.includes(term)) ? "" : "none";
|
|
3203
|
+
});
|
|
3204
|
+
};
|
|
3205
|
+
dialog.appendChild(list);
|
|
3206
|
+
const close2 = () => {
|
|
3207
|
+
overlay.remove();
|
|
3208
|
+
this.presetModal = null;
|
|
3209
|
+
};
|
|
3210
|
+
overlay.onclick = (e) => {
|
|
3211
|
+
if (e.target === overlay) {
|
|
3212
|
+
close2();
|
|
3213
|
+
}
|
|
3214
|
+
};
|
|
3215
|
+
document.addEventListener(
|
|
3216
|
+
"keydown",
|
|
3217
|
+
(e) => {
|
|
3218
|
+
if (e.key === "Escape") {
|
|
3219
|
+
close2();
|
|
3220
|
+
}
|
|
3221
|
+
},
|
|
3222
|
+
{ once: true }
|
|
3223
|
+
);
|
|
3224
|
+
overlay.appendChild(dialog);
|
|
3225
|
+
document.body.appendChild(overlay);
|
|
3226
|
+
this.presetModal = overlay;
|
|
3137
3227
|
};
|
|
3138
|
-
|
|
3139
|
-
|
|
3228
|
+
const plusButton = createButton(options.content);
|
|
3229
|
+
plusButton.onclick = handlePlusClick;
|
|
3230
|
+
this.button = plusButton;
|
|
3231
|
+
if (options.showPresets) {
|
|
3232
|
+
const templateButton = createButton(
|
|
3233
|
+
(_a = options.templateButtonContent) != null ? _a : "\u2728"
|
|
3234
|
+
);
|
|
3235
|
+
templateButton.onclick = (event) => {
|
|
3236
|
+
event.preventDefault();
|
|
3237
|
+
event.stopPropagation();
|
|
3238
|
+
const pos = this.getPos();
|
|
3239
|
+
if (pos === void 0) return;
|
|
3240
|
+
openPresetModal(pos);
|
|
3241
|
+
};
|
|
3242
|
+
const group = document.createElement("div");
|
|
3243
|
+
group.className = "add-slide-button-group";
|
|
3244
|
+
group.appendChild(plusButton);
|
|
3245
|
+
group.appendChild(templateButton);
|
|
3246
|
+
this.dom.appendChild(this.contentDOM);
|
|
3247
|
+
this.dom.appendChild(group);
|
|
3248
|
+
} else {
|
|
3249
|
+
this.button = plusButton;
|
|
3250
|
+
this.dom.appendChild(this.contentDOM);
|
|
3251
|
+
this.dom.appendChild(this.button);
|
|
3252
|
+
}
|
|
3140
3253
|
}
|
|
3141
3254
|
update(node) {
|
|
3142
3255
|
if (node.type.name !== "slide") return false;
|
|
@@ -3144,7 +3257,13 @@ var SlideNodeView = class {
|
|
|
3144
3257
|
return true;
|
|
3145
3258
|
}
|
|
3146
3259
|
destroy() {
|
|
3147
|
-
this.button
|
|
3260
|
+
if (this.button) {
|
|
3261
|
+
this.button.onclick = null;
|
|
3262
|
+
}
|
|
3263
|
+
if (this.presetModal) {
|
|
3264
|
+
this.presetModal.remove();
|
|
3265
|
+
this.presetModal = null;
|
|
3266
|
+
}
|
|
3148
3267
|
}
|
|
3149
3268
|
};
|
|
3150
3269
|
var addSlideButtonStyles = `
|
|
@@ -3186,6 +3305,131 @@ var addSlideButtonStyles = `
|
|
|
3186
3305
|
outline: 2px solid var(--editor-focus, #3b82f6);
|
|
3187
3306
|
outline-offset: 2px;
|
|
3188
3307
|
}
|
|
3308
|
+
|
|
3309
|
+
.add-slide-button-group {
|
|
3310
|
+
display: grid;
|
|
3311
|
+
grid-template-columns: repeat(2, 1fr);
|
|
3312
|
+
width: 180px;
|
|
3313
|
+
margin: 16px auto 32px auto;
|
|
3314
|
+
border: 1px solid var(--slide-border, #e5e5e5);
|
|
3315
|
+
border-radius: 12px;
|
|
3316
|
+
overflow: hidden;
|
|
3317
|
+
background-color: var(--slide-bg, #ffffff);
|
|
3318
|
+
box-shadow: var(--slide-shadow, 0 4px 12px rgba(0, 0, 0, 0.08));
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
.add-slide-button-group .add-slide-button {
|
|
3322
|
+
margin: 0;
|
|
3323
|
+
border: none;
|
|
3324
|
+
border-radius: 0;
|
|
3325
|
+
width: 100%;
|
|
3326
|
+
height: 48px;
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
.add-slide-button-group .add-slide-button:first-child {
|
|
3330
|
+
border-right: 1px solid var(--slide-border, #e5e5e5);
|
|
3331
|
+
border-top-left-radius: 12px;
|
|
3332
|
+
border-bottom-left-radius: 12px;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
.add-slide-button-group .add-slide-button:last-child {
|
|
3336
|
+
border-top-right-radius: 12px;
|
|
3337
|
+
border-bottom-right-radius: 12px;
|
|
3338
|
+
white-space: nowrap;
|
|
3339
|
+
font-size: 14px;
|
|
3340
|
+
padding: 0 12px;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
.add-slide-preset-modal {
|
|
3344
|
+
position: fixed;
|
|
3345
|
+
inset: 0;
|
|
3346
|
+
background: rgba(0, 0, 0, 0.35);
|
|
3347
|
+
display: flex;
|
|
3348
|
+
align-items: center;
|
|
3349
|
+
justify-content: center;
|
|
3350
|
+
z-index: 9999;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
.add-slide-preset-dialog {
|
|
3354
|
+
background: var(--add-slide-preset-bg, #ffffff);
|
|
3355
|
+
color: var(--add-slide-preset-fg, #000000);
|
|
3356
|
+
border-radius: 8px;
|
|
3357
|
+
padding: 16px 16px 12px 16px;
|
|
3358
|
+
min-width: 140px;
|
|
3359
|
+
max-width: 480px;
|
|
3360
|
+
max-height: 60vh;
|
|
3361
|
+
overflow: hidden;
|
|
3362
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
.add-slide-preset-search {
|
|
3366
|
+
width: 100%;
|
|
3367
|
+
padding: 10px 12px;
|
|
3368
|
+
margin-bottom: 10px;
|
|
3369
|
+
border-radius: 8px;
|
|
3370
|
+
border: 1px solid color-mix(in srgb, var(--add-slide-preset-fg, #000000) 18%, transparent);
|
|
3371
|
+
font-size: 14px;
|
|
3372
|
+
color: inherit;
|
|
3373
|
+
background: color-mix(in srgb, var(--add-slide-preset-bg, #ffffff) 90%, var(--add-slide-preset-fg, #000000) 10%);
|
|
3374
|
+
}
|
|
3375
|
+
.add-slide-preset-search:focus {
|
|
3376
|
+
outline: 2px solid color-mix(in srgb, var(--add-slide-preset-fg, #3b82f6) 35%, transparent);
|
|
3377
|
+
outline-offset: 1px;
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
.add-slide-preset-list {
|
|
3381
|
+
display: flex;
|
|
3382
|
+
flex-direction: column;
|
|
3383
|
+
gap: 6px;
|
|
3384
|
+
max-height: 52vh;
|
|
3385
|
+
overflow: auto;
|
|
3386
|
+
scrollbar-width: none;
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
.add-slide-preset-list::-webkit-scrollbar {
|
|
3390
|
+
display: none;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
.add-slide-preset-item {
|
|
3394
|
+
display: flex;
|
|
3395
|
+
flex-direction: column;
|
|
3396
|
+
align-items: center;
|
|
3397
|
+
gap: 8px;
|
|
3398
|
+
width: 100%;
|
|
3399
|
+
border: none;
|
|
3400
|
+
background: transparent;
|
|
3401
|
+
color: inherit;
|
|
3402
|
+
padding: 10px 0 12px 0;
|
|
3403
|
+
cursor: pointer;
|
|
3404
|
+
border-bottom: 1px solid color-mix(in srgb, var(--add-slide-preset-fg, #000000) 12%, transparent);
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
.add-slide-preset-item:last-child {
|
|
3408
|
+
border-bottom: none;
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
.add-slide-preset-item:hover {
|
|
3412
|
+
background: rgba(0, 0, 0, 0.03);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
.add-slide-preset-icon {
|
|
3416
|
+
display: block;
|
|
3417
|
+
width: 100%;
|
|
3418
|
+
height: auto;
|
|
3419
|
+
line-height: 0;
|
|
3420
|
+
}
|
|
3421
|
+
.add-slide-preset-icon > svg {
|
|
3422
|
+
width: 100%;
|
|
3423
|
+
height: auto;
|
|
3424
|
+
display: block;
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
.add-slide-preset-label {
|
|
3428
|
+
text-align: center;
|
|
3429
|
+
font-size: 14px;
|
|
3430
|
+
font-weight: 600;
|
|
3431
|
+
width: 100%;
|
|
3432
|
+
}
|
|
3189
3433
|
`;
|
|
3190
3434
|
var AddSlideButton = Extension.create({
|
|
3191
3435
|
name: "addSlideButton",
|
|
@@ -3195,7 +3439,12 @@ var AddSlideButton = Extension.create({
|
|
|
3195
3439
|
injectNonce: void 0,
|
|
3196
3440
|
buttonStyle: {},
|
|
3197
3441
|
content: "+",
|
|
3198
|
-
|
|
3442
|
+
showPresets: false,
|
|
3443
|
+
presets: [],
|
|
3444
|
+
templateButtonContent: "Template +",
|
|
3445
|
+
onClick: null,
|
|
3446
|
+
presetBackground: "#ffffff",
|
|
3447
|
+
presetForeground: "#000000"
|
|
3199
3448
|
};
|
|
3200
3449
|
},
|
|
3201
3450
|
addProseMirrorPlugins() {
|