@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 CHANGED
@@ -3097,6 +3097,7 @@ var PluginKey = class {
3097
3097
  // src/add-slide-button.ts
3098
3098
  var SlideNodeView = class {
3099
3099
  constructor(node, view, getPos, options) {
3100
+ var _a;
3100
3101
  this.node = node;
3101
3102
  this.view = view;
3102
3103
  this.getPos = getPos;
@@ -3122,21 +3123,33 @@ var SlideNodeView = class {
3122
3123
  this.contentDOM.className = "slide";
3123
3124
  this.contentDOM.setAttribute("data-node-type", "slide");
3124
3125
  }
3125
- this.button = document.createElement("button");
3126
- this.button.className = "add-slide-button";
3127
- this.button.innerHTML = options.content;
3128
- this.button.setAttribute("type", "button");
3129
- this.button.contentEditable = "false";
3130
- if (options.buttonStyle) {
3131
- Object.entries(options.buttonStyle).forEach(([key, value]) => {
3132
- const camelKey = key.replace(
3133
- /-([a-z])/g,
3134
- (_, letter) => letter.toUpperCase()
3135
- );
3136
- this.button.style[camelKey] = value;
3137
- });
3138
- }
3139
- this.button.onclick = (event) => {
3126
+ const createButton = (html) => {
3127
+ const btn = document.createElement("button");
3128
+ btn.className = "add-slide-button";
3129
+ btn.innerHTML = html;
3130
+ btn.setAttribute("type", "button");
3131
+ btn.contentEditable = "false";
3132
+ if (options.buttonStyle) {
3133
+ Object.entries(options.buttonStyle).forEach(([key, value]) => {
3134
+ const camelKey = key.replace(
3135
+ /-([a-z])/g,
3136
+ (_, letter) => letter.toUpperCase()
3137
+ );
3138
+ btn.style[camelKey] = value;
3139
+ });
3140
+ }
3141
+ return btn;
3142
+ };
3143
+ const insertEmptySlide = (pos) => {
3144
+ const schema = this.view.state.schema;
3145
+ const slideType = schema.nodes.slide;
3146
+ const paragraphType = schema.nodes.paragraph;
3147
+ const slideContent = paragraphType.create();
3148
+ const slide = slideType.create(null, slideContent);
3149
+ const tr = this.view.state.tr.insert(pos + this.node.nodeSize, slide);
3150
+ this.view.dispatch(tr);
3151
+ };
3152
+ const handlePlusClick = (event) => {
3140
3153
  event.preventDefault();
3141
3154
  event.stopPropagation();
3142
3155
  const pos = this.getPos();
@@ -3153,17 +3166,117 @@ var SlideNodeView = class {
3153
3166
  event
3154
3167
  });
3155
3168
  } else {
3156
- const schema = this.view.state.schema;
3157
- const slideType = schema.nodes.slide;
3158
- const paragraphType = schema.nodes.paragraph;
3159
- const slideContent = paragraphType.create();
3160
- const slide = slideType.create(null, slideContent);
3161
- const tr = this.view.state.tr.insert(pos + this.node.nodeSize, slide);
3162
- this.view.dispatch(tr);
3169
+ insertEmptySlide(pos);
3170
+ }
3171
+ };
3172
+ const openPresetModal = (pos) => {
3173
+ if (this.presetModal) {
3174
+ this.presetModal.remove();
3163
3175
  }
3176
+ const overlay = document.createElement("div");
3177
+ overlay.className = "add-slide-preset-modal";
3178
+ const dialog = document.createElement("div");
3179
+ dialog.className = "add-slide-preset-dialog";
3180
+ if (options.presetBackground) {
3181
+ dialog.style.setProperty("--add-slide-preset-bg", options.presetBackground);
3182
+ }
3183
+ if (options.presetForeground) {
3184
+ dialog.style.setProperty("--add-slide-preset-fg", options.presetForeground);
3185
+ }
3186
+ const search = document.createElement("input");
3187
+ search.className = "add-slide-preset-search";
3188
+ search.type = "search";
3189
+ search.placeholder = "Choose a template";
3190
+ dialog.appendChild(search);
3191
+ const list = document.createElement("div");
3192
+ list.className = "add-slide-preset-list";
3193
+ const items = [];
3194
+ (options.presets || []).forEach((preset) => {
3195
+ var _a2;
3196
+ const item = document.createElement("button");
3197
+ item.className = "add-slide-preset-item";
3198
+ item.setAttribute("type", "button");
3199
+ item.innerHTML = `
3200
+ <span class="add-slide-preset-icon">${(_a2 = preset.icon) != null ? _a2 : ""}</span>
3201
+ <span class="add-slide-preset-label">${preset.label}</span>
3202
+ `;
3203
+ item.onclick = (event) => {
3204
+ event.preventDefault();
3205
+ event.stopPropagation();
3206
+ try {
3207
+ const nodeJSON = preset.build();
3208
+ const newSlide = this.view.state.schema.nodeFromJSON(nodeJSON);
3209
+ const tr = this.view.state.tr.insert(
3210
+ pos + this.node.nodeSize,
3211
+ newSlide
3212
+ );
3213
+ this.view.dispatch(tr);
3214
+ } catch (err) {
3215
+ console.error("Failed to insert preset slide", err);
3216
+ } finally {
3217
+ overlay.remove();
3218
+ this.presetModal = null;
3219
+ }
3220
+ };
3221
+ list.appendChild(item);
3222
+ items.push(item);
3223
+ });
3224
+ search.oninput = () => {
3225
+ const term = search.value.toLowerCase();
3226
+ items.forEach((item) => {
3227
+ var _a2, _b;
3228
+ const label = (_b = (_a2 = item.querySelector(".add-slide-preset-label")) == null ? void 0 : _a2.textContent) == null ? void 0 : _b.toLowerCase();
3229
+ item.style.display = !term || (label == null ? void 0 : label.includes(term)) ? "" : "none";
3230
+ });
3231
+ };
3232
+ dialog.appendChild(list);
3233
+ const close2 = () => {
3234
+ overlay.remove();
3235
+ this.presetModal = null;
3236
+ };
3237
+ overlay.onclick = (e) => {
3238
+ if (e.target === overlay) {
3239
+ close2();
3240
+ }
3241
+ };
3242
+ document.addEventListener(
3243
+ "keydown",
3244
+ (e) => {
3245
+ if (e.key === "Escape") {
3246
+ close2();
3247
+ }
3248
+ },
3249
+ { once: true }
3250
+ );
3251
+ overlay.appendChild(dialog);
3252
+ document.body.appendChild(overlay);
3253
+ this.presetModal = overlay;
3164
3254
  };
3165
- this.dom.appendChild(this.contentDOM);
3166
- this.dom.appendChild(this.button);
3255
+ const plusButton = createButton(options.content);
3256
+ plusButton.onclick = handlePlusClick;
3257
+ this.button = plusButton;
3258
+ if (options.showPresets) {
3259
+ const templateButton = createButton(
3260
+ (_a = options.templateButtonContent) != null ? _a : "\u2728"
3261
+ );
3262
+ templateButton.onclick = (event) => {
3263
+ event.preventDefault();
3264
+ event.stopPropagation();
3265
+ const pos = this.getPos();
3266
+ if (pos === void 0) return;
3267
+ openPresetModal(pos);
3268
+ };
3269
+ const group = document.createElement("div");
3270
+ group.className = "add-slide-button-group";
3271
+ group.appendChild(plusButton);
3272
+ group.appendChild(templateButton);
3273
+ this.dom.appendChild(this.contentDOM);
3274
+ this.dom.appendChild(group);
3275
+ } else {
3276
+ this.button = plusButton;
3277
+ this.dom.appendChild(this.contentDOM);
3278
+ this.dom.appendChild(this.button);
3279
+ }
3167
3280
  }
3168
3281
  update(node) {
3169
3282
  if (node.type.name !== "slide") return false;
@@ -3171,7 +3284,13 @@ var SlideNodeView = class {
3171
3284
  return true;
3172
3285
  }
3173
3286
  destroy() {
3174
- this.button.onclick = null;
3287
+ if (this.button) {
3288
+ this.button.onclick = null;
3289
+ }
3290
+ if (this.presetModal) {
3291
+ this.presetModal.remove();
3292
+ this.presetModal = null;
3293
+ }
3175
3294
  }
3176
3295
  };
3177
3296
  var addSlideButtonStyles = `
@@ -3213,6 +3332,131 @@ var addSlideButtonStyles = `
3213
3332
  outline: 2px solid var(--editor-focus, #3b82f6);
3214
3333
  outline-offset: 2px;
3215
3334
  }
3335
+
3336
+ .add-slide-button-group {
3337
+ display: grid;
3338
+ grid-template-columns: repeat(2, 1fr);
3339
+ width: 180px;
3340
+ margin: 16px auto 32px auto;
3341
+ border: 1px solid var(--slide-border, #e5e5e5);
3342
+ border-radius: 12px;
3343
+ overflow: hidden;
3344
+ background-color: var(--slide-bg, #ffffff);
3345
+ box-shadow: var(--slide-shadow, 0 4px 12px rgba(0, 0, 0, 0.08));
3346
+ }
3347
+
3348
+ .add-slide-button-group .add-slide-button {
3349
+ margin: 0;
3350
+ border: none;
3351
+ border-radius: 0;
3352
+ width: 100%;
3353
+ height: 48px;
3354
+ }
3355
+
3356
+ .add-slide-button-group .add-slide-button:first-child {
3357
+ border-right: 1px solid var(--slide-border, #e5e5e5);
3358
+ border-top-left-radius: 12px;
3359
+ border-bottom-left-radius: 12px;
3360
+ }
3361
+
3362
+ .add-slide-button-group .add-slide-button:last-child {
3363
+ border-top-right-radius: 12px;
3364
+ border-bottom-right-radius: 12px;
3365
+ white-space: nowrap;
3366
+ font-size: 14px;
3367
+ padding: 0 12px;
3368
+ }
3369
+
3370
+ .add-slide-preset-modal {
3371
+ position: fixed;
3372
+ inset: 0;
3373
+ background: rgba(0, 0, 0, 0.35);
3374
+ display: flex;
3375
+ align-items: center;
3376
+ justify-content: center;
3377
+ z-index: 9999;
3378
+ }
3379
+
3380
+ .add-slide-preset-dialog {
3381
+ background: var(--add-slide-preset-bg, #ffffff);
3382
+ color: var(--add-slide-preset-fg, #000000);
3383
+ border-radius: 8px;
3384
+ padding: 16px 16px 12px 16px;
3385
+ min-width: 140px;
3386
+ max-width: 480px;
3387
+ max-height: 60vh;
3388
+ overflow: hidden;
3389
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
3390
+ }
3391
+
3392
+ .add-slide-preset-search {
3393
+ width: 100%;
3394
+ padding: 10px 12px;
3395
+ margin-bottom: 10px;
3396
+ border-radius: 8px;
3397
+ border: 1px solid color-mix(in srgb, var(--add-slide-preset-fg, #000000) 18%, transparent);
3398
+ font-size: 14px;
3399
+ color: inherit;
3400
+ background: color-mix(in srgb, var(--add-slide-preset-bg, #ffffff) 90%, var(--add-slide-preset-fg, #000000) 10%);
3401
+ }
3402
+ .add-slide-preset-search:focus {
3403
+ outline: 2px solid color-mix(in srgb, var(--add-slide-preset-fg, #3b82f6) 35%, transparent);
3404
+ outline-offset: 1px;
3405
+ }
3406
+
3407
+ .add-slide-preset-list {
3408
+ display: flex;
3409
+ flex-direction: column;
3410
+ gap: 6px;
3411
+ max-height: 52vh;
3412
+ overflow: auto;
3413
+ scrollbar-width: none;
3414
+ }
3415
+
3416
+ .add-slide-preset-list::-webkit-scrollbar {
3417
+ display: none;
3418
+ }
3419
+
3420
+ .add-slide-preset-item {
3421
+ display: flex;
3422
+ flex-direction: column;
3423
+ align-items: center;
3424
+ gap: 8px;
3425
+ width: 100%;
3426
+ border: none;
3427
+ background: transparent;
3428
+ color: inherit;
3429
+ padding: 10px 0 12px 0;
3430
+ cursor: pointer;
3431
+ border-bottom: 1px solid color-mix(in srgb, var(--add-slide-preset-fg, #000000) 12%, transparent);
3432
+ }
3433
+
3434
+ .add-slide-preset-item:last-child {
3435
+ border-bottom: none;
3436
+ }
3437
+
3438
+ .add-slide-preset-item:hover {
3439
+ background: rgba(0, 0, 0, 0.03);
3440
+ }
3441
+
3442
+ .add-slide-preset-icon {
3443
+ display: block;
3444
+ width: 100%;
3445
+ height: auto;
3446
+ line-height: 0;
3447
+ }
3448
+ .add-slide-preset-icon > svg {
3449
+ width: 100%;
3450
+ height: auto;
3451
+ display: block;
3452
+ }
3453
+
3454
+ .add-slide-preset-label {
3455
+ text-align: center;
3456
+ font-size: 14px;
3457
+ font-weight: 600;
3458
+ width: 100%;
3459
+ }
3216
3460
  `;
3217
3461
  var AddSlideButton = import_core.Extension.create({
3218
3462
  name: "addSlideButton",
@@ -3222,7 +3466,12 @@ var AddSlideButton = import_core.Extension.create({
3222
3466
  injectNonce: void 0,
3223
3467
  buttonStyle: {},
3224
3468
  content: "+",
3225
- onClick: null
3469
+ showPresets: false,
3470
+ presets: [],
3471
+ templateButtonContent: "Template +",
3472
+ onClick: null,
3473
+ presetBackground: "#ffffff",
3474
+ presetForeground: "#000000"
3226
3475
  };
3227
3476
  },
3228
3477
  addProseMirrorPlugins() {