@jhits/plugin-blog 0.0.5 → 0.0.7

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.
Files changed (39) hide show
  1. package/package.json +16 -16
  2. package/src/api/config-handler.ts +76 -0
  3. package/src/api/handler.ts +4 -4
  4. package/src/api/router.ts +17 -0
  5. package/src/hooks/index.ts +1 -0
  6. package/src/hooks/useCategories.ts +76 -0
  7. package/src/index.tsx +8 -27
  8. package/src/init.tsx +0 -9
  9. package/src/lib/config-storage.ts +65 -0
  10. package/src/lib/layouts/blocks/ColumnsBlock.tsx +177 -13
  11. package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +81 -0
  12. package/src/lib/layouts/registerLayoutBlocks.ts +6 -1
  13. package/src/lib/mappers/apiMapper.ts +53 -22
  14. package/src/registry/BlockRegistry.ts +1 -4
  15. package/src/state/EditorContext.tsx +39 -33
  16. package/src/state/types.ts +1 -1
  17. package/src/types/index.ts +2 -0
  18. package/src/types/post.ts +4 -0
  19. package/src/views/CanvasEditor/BlockWrapper.tsx +87 -24
  20. package/src/views/CanvasEditor/CanvasEditorView.tsx +214 -794
  21. package/src/views/CanvasEditor/EditorBody.tsx +317 -127
  22. package/src/views/CanvasEditor/EditorHeader.tsx +106 -17
  23. package/src/views/CanvasEditor/LayoutContainer.tsx +208 -380
  24. package/src/views/CanvasEditor/components/EditorCanvas.tsx +160 -0
  25. package/src/views/CanvasEditor/components/EditorLibrary.tsx +122 -0
  26. package/src/views/CanvasEditor/components/EditorSidebar.tsx +181 -0
  27. package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
  28. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +260 -49
  29. package/src/views/CanvasEditor/components/index.ts +11 -0
  30. package/src/views/CanvasEditor/hooks/index.ts +10 -0
  31. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +103 -0
  32. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +142 -0
  33. package/src/views/CanvasEditor/hooks/usePostLoader.ts +39 -0
  34. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +55 -0
  35. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +339 -0
  36. package/src/views/PostManager/PostCards.tsx +18 -13
  37. package/src/views/PostManager/PostFilters.tsx +15 -0
  38. package/src/views/PostManager/PostManagerView.tsx +21 -15
  39. package/src/views/PostManager/PostTable.tsx +7 -4
@@ -274,7 +274,7 @@ export function BlockWrapper({
274
274
  >
275
275
  {/* Left Margin Controls - Visible on Hover */}
276
276
  <div
277
- className={`absolute -left-16 top-1/2 -translate-y-1/2 flex flex-col gap-2 transition-all duration-200 z-20 ${showControls ? 'opacity-100' : 'opacity-0 pointer-events-none'
277
+ className={`absolute -left-16 top-1/2 -translate-y-1/2 flex flex-col gap-2 transition-all duration-200 ${showControls ? 'opacity-100' : 'opacity-0 pointer-events-none'
278
278
  }`}
279
279
  onMouseEnter={() => setIsControlsHovered(true)}
280
280
  onMouseLeave={() => setIsControlsHovered(false)}
@@ -321,7 +321,7 @@ export function BlockWrapper({
321
321
 
322
322
  {/* Block Header - Positioned above the component */}
323
323
  <div
324
- className={`mb-2 transition-all relative z-[100] ${isHovered || showControls || showSettingsMenu
324
+ className={`mb-2 transition-all relative ${isHovered || showControls || showSettingsMenu
325
325
  ? 'opacity-100 translate-y-0'
326
326
  : 'opacity-0 -translate-y-2 pointer-events-none'
327
327
  }`}
@@ -339,7 +339,7 @@ export function BlockWrapper({
339
339
  </span>
340
340
 
341
341
  {/* Layout Block Settings - Inline in header */}
342
- {block.type === 'section' && (
342
+ {/* {block.type === 'section' && (
343
343
  <div className="flex items-center gap-2 ml-4 flex-1 min-w-0">
344
344
  <select
345
345
  value={(block.data.padding as string) || 'md'}
@@ -363,31 +363,95 @@ export function BlockWrapper({
363
363
  <option value="CREAM">Cream</option>
364
364
  </select>
365
365
  </div>
366
- )}
367
-
368
- {block.type === 'columns' && (
369
- <select
370
- value={(block.data.layout as string) || '50-50'}
371
- onChange={(e) => onUpdate({ layout: e.target.value })}
372
- onClick={(e) => e.stopPropagation()}
373
- className="text-[10px] font-bold bg-white dark:bg-neutral-900/50 border border-neutral-300 dark:border-neutral-700 px-2 py-1 rounded outline-none focus:border-primary transition-all dark:text-neutral-100 ml-4"
374
- >
375
- <option value="50-50">50% / 50%</option>
376
- <option value="33-66">33% / 66%</option>
377
- <option value="66-33">66% / 33%</option>
378
- <option value="25-25-25-25">25% / 25% / 25% / 25%</option>
379
- <option value="25-75">25% / 75%</option>
380
- <option value="75-25">75% / 25%</option>
381
- </select>
382
- )}
366
+ )} */}
367
+
368
+ {block.type === 'columns' && (() => {
369
+ const columnCount = block.data.columnCount as number | undefined;
370
+ const layout = block.data.layout as string | undefined;
371
+
372
+ // Determine number of columns
373
+ let numColumns: number;
374
+ if (columnCount !== undefined && columnCount > 0) {
375
+ numColumns = columnCount;
376
+ } else if (layout) {
377
+ // Legacy layout system
378
+ const layoutMap: Record<string, number> = {
379
+ '50-50': 2,
380
+ '33-66': 2,
381
+ '66-33': 2,
382
+ '25-25-25-25': 4,
383
+ '25-75': 2,
384
+ '75-25': 2,
385
+ };
386
+ numColumns = layoutMap[layout] || 2;
387
+ } else {
388
+ numColumns = 2;
389
+ }
390
+
391
+ // Get column widths
392
+ const storedWidths = block.data.columnWidths as number[] | undefined;
393
+ const columnWidths = storedWidths && storedWidths.length === numColumns
394
+ ? storedWidths
395
+ : Array(numColumns).fill(Math.floor(100 / numColumns));
396
+
397
+ return (
398
+ <div className="flex items-center gap-1.5 ml-4 flex-1 min-w-0">
399
+ {Array.from({ length: numColumns }).map((_, colIndex) => (
400
+ <div key={colIndex} className="flex items-center gap-1">
401
+ <input
402
+ type="number"
403
+ min="10"
404
+ max="90"
405
+ step="1"
406
+ value={columnWidths[colIndex] || 50}
407
+ onChange={(e) => {
408
+ const newWidth = parseInt(e.target.value) || 50;
409
+ const newWidths = [...columnWidths];
410
+ newWidths[colIndex] = Math.max(10, Math.min(90, newWidth));
411
+
412
+ // Normalize remaining columns to sum to 100
413
+ const remainingTotal = newWidths.reduce((sum, w, i) => i === colIndex ? sum : sum + w, 0);
414
+ const remainingTarget = 100 - newWidths[colIndex];
415
+ if (remainingTotal > 0 && remainingTarget > 0) {
416
+ const scale = remainingTarget / remainingTotal;
417
+ newWidths.forEach((w, i) => {
418
+ if (i !== colIndex) {
419
+ newWidths[i] = Math.round(w * scale);
420
+ }
421
+ });
422
+ }
423
+
424
+ // Ensure sum is exactly 100
425
+ const finalTotal = newWidths.reduce((sum, w) => sum + w, 0);
426
+ if (finalTotal !== 100) {
427
+ const diff = 100 - finalTotal;
428
+ const lastIndex = newWidths.length - 1;
429
+ newWidths[lastIndex] = Math.max(10, newWidths[lastIndex] + diff);
430
+ }
431
+
432
+ onUpdate({
433
+ ...block.data,
434
+ columnWidths: newWidths,
435
+ layout: undefined, // Clear layout when using custom widths
436
+ });
437
+ }}
438
+ onClick={(e) => e.stopPropagation()}
439
+ className="w-12 text-[10px] font-bold bg-white dark:bg-neutral-900/50 border border-neutral-300 dark:border-neutral-700 px-1.5 py-0.5 rounded outline-none focus:border-primary transition-all dark:text-neutral-100 text-center"
440
+ />
441
+ <span className="text-[9px] text-neutral-400 dark:text-neutral-500">%</span>
442
+ </div>
443
+ ))}
444
+ </div>
445
+ );
446
+ })()}
383
447
  </div>
384
- <div className="relative shrink-0 z-[200]" ref={settingsMenuRef}>
448
+ <div className="relative shrink-0 z-20" ref={settingsMenuRef}>
385
449
  <button
386
450
  onClick={(e) => {
387
451
  e.stopPropagation();
388
452
  setShowSettingsMenu(!showSettingsMenu);
389
453
  }}
390
- className="p-1 rounded transition-colors text-neutral-400 dark:text-neutral-500 hover:text-neutral-950 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 relative z-[200]"
454
+ className="p-1 rounded transition-colors text-neutral-400 dark:text-neutral-500 hover:text-neutral-950 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 relative z-20"
391
455
  title="Block settings"
392
456
  >
393
457
  <Settings2 size={12} />
@@ -395,7 +459,7 @@ export function BlockWrapper({
395
459
 
396
460
  {/* Settings Dropdown Menu */}
397
461
  {showSettingsMenu && (
398
- <div className="absolute right-0 top-full mt-1 w-40 bg-white dark:bg-neutral-900 border border-neutral-300 dark:border-neutral-700 rounded-lg shadow-xl z-[200] overflow-hidden">
462
+ <div className="absolute right-0 top-full mt-1 w-40 bg-white dark:bg-neutral-900 border border-neutral-300 dark:border-neutral-700 rounded-lg shadow-xl z-20 overflow-hidden">
399
463
  <button
400
464
  onClick={(e) => {
401
465
  e.stopPropagation();
@@ -418,7 +482,6 @@ export function BlockWrapper({
418
482
  ? 'border-primary/60 dark:border-primary/40 bg-primary/5 dark:bg-primary/10'
419
483
  : 'border-neutral-200 dark:border-neutral-700 bg-transparent'
420
484
  }`}
421
- style={{ zIndex: 1 }}
422
485
  >
423
486
  {/* Edit Component - No padding, let the component control its own spacing */}
424
487
  <div className="relative" style={{ userSelect: 'text' }}>