hazo_notes 1.0.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/README.md +661 -0
- package/SETUP_CHECKLIST.md +453 -0
- package/dist/api/create_files_handler.d.ts +42 -0
- package/dist/api/create_files_handler.d.ts.map +1 -0
- package/dist/api/create_files_handler.js +213 -0
- package/dist/api/create_files_handler.js.map +1 -0
- package/dist/api/create_notes_handler.d.ts +50 -0
- package/dist/api/create_notes_handler.d.ts.map +1 -0
- package/dist/api/create_notes_handler.js +242 -0
- package/dist/api/create_notes_handler.js.map +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -0
- package/dist/components/hazo_notes_entry.d.ts +6 -0
- package/dist/components/hazo_notes_entry.d.ts.map +1 -0
- package/dist/components/hazo_notes_entry.js +69 -0
- package/dist/components/hazo_notes_entry.js.map +1 -0
- package/dist/components/hazo_notes_file_preview.d.ts +16 -0
- package/dist/components/hazo_notes_file_preview.d.ts.map +1 -0
- package/dist/components/hazo_notes_file_preview.js +77 -0
- package/dist/components/hazo_notes_file_preview.js.map +1 -0
- package/dist/components/hazo_notes_icon.d.ts +6 -0
- package/dist/components/hazo_notes_icon.d.ts.map +1 -0
- package/dist/components/hazo_notes_icon.js +208 -0
- package/dist/components/hazo_notes_icon.js.map +1 -0
- package/dist/components/hazo_notes_panel.d.ts +6 -0
- package/dist/components/hazo_notes_panel.d.ts.map +1 -0
- package/dist/components/hazo_notes_panel.js +197 -0
- package/dist/components/hazo_notes_panel.js.map +1 -0
- package/dist/components/index.d.ts +8 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +8 -0
- package/dist/components/index.js.map +1 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use_notes.d.ts +46 -0
- package/dist/hooks/use_notes.d.ts.map +1 -0
- package/dist/hooks/use_notes.js +146 -0
- package/dist/hooks/use_notes.js.map +1 -0
- package/dist/hooks/use_notes_file_upload.d.ts +52 -0
- package/dist/hooks/use_notes_file_upload.d.ts.map +1 -0
- package/dist/hooks/use_notes_file_upload.js +125 -0
- package/dist/hooks/use_notes_file_upload.js.map +1 -0
- package/dist/index.client.d.ts +16 -0
- package/dist/index.client.d.ts.map +1 -0
- package/dist/index.client.js +18 -0
- package/dist/index.client.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +31 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +123 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/logger/context.d.ts +49 -0
- package/dist/logger/context.d.ts.map +1 -0
- package/dist/logger/context.js +45 -0
- package/dist/logger/context.js.map +1 -0
- package/dist/logger/index.d.ts +9 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +7 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/server.d.ts +27 -0
- package/dist/logger/server.d.ts.map +1 -0
- package/dist/logger/server.js +36 -0
- package/dist/logger/server.js.map +1 -0
- package/dist/logger/types.d.ts +20 -0
- package/dist/logger/types.d.ts.map +1 -0
- package/dist/logger/types.js +15 -0
- package/dist/logger/types.js.map +1 -0
- package/dist/types/index.d.ts +267 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cn.d.ts +16 -0
- package/dist/utils/cn.d.ts.map +1 -0
- package/dist/utils/cn.js +19 -0
- package/dist/utils/cn.js.map +1 -0
- package/dist/utils/file_utils.d.ts +51 -0
- package/dist/utils/file_utils.d.ts.map +1 -0
- package/dist/utils/file_utils.js +128 -0
- package/dist/utils/file_utils.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -0
- package/migrations/001_create_hazo_notes_table.sql +77 -0
- package/package.json +119 -0
- package/templates/config/hazo_notes_config.ini +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
# hazo_notes
|
|
2
|
+
|
|
3
|
+
Database-backed notes system with file attachment support for Next.js applications in the hazo ecosystem.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Database-backed persistence** - Notes stored in PostgreSQL or SQLite via hazo_connect
|
|
8
|
+
- **File attachments** - Support for images, PDFs, and documents with embed/attach modes
|
|
9
|
+
- **Flexible UI styles** - Choose between popover or slide panel presentation
|
|
10
|
+
- **Smart save modes** - Explicit save/cancel buttons or auto-save on blur
|
|
11
|
+
- **INI-based configuration** - Simple config file for all settings
|
|
12
|
+
- **Full TypeScript support** - Complete type definitions included
|
|
13
|
+
- **Controlled and uncontrolled modes** - Works with parent state or manages its own
|
|
14
|
+
- **Paste-to-embed images** - Paste images directly into notes
|
|
15
|
+
- **User attribution** - Automatic user profiles with avatars (optional)
|
|
16
|
+
- **File reference syntax** - Inline file references with `<<embed:XXXX>>` and `<<attach:XXXX>>`
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
Before installing hazo_notes, ensure you have:
|
|
21
|
+
|
|
22
|
+
- Next.js 14+ with React 18+
|
|
23
|
+
- Tailwind CSS configured
|
|
24
|
+
- **hazo_connect** installed and configured (for database access)
|
|
25
|
+
- PostgreSQL or SQLite database
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install hazo_notes
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Peer Dependencies
|
|
34
|
+
|
|
35
|
+
Install these based on your needs:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Required for UI components
|
|
39
|
+
npm install @radix-ui/react-popover @radix-ui/react-dialog react-icons
|
|
40
|
+
|
|
41
|
+
# Recommended for full functionality
|
|
42
|
+
npm install hazo_connect hazo_auth hazo_logs
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### 1. Add the Component
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { HazoNotesIcon } from 'hazo_notes';
|
|
51
|
+
|
|
52
|
+
function MyComponent() {
|
|
53
|
+
return (
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
<h2>Customer Information</h2>
|
|
56
|
+
<HazoNotesIcon
|
|
57
|
+
ref_id="customer-info-section"
|
|
58
|
+
label="Customer Information"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Create API Route
|
|
66
|
+
|
|
67
|
+
Create `app/api/hazo_notes/[ref_id]/route.ts`:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { createNotesHandler } from 'hazo_notes/api';
|
|
71
|
+
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
72
|
+
// Import your auth and user lookup functions
|
|
73
|
+
import { getSession } from '@/lib/auth'; // Replace with your auth
|
|
74
|
+
import { getUserById } from '@/lib/users'; // Replace with your user lookup
|
|
75
|
+
|
|
76
|
+
export const dynamic = 'force-dynamic';
|
|
77
|
+
|
|
78
|
+
const { GET, POST } = createNotesHandler({
|
|
79
|
+
getHazoConnect: () => getHazoConnectSingleton(),
|
|
80
|
+
getUserIdFromRequest: async (req) => {
|
|
81
|
+
// IMPORTANT: Replace with your authentication logic
|
|
82
|
+
const session = await getSession(req);
|
|
83
|
+
return session?.user?.id || null;
|
|
84
|
+
},
|
|
85
|
+
getUserProfile: async (userId) => {
|
|
86
|
+
// IMPORTANT: Replace with your user profile lookup
|
|
87
|
+
const user = await getUserById(userId);
|
|
88
|
+
return {
|
|
89
|
+
id: userId,
|
|
90
|
+
name: user?.name || 'Unknown User',
|
|
91
|
+
email: user?.email || '',
|
|
92
|
+
profile_image: user?.avatar,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export { GET, POST };
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. Set Up Database
|
|
101
|
+
|
|
102
|
+
Run the migration:
|
|
103
|
+
|
|
104
|
+
**PostgreSQL:**
|
|
105
|
+
```sql
|
|
106
|
+
CREATE TABLE hazo_notes (
|
|
107
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
108
|
+
ref_id UUID NOT NULL,
|
|
109
|
+
note JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
110
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
111
|
+
changed_at TIMESTAMPTZ,
|
|
112
|
+
note_count INTEGER NOT NULL DEFAULT 0
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX idx_hazo_notes_ref_id ON hazo_notes(ref_id);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**SQLite:**
|
|
119
|
+
```sql
|
|
120
|
+
CREATE TABLE IF NOT EXISTS hazo_notes (
|
|
121
|
+
id TEXT PRIMARY KEY,
|
|
122
|
+
ref_id TEXT NOT NULL,
|
|
123
|
+
note TEXT NOT NULL DEFAULT '[]',
|
|
124
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
125
|
+
changed_at TEXT,
|
|
126
|
+
note_count INTEGER NOT NULL DEFAULT 0
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_notes_ref_id ON hazo_notes(ref_id);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Configure (Optional)
|
|
133
|
+
|
|
134
|
+
Copy the config template:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
mkdir -p config
|
|
138
|
+
cp node_modules/hazo_notes/templates/config/hazo_notes_config.ini config/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Edit `config/hazo_notes_config.ini` to customize behavior.
|
|
142
|
+
|
|
143
|
+
For detailed setup instructions, see [SETUP_CHECKLIST.md](./SETUP_CHECKLIST.md).
|
|
144
|
+
|
|
145
|
+
## Usage Examples
|
|
146
|
+
|
|
147
|
+
### Basic Usage
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { HazoNotesIcon } from 'hazo_notes';
|
|
151
|
+
|
|
152
|
+
export default function FormPage() {
|
|
153
|
+
return (
|
|
154
|
+
<div className="space-y-4">
|
|
155
|
+
<div className="flex items-center justify-between">
|
|
156
|
+
<label>Annual Income</label>
|
|
157
|
+
<HazoNotesIcon
|
|
158
|
+
ref_id="income-field"
|
|
159
|
+
label="Annual Income"
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
<input type="number" name="income" />
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Result**: Click the notes icon to add contextual notes about this field.
|
|
169
|
+
|
|
170
|
+
### With File Attachments
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
<HazoNotesIcon
|
|
174
|
+
ref_id="contract-review"
|
|
175
|
+
label="Contract Review"
|
|
176
|
+
enable_files={true}
|
|
177
|
+
max_files_per_note={5}
|
|
178
|
+
allowed_file_types={['pdf', 'docx', 'png', 'jpg']}
|
|
179
|
+
max_file_size_mb={10}
|
|
180
|
+
/>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Features**:
|
|
184
|
+
- Upload files via file picker or paste images
|
|
185
|
+
- Files referenced in note text with `<<embed:0001>>` or `<<attach:0001>>`
|
|
186
|
+
- Images display inline, other files show as download links
|
|
187
|
+
|
|
188
|
+
### Slide Panel Style
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
<HazoNotesIcon
|
|
192
|
+
ref_id="detailed-notes"
|
|
193
|
+
label="Detailed Notes"
|
|
194
|
+
panel_style="slide_panel"
|
|
195
|
+
/>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Result**: Notes open in a slide-out panel instead of a popover.
|
|
199
|
+
|
|
200
|
+
### Auto-Save Mode
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
<HazoNotesIcon
|
|
204
|
+
ref_id="quick-notes"
|
|
205
|
+
label="Quick Notes"
|
|
206
|
+
save_mode="auto"
|
|
207
|
+
/>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Result**: Notes save automatically when panel closes (no save/cancel buttons).
|
|
211
|
+
|
|
212
|
+
### Custom Styling
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<HazoNotesIcon
|
|
216
|
+
ref_id="styled-notes"
|
|
217
|
+
label="Styled Notes"
|
|
218
|
+
background_color="bg-blue-50"
|
|
219
|
+
className="ml-2"
|
|
220
|
+
/>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Controlled Mode
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
'use client';
|
|
227
|
+
|
|
228
|
+
import { useState } from 'react';
|
|
229
|
+
import { HazoNotesIcon } from 'hazo_notes';
|
|
230
|
+
import type { NoteEntry } from 'hazo_notes/types';
|
|
231
|
+
|
|
232
|
+
export default function ControlledExample() {
|
|
233
|
+
const [notes, setNotes] = useState<NoteEntry[]>([]);
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<HazoNotesIcon
|
|
237
|
+
ref_id="controlled-notes"
|
|
238
|
+
label="Controlled Notes"
|
|
239
|
+
notes={notes}
|
|
240
|
+
on_notes_change={setNotes}
|
|
241
|
+
/>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Use Case**: Sync notes with parent component state or external state management.
|
|
247
|
+
|
|
248
|
+
## Configuration
|
|
249
|
+
|
|
250
|
+
Configuration options in `config/hazo_notes_config.ini`:
|
|
251
|
+
|
|
252
|
+
```ini
|
|
253
|
+
[ui]
|
|
254
|
+
# Background color for notes panel (Tailwind CSS class)
|
|
255
|
+
background_color = bg-yellow-100
|
|
256
|
+
|
|
257
|
+
# Panel presentation style: popover | slide_panel
|
|
258
|
+
panel_style = popover
|
|
259
|
+
|
|
260
|
+
# Save behavior: explicit | auto
|
|
261
|
+
save_mode = explicit
|
|
262
|
+
|
|
263
|
+
[storage]
|
|
264
|
+
# File storage mode: jsonb | filesystem
|
|
265
|
+
file_storage_mode = jsonb
|
|
266
|
+
|
|
267
|
+
# Path for filesystem storage (only used when file_storage_mode = filesystem)
|
|
268
|
+
file_storage_path = /uploads/notes
|
|
269
|
+
|
|
270
|
+
[files]
|
|
271
|
+
# Maximum file size in MB
|
|
272
|
+
max_file_size_mb = 10
|
|
273
|
+
|
|
274
|
+
# Allowed file types (comma-separated extensions, no dots)
|
|
275
|
+
allowed_file_types = pdf,png,jpg,jpeg,gif,doc,docx
|
|
276
|
+
|
|
277
|
+
# Maximum files per single note entry
|
|
278
|
+
max_files_per_note = 5
|
|
279
|
+
|
|
280
|
+
[logging]
|
|
281
|
+
# Log file path (relative to application root)
|
|
282
|
+
logfile = logs/hazo_notes.log
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Component API
|
|
286
|
+
|
|
287
|
+
### HazoNotesIcon Props
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
interface HazoNotesIconProps {
|
|
291
|
+
// Required
|
|
292
|
+
ref_id: string; // Unique identifier for this notes instance
|
|
293
|
+
|
|
294
|
+
// Display
|
|
295
|
+
label?: string; // Panel header label
|
|
296
|
+
has_notes?: boolean; // Show indicator when notes exist
|
|
297
|
+
note_count?: number; // Display count badge
|
|
298
|
+
|
|
299
|
+
// Controlled mode
|
|
300
|
+
notes?: NoteEntry[]; // Controlled notes array
|
|
301
|
+
on_notes_change?: (notes: NoteEntry[]) => void;
|
|
302
|
+
|
|
303
|
+
// User context
|
|
304
|
+
current_user?: NoteUserInfo; // User info (auto-fetched if not provided)
|
|
305
|
+
|
|
306
|
+
// Configuration overrides
|
|
307
|
+
panel_style?: 'popover' | 'slide_panel';
|
|
308
|
+
save_mode?: 'explicit' | 'auto';
|
|
309
|
+
background_color?: string; // Tailwind class
|
|
310
|
+
|
|
311
|
+
// File options
|
|
312
|
+
enable_files?: boolean; // Enable file attachments
|
|
313
|
+
max_files_per_note?: number;
|
|
314
|
+
allowed_file_types?: string[];
|
|
315
|
+
max_file_size_mb?: number;
|
|
316
|
+
|
|
317
|
+
// Callbacks
|
|
318
|
+
on_open?: () => void;
|
|
319
|
+
on_close?: () => void;
|
|
320
|
+
|
|
321
|
+
// Styling
|
|
322
|
+
disabled?: boolean;
|
|
323
|
+
className?: string;
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Hooks API
|
|
328
|
+
|
|
329
|
+
### use_notes
|
|
330
|
+
|
|
331
|
+
Manages notes state and API interactions.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { use_notes } from 'hazo_notes/hooks';
|
|
335
|
+
|
|
336
|
+
function MyComponent({ refId }: { refId: string }) {
|
|
337
|
+
const {
|
|
338
|
+
notes,
|
|
339
|
+
note_count,
|
|
340
|
+
loading,
|
|
341
|
+
error,
|
|
342
|
+
add_note,
|
|
343
|
+
refresh,
|
|
344
|
+
} = use_notes(refId);
|
|
345
|
+
|
|
346
|
+
const handleAddNote = async () => {
|
|
347
|
+
const success = await add_note('This is my note');
|
|
348
|
+
if (success) {
|
|
349
|
+
console.log('Note added!');
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
if (loading) return <div>Loading notes...</div>;
|
|
354
|
+
if (error) return <div>Error: {error}</div>;
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<div>
|
|
358
|
+
<p>{note_count} notes</p>
|
|
359
|
+
<button onClick={handleAddNote}>Add Note</button>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### use_notes_file_upload
|
|
366
|
+
|
|
367
|
+
Handles file uploads and validation.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { use_notes_file_upload } from 'hazo_notes/hooks';
|
|
371
|
+
|
|
372
|
+
function FileUploadExample() {
|
|
373
|
+
const {
|
|
374
|
+
pending_files,
|
|
375
|
+
upload_file,
|
|
376
|
+
remove_file,
|
|
377
|
+
uploading,
|
|
378
|
+
error,
|
|
379
|
+
} = use_notes_file_upload({
|
|
380
|
+
ref_id: 'my-notes',
|
|
381
|
+
max_file_size_mb: 5,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
385
|
+
const file = e.target.files?.[0];
|
|
386
|
+
if (file) {
|
|
387
|
+
const uploaded = await upload_file(file, 'attachment');
|
|
388
|
+
if (uploaded) {
|
|
389
|
+
console.log('File uploaded:', uploaded.filename);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<div>
|
|
396
|
+
<input type="file" onChange={handleFileSelect} disabled={uploading} />
|
|
397
|
+
{pending_files.map(f => (
|
|
398
|
+
<div key={f.file_no}>
|
|
399
|
+
{f.filename}
|
|
400
|
+
<button onClick={() => remove_file(f.file_no)}>Remove</button>
|
|
401
|
+
</div>
|
|
402
|
+
))}
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## File Attachments
|
|
409
|
+
|
|
410
|
+
Notes support inline file references in text:
|
|
411
|
+
|
|
412
|
+
### Embed Mode (Images Inline)
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
Check out this screenshot:
|
|
416
|
+
<<embed:0001>>
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Result**: Image displays directly in the note.
|
|
420
|
+
|
|
421
|
+
### Attach Mode (Download Link)
|
|
422
|
+
|
|
423
|
+
```
|
|
424
|
+
Download the full report:
|
|
425
|
+
<<attach:0001>>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**Result**: Shows as a clickable download link with file icon.
|
|
429
|
+
|
|
430
|
+
### Paste to Embed
|
|
431
|
+
|
|
432
|
+
Users can paste images directly into the note textarea - they're automatically uploaded and referenced with `<<embed:XXXX>>` syntax.
|
|
433
|
+
|
|
434
|
+
## File Storage Modes
|
|
435
|
+
|
|
436
|
+
### JSONB Mode (Default)
|
|
437
|
+
|
|
438
|
+
```ini
|
|
439
|
+
[storage]
|
|
440
|
+
file_storage_mode = jsonb
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- Files stored as Base64 in database
|
|
444
|
+
- Simpler setup (no file API needed)
|
|
445
|
+
- Good for small files (< 1MB)
|
|
446
|
+
- Works out of the box
|
|
447
|
+
|
|
448
|
+
### Filesystem Mode
|
|
449
|
+
|
|
450
|
+
```ini
|
|
451
|
+
[storage]
|
|
452
|
+
file_storage_mode = filesystem
|
|
453
|
+
file_storage_path = /uploads/notes
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
- Files stored on server filesystem
|
|
457
|
+
- Better for large files
|
|
458
|
+
- Requires file upload API route
|
|
459
|
+
|
|
460
|
+
Create `app/api/hazo_notes/files/upload/route.ts`:
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { createFilesHandler } from 'hazo_notes/api';
|
|
464
|
+
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
465
|
+
|
|
466
|
+
export const dynamic = 'force-dynamic';
|
|
467
|
+
|
|
468
|
+
const { POST } = createFilesHandler({
|
|
469
|
+
getHazoConnect: () => getHazoConnectSingleton(),
|
|
470
|
+
getUserIdFromRequest: async (req) => {
|
|
471
|
+
const session = await getSession(req);
|
|
472
|
+
return session?.user?.id || null;
|
|
473
|
+
},
|
|
474
|
+
file_storage_mode: 'filesystem',
|
|
475
|
+
file_storage_path: '/uploads/notes',
|
|
476
|
+
max_file_size_mb: 10,
|
|
477
|
+
allowed_file_types: ['pdf', 'png', 'jpg', 'jpeg', 'gif'],
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
export { POST };
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Logger Integration (Optional)
|
|
484
|
+
|
|
485
|
+
### Client-Side
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
// app/providers.tsx
|
|
489
|
+
'use client';
|
|
490
|
+
|
|
491
|
+
import { LoggerProvider } from 'hazo_notes';
|
|
492
|
+
import { createClientLogger } from 'hazo_logs/ui';
|
|
493
|
+
|
|
494
|
+
const logger = createClientLogger({ packageName: 'my_app' });
|
|
495
|
+
|
|
496
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
497
|
+
return (
|
|
498
|
+
<LoggerProvider logger={logger}>
|
|
499
|
+
{children}
|
|
500
|
+
</LoggerProvider>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Server-Side
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
// lib/logger-setup.ts
|
|
509
|
+
import { set_server_logger } from 'hazo_notes/lib';
|
|
510
|
+
import { createLogger } from 'hazo_logs';
|
|
511
|
+
|
|
512
|
+
export function initializeLogger() {
|
|
513
|
+
set_server_logger(createLogger('hazo_notes'));
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## TypeScript Types
|
|
518
|
+
|
|
519
|
+
All types are exported from `hazo_notes/types`:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
import type {
|
|
523
|
+
NoteEntry,
|
|
524
|
+
NoteFile,
|
|
525
|
+
NoteUserInfo,
|
|
526
|
+
HazoNotesIconProps,
|
|
527
|
+
HazoNotesPanelProps,
|
|
528
|
+
} from 'hazo_notes/types';
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Database Schema
|
|
532
|
+
|
|
533
|
+
The `hazo_notes` table stores all notes:
|
|
534
|
+
|
|
535
|
+
```sql
|
|
536
|
+
CREATE TABLE hazo_notes (
|
|
537
|
+
id UUID PRIMARY KEY,
|
|
538
|
+
ref_id UUID NOT NULL, -- Links to parent entity
|
|
539
|
+
note JSONB NOT NULL DEFAULT '[]', -- Array of note entries
|
|
540
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
541
|
+
changed_at TIMESTAMPTZ,
|
|
542
|
+
note_count INTEGER NOT NULL DEFAULT 0
|
|
543
|
+
);
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Each note entry in the JSONB array:
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
{
|
|
550
|
+
userid: "user-uuid",
|
|
551
|
+
created_at: "2026-01-07T12:30:00.000Z",
|
|
552
|
+
note_text: "This is the note content",
|
|
553
|
+
note_files: [
|
|
554
|
+
{
|
|
555
|
+
file_no: "0001",
|
|
556
|
+
embed_type: "embed",
|
|
557
|
+
filename: "screenshot.png",
|
|
558
|
+
filedata: "base64_data_or_file_path",
|
|
559
|
+
mime_type: "image/png",
|
|
560
|
+
file_size: 12345
|
|
561
|
+
}
|
|
562
|
+
]
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
## Troubleshooting
|
|
567
|
+
|
|
568
|
+
### Notes icon doesn't open panel
|
|
569
|
+
|
|
570
|
+
**Problem**: Icon renders but clicking does nothing.
|
|
571
|
+
|
|
572
|
+
**Solution**: Install required UI components:
|
|
573
|
+
```bash
|
|
574
|
+
npm install @radix-ui/react-popover @radix-ui/react-dialog
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### User shows as "Unknown User"
|
|
578
|
+
|
|
579
|
+
**Problem**: Notes display but no user names.
|
|
580
|
+
|
|
581
|
+
**Solution**: Implement `getUserProfile` in your API handler:
|
|
582
|
+
```typescript
|
|
583
|
+
getUserProfile: async (userId) => {
|
|
584
|
+
const user = await fetchUserFromDatabase(userId);
|
|
585
|
+
return {
|
|
586
|
+
id: userId,
|
|
587
|
+
name: user.name,
|
|
588
|
+
email: user.email,
|
|
589
|
+
profile_image: user.avatar,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Notes don't persist
|
|
595
|
+
|
|
596
|
+
**Problem**: Notes disappear after refresh.
|
|
597
|
+
|
|
598
|
+
**Solution**:
|
|
599
|
+
1. Verify database table exists (run migration)
|
|
600
|
+
2. Check `ref_id` is consistent
|
|
601
|
+
3. Verify API route is working: `curl http://localhost:3000/api/hazo_notes/test-id`
|
|
602
|
+
|
|
603
|
+
### File upload fails
|
|
604
|
+
|
|
605
|
+
**Problem**: Can't upload files.
|
|
606
|
+
|
|
607
|
+
**Solution**:
|
|
608
|
+
- For JSONB mode: Should work out of the box
|
|
609
|
+
- For filesystem mode: Create the files upload API route (see File Storage Modes above)
|
|
610
|
+
|
|
611
|
+
### Authentication errors
|
|
612
|
+
|
|
613
|
+
**Problem**: "Unauthorized" when adding notes.
|
|
614
|
+
|
|
615
|
+
**Solution**: Implement `getUserIdFromRequest` to return authenticated user ID:
|
|
616
|
+
```typescript
|
|
617
|
+
getUserIdFromRequest: async (req) => {
|
|
618
|
+
const session = await getSession(req);
|
|
619
|
+
if (!session?.user?.id) return null;
|
|
620
|
+
return session.user.id;
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## Examples
|
|
625
|
+
|
|
626
|
+
See the `test-app/` directory for complete working examples:
|
|
627
|
+
|
|
628
|
+
- **Basic notes**: Simple note creation and display
|
|
629
|
+
- **Popover style**: Notes in a popover
|
|
630
|
+
- **Slide panel style**: Notes in a slide-out panel
|
|
631
|
+
- **With files**: File attachment demonstrations
|
|
632
|
+
- **Auto-save**: Auto-save mode example
|
|
633
|
+
- **Multiple instances**: Multiple independent notes on one page
|
|
634
|
+
- **Controlled mode**: Parent state integration
|
|
635
|
+
|
|
636
|
+
Run the test app:
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
npm run dev:test-app
|
|
640
|
+
# Open http://localhost:3002
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Contributing
|
|
644
|
+
|
|
645
|
+
See [SETUP_CHECKLIST.md](./SETUP_CHECKLIST.md) for development setup instructions.
|
|
646
|
+
|
|
647
|
+
## License
|
|
648
|
+
|
|
649
|
+
MIT
|
|
650
|
+
|
|
651
|
+
## Related Packages
|
|
652
|
+
|
|
653
|
+
- [hazo_connect](https://github.com/pub12/hazo_connect) - Database connection abstraction
|
|
654
|
+
- [hazo_auth](https://github.com/pub12/hazo_auth) - Authentication and user management
|
|
655
|
+
- [hazo_logs](https://github.com/pub12/hazo_logs) - Structured logging
|
|
656
|
+
- [hazo_config](https://github.com/pub12/hazo_config) - INI configuration management
|
|
657
|
+
|
|
658
|
+
## Support
|
|
659
|
+
|
|
660
|
+
- Issues: https://github.com/pub12/hazo_notes/issues
|
|
661
|
+
- Documentation: https://github.com/pub12/hazo_notes#readme
|