@pramen/cms-editor 0.0.27 → 0.0.28
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/app.css +1 -1
- package/dist/index.html +1 -1
- package/dist/main.e0yd6v1m.js +251 -0
- package/package.json +1 -1
- package/src/components.tsx +51 -2
- package/dist/main.negeh67g.js +0 -251
package/package.json
CHANGED
package/src/components.tsx
CHANGED
|
@@ -219,6 +219,22 @@ export function PageEditor({ api, page, blockTypes, tab, onTab, onBack, onChange
|
|
|
219
219
|
});
|
|
220
220
|
}, []);
|
|
221
221
|
|
|
222
|
+
// Drag-to-reorder within a region. `from`/`over` are indices in the region's block list;
|
|
223
|
+
// hovering updates `over` live (for the drop-target highlight), and the drop commits the
|
|
224
|
+
// new order via reorderRegion. Dragging is confined to the region it started in.
|
|
225
|
+
const [drag, setDrag] = useState<{ region: string; from: number; over: number } | null>(null);
|
|
226
|
+
const beginDrag = (region: string, i: number) => setDrag({ region, from: i, over: i });
|
|
227
|
+
const hoverDrag = (region: string, i: number) => setDrag((d) => (d && d.region === region && d.over !== i ? { ...d, over: i } : d));
|
|
228
|
+
const cancelDrag = () => setDrag(null);
|
|
229
|
+
const dropDrag = (region: string) => {
|
|
230
|
+
setDrag(null);
|
|
231
|
+
if (!drag || drag.region !== region || drag.from === drag.over) return;
|
|
232
|
+
const ids = (assembled?.regions[region] ?? []).map((b) => b.id);
|
|
233
|
+
const [moved] = ids.splice(drag.from, 1);
|
|
234
|
+
ids.splice(drag.over, 0, moved);
|
|
235
|
+
reorder(region, ids);
|
|
236
|
+
};
|
|
237
|
+
|
|
222
238
|
return (
|
|
223
239
|
<div className="grid min-h-[calc(100vh-68px)] grid-cols-[260px_1fr_400px] gap-5 px-7 pb-7 pt-2 max-[820px]:grid-cols-1">
|
|
224
240
|
<div className="overflow-auto rounded-panel bg-surface-muted p-[18px]">
|
|
@@ -259,6 +275,12 @@ export function PageEditor({ api, page, blockTypes, tab, onTab, onBack, onChange
|
|
|
259
275
|
onRemove={() => removeBlock(b)}
|
|
260
276
|
onPatch={patchBlockFields}
|
|
261
277
|
onError={setErr}
|
|
278
|
+
dragging={drag?.region === r.name && drag.from === i}
|
|
279
|
+
isOver={!!drag && drag.region === r.name && drag.over === i && drag.from !== i}
|
|
280
|
+
onDragStartBlock={() => beginDrag(r.name, i)}
|
|
281
|
+
onDragOverBlock={() => hoverDrag(r.name, i)}
|
|
282
|
+
onDropBlock={() => dropDrag(r.name)}
|
|
283
|
+
onDragEndBlock={cancelDrag}
|
|
262
284
|
/>
|
|
263
285
|
))}
|
|
264
286
|
<AddBlock allowed={allowed} btBySlug={btBySlug} onAdd={(slug) => addBlock(r.name, slug)} />
|
|
@@ -288,7 +310,7 @@ export function PageEditor({ api, page, blockTypes, tab, onTab, onBack, onChange
|
|
|
288
310
|
// as the WYSIWYG, media as a thumbnail picker), and edits autosave — debounced while typing,
|
|
289
311
|
// flushed on blur and on unmount — so there's no separate "save block" step and no full page
|
|
290
312
|
// reload that would drop the caret. Collapse folds it to a one-line plain-text preview.
|
|
291
|
-
function BlockCard({ api, block, blockType, isFirst, isLast, onMove, onRemove, onPatch, onError }: {
|
|
313
|
+
function BlockCard({ api, block, blockType, isFirst, isLast, onMove, onRemove, onPatch, onError, dragging, isOver, onDragStartBlock, onDragOverBlock, onDropBlock, onDragEndBlock }: {
|
|
292
314
|
api: Api;
|
|
293
315
|
block: RenderedBlock;
|
|
294
316
|
blockType: BlockType | undefined;
|
|
@@ -298,12 +320,19 @@ function BlockCard({ api, block, blockType, isFirst, isLast, onMove, onRemove, o
|
|
|
298
320
|
onRemove: () => void;
|
|
299
321
|
onPatch: (placementId: string, fields: Record<string, unknown>) => void;
|
|
300
322
|
onError: (s: string) => void;
|
|
323
|
+
dragging: boolean;
|
|
324
|
+
isOver: boolean;
|
|
325
|
+
onDragStartBlock: () => void;
|
|
326
|
+
onDragOverBlock: () => void;
|
|
327
|
+
onDropBlock: () => void;
|
|
328
|
+
onDragEndBlock: () => void;
|
|
301
329
|
}) {
|
|
302
330
|
const [fields, setFields] = useState<Record<string, unknown> | null>(null);
|
|
303
331
|
const [collapsed, setCollapsed] = useState(false);
|
|
304
332
|
const [saveState, setSaveState] = useState<"idle" | "saving" | "saved">("idle");
|
|
305
333
|
const pending = useRef<Record<string, unknown> | null>(null);
|
|
306
334
|
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
335
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
307
336
|
const blockId = block.block_id;
|
|
308
337
|
const placementId = block.id;
|
|
309
338
|
|
|
@@ -345,8 +374,28 @@ function BlockCard({ api, block, blockType, isFirst, isLast, onMove, onRemove, o
|
|
|
345
374
|
const name = blockType?.name ?? block.block_type;
|
|
346
375
|
|
|
347
376
|
return (
|
|
348
|
-
|
|
377
|
+
// The card is the drop target; the ⠿ handle is the only draggable element, so
|
|
378
|
+
// dragging never fights the inline contentEditable text selection. The handle's drag
|
|
379
|
+
// image is set to the whole card so you drag a preview of the block, not just the grip.
|
|
380
|
+
<div
|
|
381
|
+
ref={cardRef}
|
|
382
|
+
className={`mb-2.5 rounded-panel border bg-surface-card transition-colors ${isOver ? "border-brand-green" : "border-border"} ${dragging ? "opacity-40" : ""}`}
|
|
383
|
+
onDragOver={(e) => { e.preventDefault(); onDragOverBlock(); }}
|
|
384
|
+
onDrop={(e) => { e.preventDefault(); onDropBlock(); }}
|
|
385
|
+
>
|
|
349
386
|
<div className="flex items-center gap-2.5 px-3.5 py-2.5">
|
|
387
|
+
<span
|
|
388
|
+
draggable
|
|
389
|
+
onDragStart={(e) => {
|
|
390
|
+
if (cardRef.current) e.dataTransfer.setDragImage(cardRef.current, 12, 12);
|
|
391
|
+
e.dataTransfer.effectAllowed = "move";
|
|
392
|
+
e.dataTransfer.setData("text/plain", "");
|
|
393
|
+
onDragStartBlock();
|
|
394
|
+
}}
|
|
395
|
+
onDragEnd={onDragEndBlock}
|
|
396
|
+
className="shrink-0 cursor-grab select-none px-0.5 text-fg-subtle hover:text-fg active:cursor-grabbing"
|
|
397
|
+
title="Drag to reorder"
|
|
398
|
+
>⠿</span>
|
|
350
399
|
<button type="button" className="w-4 shrink-0 text-fg-subtle hover:text-fg" title={collapsed ? "Expand" : "Collapse"} onClick={() => setCollapsed((c) => !c)}>{collapsed ? "▸" : "▾"}</button>
|
|
351
400
|
<span className="font-medium text-fg">{name}</span>
|
|
352
401
|
{block.is_shared ? <span className="text-[11px] text-accent-strong">shared</span> : null}
|