markdown-notes-engine 1.0.1 → 1.0.2
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 +55 -2
- package/lib/README.md +123 -2
- package/lib/backend/github.mjs +316 -0
- package/lib/backend/index.mjs +74 -0
- package/lib/backend/markdown.mjs +60 -0
- package/lib/backend/routes/notes.mjs +197 -0
- package/lib/backend/routes/search.mjs +28 -0
- package/lib/backend/routes/upload.mjs +122 -0
- package/lib/backend/storage.mjs +119 -0
- package/lib/frontend/index.mjs +15 -0
- package/lib/index.mjs +17 -0
- package/package.json +30 -2
- package/lib/frontend/styles.css +0 -431
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notes Routes
|
|
3
|
+
* Handles note CRUD operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import express from 'express';
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
// Get file structure
|
|
10
|
+
router.get('/structure', async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const { githubClient } = req.notesEngine;
|
|
13
|
+
const structure = await githubClient.getFileStructure();
|
|
14
|
+
res.json({ structure });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error('Error fetching structure:', error);
|
|
17
|
+
res.status(500).json({ error: 'Failed to fetch file structure' });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Get note content
|
|
22
|
+
router.get('/note', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const { path } = req.query;
|
|
25
|
+
if (!path) {
|
|
26
|
+
return res.status(400).json({ error: 'Path is required' });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { githubClient } = req.notesEngine;
|
|
30
|
+
const file = await githubClient.getFile(path);
|
|
31
|
+
|
|
32
|
+
if (!file) {
|
|
33
|
+
return res.status(404).json({ error: 'Note not found' });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
res.json(file);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Error fetching note:', error);
|
|
39
|
+
res.status(500).json({ error: 'Failed to fetch note' });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Save note
|
|
44
|
+
router.post('/note', async (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
let { path, content } = req.body;
|
|
47
|
+
|
|
48
|
+
if (!path || content === undefined) {
|
|
49
|
+
return res.status(400).json({ error: 'Path and content are required' });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Ensure .md extension
|
|
53
|
+
if (!path.endsWith('.md')) {
|
|
54
|
+
path += '.md';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { githubClient, options } = req.notesEngine;
|
|
58
|
+
|
|
59
|
+
// Get existing file SHA if it exists
|
|
60
|
+
const existingFile = await githubClient.getFile(path);
|
|
61
|
+
const sha = existingFile ? existingFile.sha : null;
|
|
62
|
+
|
|
63
|
+
// Save the file
|
|
64
|
+
await githubClient.saveFile(path, content, sha);
|
|
65
|
+
|
|
66
|
+
// Auto-update README if enabled
|
|
67
|
+
if (options.autoUpdateReadme !== false && path !== 'README.md') {
|
|
68
|
+
try {
|
|
69
|
+
await updateReadme(githubClient);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error updating README:', error);
|
|
72
|
+
// Don't fail the request if README update fails
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
res.json({ success: true, path });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Error saving note:', error);
|
|
79
|
+
res.status(500).json({ error: 'Failed to save note' });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Delete note
|
|
84
|
+
router.delete('/note', async (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
const { path } = req.body;
|
|
87
|
+
|
|
88
|
+
if (!path) {
|
|
89
|
+
return res.status(400).json({ error: 'Path is required' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { githubClient } = req.notesEngine;
|
|
93
|
+
|
|
94
|
+
// Get file SHA
|
|
95
|
+
const file = await githubClient.getFile(path);
|
|
96
|
+
if (!file) {
|
|
97
|
+
return res.status(404).json({ error: 'Note not found' });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await githubClient.deleteFile(path, file.sha);
|
|
101
|
+
|
|
102
|
+
res.json({ success: true });
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Error deleting note:', error);
|
|
105
|
+
res.status(500).json({ error: 'Failed to delete note' });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Create folder
|
|
110
|
+
router.post('/folder', async (req, res) => {
|
|
111
|
+
try {
|
|
112
|
+
const { path } = req.body;
|
|
113
|
+
|
|
114
|
+
if (!path) {
|
|
115
|
+
return res.status(400).json({ error: 'Path is required' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { githubClient } = req.notesEngine;
|
|
119
|
+
await githubClient.createFolder(path);
|
|
120
|
+
|
|
121
|
+
res.json({ success: true });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('Error creating folder:', error);
|
|
124
|
+
res.status(500).json({ error: 'Failed to create folder' });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Delete folder
|
|
129
|
+
router.delete('/folder', async (req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
const { path } = req.body;
|
|
132
|
+
|
|
133
|
+
if (!path) {
|
|
134
|
+
return res.status(400).json({ error: 'Path is required' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { githubClient } = req.notesEngine;
|
|
138
|
+
await githubClient.deleteFolder(path);
|
|
139
|
+
|
|
140
|
+
res.json({ success: true });
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('Error deleting folder:', error);
|
|
143
|
+
res.status(500).json({ error: 'Failed to delete folder' });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Render markdown to HTML
|
|
148
|
+
router.post('/render', async (req, res) => {
|
|
149
|
+
try {
|
|
150
|
+
const { markdown } = req.body;
|
|
151
|
+
|
|
152
|
+
if (markdown === undefined) {
|
|
153
|
+
return res.status(400).json({ error: 'Markdown content is required' });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { markdownRenderer } = req.notesEngine;
|
|
157
|
+
const html = markdownRenderer.render(markdown);
|
|
158
|
+
|
|
159
|
+
res.json({ html });
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('Error rendering markdown:', error);
|
|
162
|
+
res.status(500).json({ error: 'Failed to render markdown' });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Auto-update README with list of notes
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
async function updateReadme(githubClient) {
|
|
171
|
+
const structure = await githubClient.getFileStructure();
|
|
172
|
+
|
|
173
|
+
let readmeContent = '# Notes\n\n';
|
|
174
|
+
readmeContent += 'This is an auto-generated index of all notes.\n\n';
|
|
175
|
+
|
|
176
|
+
const mdFiles = structure
|
|
177
|
+
.filter(item => item.type === 'file' && item.name.endsWith('.md') && item.name !== 'README.md')
|
|
178
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
179
|
+
|
|
180
|
+
if (mdFiles.length === 0) {
|
|
181
|
+
readmeContent += '*No notes yet.*\n';
|
|
182
|
+
} else {
|
|
183
|
+
mdFiles.forEach(file => {
|
|
184
|
+
const displayName = file.name.replace('.md', '');
|
|
185
|
+
readmeContent += `- [${displayName}](${file.path})\n`;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
readmeContent += `\n---\n*Last updated: ${new Date().toISOString()}*\n`;
|
|
190
|
+
|
|
191
|
+
const existingReadme = await githubClient.getFile('README.md');
|
|
192
|
+
const sha = existingReadme ? existingReadme.sha : null;
|
|
193
|
+
|
|
194
|
+
await githubClient.saveFile('README.md', readmeContent, sha);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default router;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Routes
|
|
3
|
+
* Handles note searching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import express from 'express';
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
// Search notes
|
|
10
|
+
router.get('/search', async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const { q: query } = req.query;
|
|
13
|
+
|
|
14
|
+
if (!query) {
|
|
15
|
+
return res.status(400).json({ error: 'Query parameter "q" is required' });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { githubClient } = req.notesEngine;
|
|
19
|
+
const results = await githubClient.searchNotes(query);
|
|
20
|
+
|
|
21
|
+
res.json({ results });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Error searching notes:', error);
|
|
24
|
+
res.status(500).json({ error: 'Failed to search notes' });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default router;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload Routes
|
|
3
|
+
* Handles image and video uploads
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import fileUpload from 'express-fileupload';
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
// File upload middleware
|
|
11
|
+
router.use(fileUpload());
|
|
12
|
+
|
|
13
|
+
// Upload image (multipart form)
|
|
14
|
+
router.post('/upload-image', async (req, res) => {
|
|
15
|
+
try {
|
|
16
|
+
if (!req.files || !req.files.image) {
|
|
17
|
+
return res.status(400).json({ error: 'No image file provided' });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const imageFile = req.files.image;
|
|
21
|
+
const folder = req.body.folder || '';
|
|
22
|
+
|
|
23
|
+
const { storageClient } = req.notesEngine;
|
|
24
|
+
const imageUrl = await storageClient.uploadImage(
|
|
25
|
+
imageFile.data,
|
|
26
|
+
imageFile.name,
|
|
27
|
+
folder
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
res.json({ imageUrl });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Error uploading image:', error);
|
|
33
|
+
res.status(500).json({ error: 'Failed to upload image' });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Upload image (base64)
|
|
38
|
+
router.post('/upload-image-base64', async (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { image, filename, folder = '' } = req.body;
|
|
41
|
+
|
|
42
|
+
if (!image || !filename) {
|
|
43
|
+
return res.status(400).json({ error: 'Image data and filename are required' });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Remove data URL prefix if present
|
|
47
|
+
const base64Data = image.replace(/^data:image\/\w+;base64,/, '');
|
|
48
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
49
|
+
|
|
50
|
+
const { storageClient } = req.notesEngine;
|
|
51
|
+
const imageUrl = await storageClient.uploadImage(buffer, filename, folder);
|
|
52
|
+
|
|
53
|
+
res.json({ imageUrl });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Error uploading image:', error);
|
|
56
|
+
res.status(500).json({ error: 'Failed to upload image' });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Upload video
|
|
61
|
+
router.post('/upload-video', async (req, res) => {
|
|
62
|
+
try {
|
|
63
|
+
if (!req.files || !req.files.video) {
|
|
64
|
+
return res.status(400).json({ error: 'No video file provided' });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const videoFile = req.files.video;
|
|
68
|
+
const folder = req.body.folder || '';
|
|
69
|
+
|
|
70
|
+
const { storageClient } = req.notesEngine;
|
|
71
|
+
const videoUrl = await storageClient.uploadVideo(
|
|
72
|
+
videoFile.data,
|
|
73
|
+
videoFile.name,
|
|
74
|
+
folder
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
res.json({ videoUrl });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Error uploading video:', error);
|
|
80
|
+
res.status(500).json({ error: 'Failed to upload video' });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Delete image
|
|
85
|
+
router.delete('/image', async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const { imageUrl } = req.body;
|
|
88
|
+
|
|
89
|
+
if (!imageUrl) {
|
|
90
|
+
return res.status(400).json({ error: 'Image URL is required' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { storageClient } = req.notesEngine;
|
|
94
|
+
await storageClient.deleteFile(imageUrl);
|
|
95
|
+
|
|
96
|
+
res.json({ success: true });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Error deleting image:', error);
|
|
99
|
+
res.status(500).json({ error: 'Failed to delete image' });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Delete video
|
|
104
|
+
router.delete('/video', async (req, res) => {
|
|
105
|
+
try {
|
|
106
|
+
const { videoUrl } = req.body;
|
|
107
|
+
|
|
108
|
+
if (!videoUrl) {
|
|
109
|
+
return res.status(400).json({ error: 'Video URL is required' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { storageClient } = req.notesEngine;
|
|
113
|
+
await storageClient.deleteFile(videoUrl);
|
|
114
|
+
|
|
115
|
+
res.json({ success: true });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Error deleting video:', error);
|
|
118
|
+
res.status(500).json({ error: 'Failed to delete video' });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export default router;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Client
|
|
3
|
+
* Handles file uploads to R2 or S3
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
7
|
+
|
|
8
|
+
export class StorageClient {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.publicUrl = config.publicUrl;
|
|
12
|
+
|
|
13
|
+
const s3Config = {
|
|
14
|
+
region: config.type === 'r2' ? 'auto' : (config.region || 'us-east-1'),
|
|
15
|
+
credentials: {
|
|
16
|
+
accessKeyId: config.accessKeyId,
|
|
17
|
+
secretAccessKey: config.secretAccessKey
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// R2-specific endpoint
|
|
22
|
+
if (config.type === 'r2') {
|
|
23
|
+
s3Config.endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.s3Client = new S3Client(s3Config);
|
|
27
|
+
this.bucketName = config.bucketName;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Upload an image file
|
|
32
|
+
* @param {Buffer} fileBuffer - File buffer
|
|
33
|
+
* @param {string} filename - Original filename
|
|
34
|
+
* @param {string} [folder=''] - Optional folder path
|
|
35
|
+
* @returns {Promise<string>} Public URL of uploaded file
|
|
36
|
+
*/
|
|
37
|
+
async uploadImage(fileBuffer, filename, folder = '') {
|
|
38
|
+
const timestamp = Date.now();
|
|
39
|
+
const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
40
|
+
const key = folder
|
|
41
|
+
? `images/${folder}/${timestamp}-${sanitizedFilename}`
|
|
42
|
+
: `images/${timestamp}-${sanitizedFilename}`;
|
|
43
|
+
|
|
44
|
+
await this.s3Client.send(new PutObjectCommand({
|
|
45
|
+
Bucket: this.bucketName,
|
|
46
|
+
Key: key,
|
|
47
|
+
Body: fileBuffer,
|
|
48
|
+
ContentType: this._getContentType(filename)
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
return `${this.publicUrl}/${key}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Upload a video file
|
|
56
|
+
* @param {Buffer} fileBuffer - File buffer
|
|
57
|
+
* @param {string} filename - Original filename
|
|
58
|
+
* @param {string} [folder=''] - Optional folder path
|
|
59
|
+
* @returns {Promise<string>} Public URL of uploaded file
|
|
60
|
+
*/
|
|
61
|
+
async uploadVideo(fileBuffer, filename, folder = '') {
|
|
62
|
+
const timestamp = Date.now();
|
|
63
|
+
const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
64
|
+
const key = folder
|
|
65
|
+
? `videos/${folder}/${timestamp}-${sanitizedFilename}`
|
|
66
|
+
: `videos/${timestamp}-${sanitizedFilename}`;
|
|
67
|
+
|
|
68
|
+
await this.s3Client.send(new PutObjectCommand({
|
|
69
|
+
Bucket: this.bucketName,
|
|
70
|
+
Key: key,
|
|
71
|
+
Body: fileBuffer,
|
|
72
|
+
ContentType: this._getContentType(filename)
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
return `${this.publicUrl}/${key}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Delete a file
|
|
80
|
+
* @param {string} fileUrl - Public URL of the file
|
|
81
|
+
* @returns {Promise<void>}
|
|
82
|
+
*/
|
|
83
|
+
async deleteFile(fileUrl) {
|
|
84
|
+
// Extract key from URL
|
|
85
|
+
const key = fileUrl.replace(this.publicUrl + '/', '');
|
|
86
|
+
|
|
87
|
+
await this.s3Client.send(new DeleteObjectCommand({
|
|
88
|
+
Bucket: this.bucketName,
|
|
89
|
+
Key: key
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get content type from filename
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
_getContentType(filename) {
|
|
98
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
99
|
+
|
|
100
|
+
const contentTypes = {
|
|
101
|
+
// Images
|
|
102
|
+
jpg: 'image/jpeg',
|
|
103
|
+
jpeg: 'image/jpeg',
|
|
104
|
+
png: 'image/png',
|
|
105
|
+
gif: 'image/gif',
|
|
106
|
+
webp: 'image/webp',
|
|
107
|
+
svg: 'image/svg+xml',
|
|
108
|
+
|
|
109
|
+
// Videos
|
|
110
|
+
mp4: 'video/mp4',
|
|
111
|
+
webm: 'video/webm',
|
|
112
|
+
mov: 'video/quicktime',
|
|
113
|
+
avi: 'video/x-msvideo',
|
|
114
|
+
mkv: 'video/x-matroska'
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return contentTypes[ext] || 'application/octet-stream';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Notes Engine - Frontend Module
|
|
3
|
+
*
|
|
4
|
+
* A markdown note-taking editor with GitHub integration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Note: This frontend module is browser-based and already supports both module systems.
|
|
8
|
+
// For ES modules in browser, you would import this, but the class itself works the same way.
|
|
9
|
+
// The actual implementation is in index.js and works in both environments.
|
|
10
|
+
|
|
11
|
+
// Re-export the NotesEditor class for ES module users
|
|
12
|
+
import { NotesEditor } from './index.js';
|
|
13
|
+
|
|
14
|
+
export { NotesEditor };
|
|
15
|
+
export default NotesEditor;
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Notes Engine
|
|
3
|
+
*
|
|
4
|
+
* A complete markdown note-taking solution with GitHub integration
|
|
5
|
+
* and media hosting (R2/S3).
|
|
6
|
+
*
|
|
7
|
+
* @module markdown-notes-engine
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Backend exports
|
|
11
|
+
export { createNotesRouter } from './backend/index.mjs';
|
|
12
|
+
export { GitHubClient } from './backend/github.mjs';
|
|
13
|
+
export { StorageClient } from './backend/storage.mjs';
|
|
14
|
+
export { MarkdownRenderer } from './backend/markdown.mjs';
|
|
15
|
+
|
|
16
|
+
// Frontend exports
|
|
17
|
+
export { NotesEditor } from './frontend/index.mjs';
|
package/package.json
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markdown-notes-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A complete markdown note-taking engine with GitHub integration and media hosting (R2/S3)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
|
+
"module": "lib/index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./lib/index.mjs",
|
|
10
|
+
"require": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./backend": {
|
|
13
|
+
"import": "./lib/backend/index.mjs",
|
|
14
|
+
"require": "./lib/backend/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./backend/github": {
|
|
17
|
+
"import": "./lib/backend/github.mjs",
|
|
18
|
+
"require": "./lib/backend/github.js"
|
|
19
|
+
},
|
|
20
|
+
"./backend/storage": {
|
|
21
|
+
"import": "./lib/backend/storage.mjs",
|
|
22
|
+
"require": "./lib/backend/storage.js"
|
|
23
|
+
},
|
|
24
|
+
"./backend/markdown": {
|
|
25
|
+
"import": "./lib/backend/markdown.mjs",
|
|
26
|
+
"require": "./lib/backend/markdown.js"
|
|
27
|
+
},
|
|
28
|
+
"./frontend": {
|
|
29
|
+
"import": "./lib/frontend/index.mjs",
|
|
30
|
+
"require": "./lib/frontend/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
6
33
|
"scripts": {
|
|
7
34
|
"start": "node server.js",
|
|
8
35
|
"dev": "nodemon server.js",
|
|
@@ -25,7 +52,8 @@
|
|
|
25
52
|
"url": "https://github.com/cdthomp1/markdown-notes-engine"
|
|
26
53
|
},
|
|
27
54
|
"files": [
|
|
28
|
-
"lib
|
|
55
|
+
"lib/**/*.js",
|
|
56
|
+
"lib/**/*.mjs",
|
|
29
57
|
"README.md",
|
|
30
58
|
"LICENSE"
|
|
31
59
|
],
|