@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
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState } from 'react';
4
- import { ArrowLeft, Library, Settings2 } from 'lucide-react';
4
+ import { ArrowLeft, Library, Settings2, Save, Clock, Edit, Eye } from 'lucide-react';
5
5
  import { useEditor } from '../../state/EditorContext';
6
6
  import { SaveConfirmationModal } from './SaveConfirmationModal';
7
7
 
@@ -15,6 +15,11 @@ export interface EditorHeaderProps {
15
15
  isSaving: boolean;
16
16
  onSave: (publish?: boolean) => Promise<void>;
17
17
  onSaveError: (error: string | null) => void;
18
+ autoSaveEnabled?: boolean;
19
+ onAutoSaveToggle?: (enabled: boolean) => void;
20
+ isDirty?: boolean;
21
+ autoSaveCountdown?: number | null;
22
+ autoSaveStatus?: 'idle' | 'saving' | 'saved' | 'error';
18
23
  }
19
24
 
20
25
  export function EditorHeader({
@@ -27,6 +32,11 @@ export function EditorHeader({
27
32
  isSaving,
28
33
  onSave,
29
34
  onSaveError,
35
+ autoSaveEnabled = false,
36
+ onAutoSaveToggle,
37
+ isDirty = false,
38
+ autoSaveCountdown = null,
39
+ autoSaveStatus = 'idle',
30
40
  }: EditorHeaderProps) {
31
41
  const { state, dispatch } = useEditor();
32
42
  const [showConfirmModal, setShowConfirmModal] = useState(false);
@@ -49,21 +59,21 @@ export function EditorHeader({
49
59
  try {
50
60
  const targetStatus = saveAsDraft ? 'draft' : 'published';
51
61
  console.log('[EditorHeader] Starting save process...', { saveAsDraft, targetStatus, currentStatus: state.status });
52
-
62
+
53
63
  // Set status before saving - ensure state is updated
54
64
  if (saveAsDraft) {
55
65
  dispatch({ type: 'SET_STATUS', payload: 'draft' });
56
66
  } else {
57
67
  dispatch({ type: 'SET_STATUS', payload: 'published' });
58
68
  }
59
-
69
+
60
70
  // Wait longer to ensure state update propagates through the reducer and context
61
71
  // React state updates are asynchronous, so we need to wait for the state to actually update
62
72
  await new Promise(resolve => setTimeout(resolve, 150));
63
-
73
+
64
74
  // Verify status was updated
65
75
  console.log('[EditorHeader] Status after update:', state.status, 'Expected:', targetStatus);
66
-
76
+
67
77
  await onSave(!saveAsDraft);
68
78
  console.log('[EditorHeader] Post saved successfully');
69
79
  // Clear any previous errors
@@ -73,7 +83,7 @@ export function EditorHeader({
73
83
  console.error('[EditorHeader] Failed to save post:', error);
74
84
  // Extract user-friendly error message
75
85
  let errorMessage = error.message || 'Failed to save post';
76
-
86
+
77
87
  // Make error messages more user-friendly
78
88
  if (errorMessage.includes('Missing required fields')) {
79
89
  // Keep the detailed message about missing fields
@@ -85,7 +95,7 @@ export function EditorHeader({
85
95
  } else if (errorMessage.includes('Failed to save')) {
86
96
  errorMessage = 'Unable to save the post. Please check your connection and try again.';
87
97
  }
88
-
98
+
89
99
  setSaveError(errorMessage);
90
100
  onSaveError(errorMessage);
91
101
  // Re-throw the error so the modal knows it failed and doesn't show success
@@ -94,10 +104,20 @@ export function EditorHeader({
94
104
  };
95
105
 
96
106
  return (
97
- <header className="flex items-center justify-between px-6 py-3 bg-dashboard-sidebar backdrop-blur-md z-10 border-b border-dashboard-border flex-none shrink-0">
107
+ <header className="flex items-center justify-between px-6 py-3 bg-dashboard-sidebar backdrop-blur-md border-b border-dashboard-border flex-none shrink-0">
98
108
  <div className="flex items-center gap-6">
99
109
  <button
100
- onClick={() => window.location.href = '/dashboard/blog'}
110
+ onClick={() => {
111
+ if (isDirty) {
112
+ const confirmed = window.confirm(
113
+ 'You have unsaved changes. Are you sure you want to leave? Your changes will be lost.'
114
+ );
115
+ if (!confirmed) {
116
+ return;
117
+ }
118
+ }
119
+ window.location.href = '/dashboard/blog';
120
+ }}
101
121
  className="text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white transition-colors"
102
122
  >
103
123
  <ArrowLeft size={20} strokeWidth={1.5} />
@@ -114,15 +134,84 @@ export function EditorHeader({
114
134
  </div>
115
135
 
116
136
  <div className="flex items-center gap-4">
117
- <button
118
- onClick={onPreviewToggle}
119
- className={`text-[10px] uppercase tracking-widest font-bold transition-colors ${isPreviewMode
120
- ? 'text-primary dark:text-primary'
121
- : 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'
137
+ {/* Auto-save Toggle */}
138
+ {onAutoSaveToggle && (
139
+ <div className="flex items-center gap-2">
140
+ <button
141
+ onClick={() => onAutoSaveToggle(!autoSaveEnabled)}
142
+ className={`relative flex items-center gap-2 px-3 py-1.5 rounded-full text-[10px] uppercase tracking-widest font-bold transition-all ${
143
+ autoSaveEnabled
144
+ ? 'bg-primary/20 text-primary border border-primary/30'
145
+ : 'bg-dashboard-bg text-neutral-600 dark:text-neutral-400 border border-dashboard-border hover:text-neutral-950 dark:hover:text-white'
146
+ }`}
147
+ title={autoSaveEnabled ? 'Auto-save enabled (saves after 10s of inactivity)' : 'Click to enable auto-save'}
148
+ >
149
+ <Clock size={12} className={autoSaveEnabled && autoSaveStatus !== 'saving' ? 'animate-pulse' : ''} />
150
+ <span>Auto-save</span>
151
+ <span className={`ml-1 text-[9px] ${autoSaveEnabled ? 'text-primary' : 'text-neutral-500 dark:text-neutral-400'}`}>
152
+ {autoSaveEnabled ? 'ON' : 'OFF'}
153
+ </span>
154
+ {/* Countdown or Status */}
155
+ {autoSaveEnabled && isDirty && (
156
+ <span className="ml-1.5 text-[9px] font-bold tabular-nums">
157
+ {autoSaveStatus === 'saving' && (
158
+ <span className="text-primary animate-pulse">Saving...</span>
159
+ )}
160
+ {autoSaveStatus === 'saved' && (
161
+ <span className="text-green-500 dark:text-green-400">Saved!</span>
162
+ )}
163
+ {autoSaveStatus === 'error' && (
164
+ <span className="text-red-500 dark:text-red-400">Error</span>
165
+ )}
166
+ {autoSaveStatus === 'idle' && autoSaveCountdown !== null && (
167
+ <span className="text-primary/70">{autoSaveCountdown}s</span>
168
+ )}
169
+ </span>
170
+ )}
171
+ </button>
172
+ {/* Unsaved Changes Indicator - only show when auto-save is off */}
173
+ {isDirty && !autoSaveEnabled && (
174
+ <span className="text-[10px] text-amber-500 dark:text-amber-400 font-bold uppercase tracking-widest animate-pulse">
175
+ Unsaved
176
+ </span>
177
+ )}
178
+ </div>
179
+ )}
180
+ {/* Edit/Preview Toggle - Segmented Control Style */}
181
+ <div className="flex items-center bg-dashboard-bg border border-dashboard-border rounded-full p-1 gap-1">
182
+ <button
183
+ onClick={() => {
184
+ if (isPreviewMode) {
185
+ onPreviewToggle();
186
+ }
187
+ }}
188
+ className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] uppercase tracking-widest font-bold transition-all ${
189
+ !isPreviewMode
190
+ ? 'bg-primary text-white shadow-sm'
191
+ : 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'
122
192
  }`}
123
- >
124
- {isPreviewMode ? 'Edit' : 'Preview'}
125
- </button>
193
+ title="Edit mode - Make changes to your post"
194
+ >
195
+ <Edit size={12} strokeWidth={2.5} />
196
+ <span>Edit</span>
197
+ </button>
198
+ <button
199
+ onClick={() => {
200
+ if (!isPreviewMode) {
201
+ onPreviewToggle();
202
+ }
203
+ }}
204
+ className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] uppercase tracking-widest font-bold transition-all ${
205
+ isPreviewMode
206
+ ? 'bg-primary text-white shadow-sm'
207
+ : 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'
208
+ }`}
209
+ title="Preview mode - See how your post will look"
210
+ >
211
+ <Eye size={12} strokeWidth={2.5} />
212
+ <span>Preview</span>
213
+ </button>
214
+ </div>
126
215
  {/* Save Draft Button - Always visible for drafts and new posts */}
127
216
  {(state.status === 'draft' || !state.postId) && (
128
217
  <button