markdown-notes-engine 1.0.2 → 2.0.1
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 +554 -119
- package/lib/backend/db/connection.js +127 -0
- package/lib/backend/db/schema.sql +144 -0
- package/lib/backend/github.js +2 -4
- package/lib/backend/index.js +69 -28
- package/lib/backend/markdown.js +4 -6
- package/lib/backend/routes/notes.js +35 -4
- package/lib/backend/routes/search.js +2 -2
- package/lib/backend/routes/upload.js +33 -6
- package/lib/backend/storage.js +2 -4
- package/lib/backend/version-control.js +458 -0
- package/lib/frontend/index.js +3 -6
- package/lib/index.js +5 -16
- package/package.json +20 -30
- package/lib/backend/github.mjs +0 -316
- package/lib/backend/index.mjs +0 -74
- package/lib/backend/markdown.mjs +0 -60
- package/lib/backend/routes/notes.mjs +0 -197
- package/lib/backend/routes/search.mjs +0 -28
- package/lib/backend/routes/upload.mjs +0 -122
- package/lib/backend/storage.mjs +0 -119
- package/lib/frontend/index.mjs +0 -15
- package/lib/index.mjs +0 -17
|
@@ -1,122 +0,0 @@
|
|
|
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;
|
package/lib/backend/storage.mjs
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
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
|
-
}
|
package/lib/frontend/index.mjs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
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';
|