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
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Control Client
|
|
3
|
+
* Git-like version control system backed by PostgreSQL
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
export class VersionControlClient {
|
|
9
|
+
constructor(db, branch = 'main', author = 'user') {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.branch = branch;
|
|
12
|
+
this.author = author;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hash content using SHA-256
|
|
17
|
+
* @private
|
|
18
|
+
* @param {string} content - Content to hash
|
|
19
|
+
* @returns {string} SHA-256 hash
|
|
20
|
+
*/
|
|
21
|
+
_hashContent(content) {
|
|
22
|
+
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the current commit for the branch
|
|
27
|
+
* @private
|
|
28
|
+
* @returns {Promise<string|null>} Commit ID or null
|
|
29
|
+
*/
|
|
30
|
+
async _getCurrentCommit() {
|
|
31
|
+
const result = await this.db.query(
|
|
32
|
+
'SELECT commit_id FROM branches WHERE name = $1',
|
|
33
|
+
[this.branch]
|
|
34
|
+
);
|
|
35
|
+
return result.rows.length > 0 ? result.rows[0].commit_id : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get all files in the current branch's latest commit
|
|
40
|
+
* @private
|
|
41
|
+
* @returns {Promise<Array>} Array of {path, blob_hash}
|
|
42
|
+
*/
|
|
43
|
+
async _getCurrentFiles() {
|
|
44
|
+
const commitId = await this._getCurrentCommit();
|
|
45
|
+
if (!commitId) return [];
|
|
46
|
+
|
|
47
|
+
const result = await this.db.query(
|
|
48
|
+
'SELECT path, blob_hash FROM trees WHERE commit_id = $1',
|
|
49
|
+
[commitId]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return result.rows;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a new commit with the given file tree
|
|
57
|
+
* @private
|
|
58
|
+
* @param {Array} files - Array of {path, blob_hash}
|
|
59
|
+
* @param {string} message - Commit message
|
|
60
|
+
* @returns {Promise<string>} New commit ID
|
|
61
|
+
*/
|
|
62
|
+
async _createCommit(files, message) {
|
|
63
|
+
const client = await this.db.getClient();
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await client.query('BEGIN');
|
|
67
|
+
|
|
68
|
+
const parentId = await this._getCurrentCommit();
|
|
69
|
+
|
|
70
|
+
// Create commit
|
|
71
|
+
const commitResult = await client.query(
|
|
72
|
+
`INSERT INTO commits (parent_id, message, author)
|
|
73
|
+
VALUES ($1, $2, $3)
|
|
74
|
+
RETURNING id`,
|
|
75
|
+
[parentId, message, this.author]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const commitId = commitResult.rows[0].id;
|
|
79
|
+
|
|
80
|
+
// Insert tree entries
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
await client.query(
|
|
83
|
+
`INSERT INTO trees (commit_id, path, blob_hash)
|
|
84
|
+
VALUES ($1, $2, $3)`,
|
|
85
|
+
[commitId, file.path, file.blob_hash]
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Update branch pointer
|
|
90
|
+
await client.query(
|
|
91
|
+
`UPDATE branches SET commit_id = $1, updated_at = NOW()
|
|
92
|
+
WHERE name = $2`,
|
|
93
|
+
[commitId, this.branch]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await client.query('COMMIT');
|
|
97
|
+
return commitId;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
await client.query('ROLLBACK');
|
|
100
|
+
throw error;
|
|
101
|
+
} finally {
|
|
102
|
+
client.release();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Store blob if it doesn't exist
|
|
108
|
+
* @private
|
|
109
|
+
* @param {string} content - Blob content
|
|
110
|
+
* @returns {Promise<string>} Blob hash
|
|
111
|
+
*/
|
|
112
|
+
async _storeBlob(content) {
|
|
113
|
+
const hash = this._hashContent(content);
|
|
114
|
+
|
|
115
|
+
// Insert blob if it doesn't exist (upsert)
|
|
116
|
+
await this.db.query(
|
|
117
|
+
`INSERT INTO blobs (hash, content, size)
|
|
118
|
+
VALUES ($1, $2, $3)
|
|
119
|
+
ON CONFLICT (hash) DO NOTHING`,
|
|
120
|
+
[hash, content, Buffer.byteLength(content, 'utf8')]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return hash;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get blob content by hash
|
|
128
|
+
* @private
|
|
129
|
+
* @param {string} hash - Blob hash
|
|
130
|
+
* @returns {Promise<string|null>} Blob content
|
|
131
|
+
*/
|
|
132
|
+
async _getBlob(hash) {
|
|
133
|
+
const result = await this.db.query(
|
|
134
|
+
'SELECT content FROM blobs WHERE hash = $1',
|
|
135
|
+
[hash]
|
|
136
|
+
);
|
|
137
|
+
return result.rows.length > 0 ? result.rows[0].content : null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Update search index for a blob
|
|
142
|
+
* @private
|
|
143
|
+
* @param {string} hash - Blob hash
|
|
144
|
+
* @param {string} content - Blob content
|
|
145
|
+
*/
|
|
146
|
+
async _updateSearchIndex(hash, content) {
|
|
147
|
+
await this.db.query(
|
|
148
|
+
`INSERT INTO search_index (blob_hash, content_tsvector)
|
|
149
|
+
VALUES ($1, to_tsvector('english', $2))
|
|
150
|
+
ON CONFLICT (blob_hash)
|
|
151
|
+
DO UPDATE SET
|
|
152
|
+
content_tsvector = to_tsvector('english', $2),
|
|
153
|
+
updated_at = NOW()`,
|
|
154
|
+
[hash, content]
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build hierarchical file structure from flat file list
|
|
160
|
+
* @private
|
|
161
|
+
* @param {Array} files - Array of {path, blob_hash}
|
|
162
|
+
* @returns {Array} Hierarchical structure
|
|
163
|
+
*/
|
|
164
|
+
_buildStructureFromFiles(files) {
|
|
165
|
+
const root = [];
|
|
166
|
+
const folderMap = new Map();
|
|
167
|
+
|
|
168
|
+
// Sort files for consistent ordering
|
|
169
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
170
|
+
|
|
171
|
+
for (const file of files) {
|
|
172
|
+
const parts = file.path.split('/');
|
|
173
|
+
let currentLevel = root;
|
|
174
|
+
let currentPath = '';
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < parts.length; i++) {
|
|
177
|
+
const part = parts[i];
|
|
178
|
+
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
179
|
+
|
|
180
|
+
if (i === parts.length - 1) {
|
|
181
|
+
// It's a file
|
|
182
|
+
currentLevel.push({
|
|
183
|
+
name: part,
|
|
184
|
+
type: 'file',
|
|
185
|
+
path: file.path,
|
|
186
|
+
sha: file.blob_hash
|
|
187
|
+
});
|
|
188
|
+
} else {
|
|
189
|
+
// It's a folder
|
|
190
|
+
let folder = folderMap.get(currentPath);
|
|
191
|
+
|
|
192
|
+
if (!folder) {
|
|
193
|
+
folder = {
|
|
194
|
+
name: part,
|
|
195
|
+
type: 'folder',
|
|
196
|
+
path: currentPath,
|
|
197
|
+
children: []
|
|
198
|
+
};
|
|
199
|
+
folderMap.set(currentPath, folder);
|
|
200
|
+
currentLevel.push(folder);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
currentLevel = folder.children;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Sort: folders first, then alphabetically
|
|
209
|
+
const sortStructure = (items) => {
|
|
210
|
+
items.sort((a, b) => {
|
|
211
|
+
if (a.type === b.type) return a.name.localeCompare(b.name);
|
|
212
|
+
return a.type === 'folder' ? -1 : 1;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
items.forEach((item) => {
|
|
216
|
+
if (item.type === 'folder' && item.children) {
|
|
217
|
+
sortStructure(item.children);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
sortStructure(root);
|
|
223
|
+
return root;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get file structure for the current branch
|
|
228
|
+
* @returns {Promise<Array>} Hierarchical file structure
|
|
229
|
+
*/
|
|
230
|
+
async getFileStructure() {
|
|
231
|
+
const files = await this._getCurrentFiles();
|
|
232
|
+
return this._buildStructureFromFiles(files);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get file content and metadata
|
|
237
|
+
* @param {string} path - File path
|
|
238
|
+
* @returns {Promise<Object|null>} File data or null if not found
|
|
239
|
+
*/
|
|
240
|
+
async getFile(path) {
|
|
241
|
+
const files = await this._getCurrentFiles();
|
|
242
|
+
const file = files.find(f => f.path === path);
|
|
243
|
+
|
|
244
|
+
if (!file) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const content = await this._getBlob(file.blob_hash);
|
|
249
|
+
|
|
250
|
+
if (!content) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
content,
|
|
256
|
+
sha: file.blob_hash,
|
|
257
|
+
path
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Save or update a file
|
|
263
|
+
* @param {string} path - File path
|
|
264
|
+
* @param {string} content - File content
|
|
265
|
+
* @param {string|null} sha - Expected current SHA (ignored, kept for API compatibility)
|
|
266
|
+
* @returns {Promise<Object>} Save result
|
|
267
|
+
*/
|
|
268
|
+
async saveFile(path, content, sha = null) {
|
|
269
|
+
// Store blob
|
|
270
|
+
const blobHash = await this._storeBlob(content);
|
|
271
|
+
|
|
272
|
+
// Update search index
|
|
273
|
+
await this._updateSearchIndex(blobHash, content);
|
|
274
|
+
|
|
275
|
+
// Get current files
|
|
276
|
+
const currentFiles = await this._getCurrentFiles();
|
|
277
|
+
|
|
278
|
+
// Update or add the file
|
|
279
|
+
const existingIndex = currentFiles.findIndex(f => f.path === path);
|
|
280
|
+
if (existingIndex >= 0) {
|
|
281
|
+
currentFiles[existingIndex].blob_hash = blobHash;
|
|
282
|
+
} else {
|
|
283
|
+
currentFiles.push({ path, blob_hash: blobHash });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Create commit
|
|
287
|
+
const message = sha ? `Update ${path}` : `Create ${path}`;
|
|
288
|
+
const commitId = await this._createCommit(currentFiles, message);
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
commit: { sha: commitId },
|
|
292
|
+
content: { sha: blobHash, path }
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Delete a file
|
|
298
|
+
* @param {string} path - File path
|
|
299
|
+
* @param {string} sha - File SHA (ignored, kept for API compatibility)
|
|
300
|
+
* @returns {Promise<Object>} Delete result
|
|
301
|
+
*/
|
|
302
|
+
async deleteFile(path, sha) {
|
|
303
|
+
// Get current files
|
|
304
|
+
const currentFiles = await this._getCurrentFiles();
|
|
305
|
+
|
|
306
|
+
// Remove the file
|
|
307
|
+
const newFiles = currentFiles.filter(f => f.path !== path);
|
|
308
|
+
|
|
309
|
+
if (newFiles.length === currentFiles.length) {
|
|
310
|
+
throw new Error('File not found');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Create commit
|
|
314
|
+
const commitId = await this._createCommit(newFiles, `Delete ${path}`);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
commit: { sha: commitId }
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create a folder (no-op in this system, folders are implicit)
|
|
323
|
+
* @param {string} path - Folder path
|
|
324
|
+
* @returns {Promise<Object>} Result
|
|
325
|
+
*/
|
|
326
|
+
async createFolder(path) {
|
|
327
|
+
// In our system, folders are implicit and don't need explicit creation
|
|
328
|
+
// Return success for API compatibility
|
|
329
|
+
return { success: true };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Delete a folder and all its contents
|
|
334
|
+
* @param {string} path - Folder path
|
|
335
|
+
* @returns {Promise<Array>} Delete results
|
|
336
|
+
*/
|
|
337
|
+
async deleteFolder(path) {
|
|
338
|
+
// Get current files
|
|
339
|
+
const currentFiles = await this._getCurrentFiles();
|
|
340
|
+
|
|
341
|
+
// Remove all files in the folder
|
|
342
|
+
const prefix = path.endsWith('/') ? path : `${path}/`;
|
|
343
|
+
const newFiles = currentFiles.filter(f => !f.path.startsWith(prefix));
|
|
344
|
+
|
|
345
|
+
if (newFiles.length === currentFiles.length) {
|
|
346
|
+
return []; // No files deleted
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Create commit
|
|
350
|
+
const commitId = await this._createCommit(newFiles, `Delete folder ${path}`);
|
|
351
|
+
|
|
352
|
+
return [{ commit: { sha: commitId } }];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Search notes for a query
|
|
357
|
+
* @param {string} query - Search query
|
|
358
|
+
* @returns {Promise<Array>} Search results
|
|
359
|
+
*/
|
|
360
|
+
async searchNotes(query) {
|
|
361
|
+
// Use PostgreSQL full-text search
|
|
362
|
+
const searchResult = await this.db.query(
|
|
363
|
+
`SELECT si.blob_hash,
|
|
364
|
+
ts_rank(si.content_tsvector, query) as rank
|
|
365
|
+
FROM search_index si,
|
|
366
|
+
to_tsquery('english', $1) query
|
|
367
|
+
WHERE si.content_tsvector @@ query
|
|
368
|
+
ORDER BY rank DESC
|
|
369
|
+
LIMIT 50`,
|
|
370
|
+
[query.split(' ').join(' & ')]
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const results = [];
|
|
374
|
+
|
|
375
|
+
// Get current files to map blob hashes to paths
|
|
376
|
+
const currentFiles = await this._getCurrentFiles();
|
|
377
|
+
|
|
378
|
+
for (const row of searchResult.rows) {
|
|
379
|
+
const file = currentFiles.find(f => f.blob_hash === row.blob_hash);
|
|
380
|
+
if (!file || !file.path.endsWith('.md')) continue;
|
|
381
|
+
|
|
382
|
+
const content = await this._getBlob(row.blob_hash);
|
|
383
|
+
if (!content) continue;
|
|
384
|
+
|
|
385
|
+
const lines = content.split('\n');
|
|
386
|
+
const matches = [];
|
|
387
|
+
|
|
388
|
+
lines.forEach((line, index) => {
|
|
389
|
+
if (line.toLowerCase().includes(query.toLowerCase())) {
|
|
390
|
+
matches.push({
|
|
391
|
+
line: index + 1,
|
|
392
|
+
content: line
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (matches.length > 0) {
|
|
398
|
+
results.push({
|
|
399
|
+
path: file.path,
|
|
400
|
+
matches
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return results;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Get commit history for a specific file
|
|
410
|
+
* @param {string} path - File path
|
|
411
|
+
* @returns {Promise<Array>} Array of commits that affected this file
|
|
412
|
+
*/
|
|
413
|
+
async getFileHistory(path) {
|
|
414
|
+
// Get all commits where this file exists
|
|
415
|
+
const result = await this.db.query(
|
|
416
|
+
`SELECT DISTINCT
|
|
417
|
+
c.id,
|
|
418
|
+
c.message,
|
|
419
|
+
c.author,
|
|
420
|
+
c.created_at,
|
|
421
|
+
t.blob_hash,
|
|
422
|
+
t.path
|
|
423
|
+
FROM commits c
|
|
424
|
+
INNER JOIN trees t ON t.commit_id = c.id
|
|
425
|
+
WHERE t.path = $1
|
|
426
|
+
ORDER BY c.created_at DESC`,
|
|
427
|
+
[path]
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
const commits = [];
|
|
431
|
+
let previousHash = null;
|
|
432
|
+
|
|
433
|
+
for (const row of result.rows) {
|
|
434
|
+
const changed = previousHash === null || previousHash !== row.blob_hash;
|
|
435
|
+
|
|
436
|
+
commits.push({
|
|
437
|
+
sha: row.id,
|
|
438
|
+
message: row.message,
|
|
439
|
+
author: row.author,
|
|
440
|
+
date: row.created_at,
|
|
441
|
+
blobHash: row.blob_hash,
|
|
442
|
+
changed
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
previousHash = row.blob_hash;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return commits;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get the default branch (compatibility method)
|
|
453
|
+
* @returns {Promise<string>} Default branch name
|
|
454
|
+
*/
|
|
455
|
+
async getDefaultBranch() {
|
|
456
|
+
return this.branch;
|
|
457
|
+
}
|
|
458
|
+
}
|
package/lib/frontend/index.js
CHANGED
|
@@ -657,9 +657,6 @@ class NotesEditor {
|
|
|
657
657
|
}
|
|
658
658
|
}
|
|
659
659
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
} else {
|
|
664
|
-
window.NotesEditor = NotesEditor;
|
|
665
|
-
}
|
|
660
|
+
|
|
661
|
+
export { NotesEditor };
|
|
662
|
+
export default NotesEditor;
|
package/lib/index.js
CHANGED
|
@@ -8,21 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
// Backend exports
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export { createNotesRouter } from './backend/index.js';
|
|
12
|
+
export { GitHubClient } from './backend/github.js';
|
|
13
|
+
export { StorageClient } from './backend/storage.js';
|
|
14
|
+
export { MarkdownRenderer } from './backend/markdown.js';
|
|
15
15
|
|
|
16
16
|
// Frontend exports
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
module.exports = {
|
|
20
|
-
// Backend
|
|
21
|
-
createNotesRouter,
|
|
22
|
-
GitHubClient,
|
|
23
|
-
StorageClient,
|
|
24
|
-
MarkdownRenderer,
|
|
25
|
-
|
|
26
|
-
// Frontend
|
|
27
|
-
NotesEditor
|
|
28
|
-
};
|
|
17
|
+
export { NotesEditor } from './frontend/index.js';
|
package/package.json
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markdown-notes-engine",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A complete markdown note-taking engine with GitHub integration and media hosting (R2/S3)",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "A complete markdown note-taking engine with Git-like version control (PostgreSQL) or GitHub integration and media hosting (R2/S3)",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "lib/index.js",
|
|
6
|
-
"module": "lib/index.mjs",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"./backend":
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
}
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./backend": "./lib/backend/index.js",
|
|
10
|
+
"./backend/github": "./lib/backend/github.js",
|
|
11
|
+
"./backend/storage": "./lib/backend/storage.js",
|
|
12
|
+
"./backend/markdown": "./lib/backend/markdown.js",
|
|
13
|
+
"./backend/version-control": "./lib/backend/version-control.js",
|
|
14
|
+
"./backend/db": "./lib/backend/db/connection.js",
|
|
15
|
+
"./frontend": "./lib/frontend/index.js"
|
|
32
16
|
},
|
|
33
17
|
"scripts": {
|
|
34
18
|
"start": "node server.js",
|
|
@@ -38,6 +22,9 @@
|
|
|
38
22
|
"keywords": [
|
|
39
23
|
"markdown",
|
|
40
24
|
"notes",
|
|
25
|
+
"version-control",
|
|
26
|
+
"git",
|
|
27
|
+
"postgresql",
|
|
41
28
|
"github",
|
|
42
29
|
"editor",
|
|
43
30
|
"r2",
|
|
@@ -53,18 +40,21 @@
|
|
|
53
40
|
},
|
|
54
41
|
"files": [
|
|
55
42
|
"lib/**/*.js",
|
|
56
|
-
"lib/**/*.
|
|
43
|
+
"lib/**/*.sql",
|
|
57
44
|
"README.md",
|
|
58
45
|
"LICENSE"
|
|
59
46
|
],
|
|
60
47
|
"dependencies": {
|
|
61
48
|
"@aws-sdk/client-s3": "^3.928.0",
|
|
62
|
-
"@octokit/rest": "^22.0.1",
|
|
63
49
|
"express": "^5.1.0",
|
|
64
50
|
"express-fileupload": "^1.5.2",
|
|
65
51
|
"highlight.js": "^11.11.1",
|
|
66
52
|
"marked": "^17.0.0",
|
|
67
|
-
"marked-highlight": "^2.2.3"
|
|
53
|
+
"marked-highlight": "^2.2.3",
|
|
54
|
+
"postgres": "^3.4.5"
|
|
55
|
+
},
|
|
56
|
+
"optionalDependencies": {
|
|
57
|
+
"@octokit/rest": "^22.0.1"
|
|
68
58
|
},
|
|
69
59
|
"devDependencies": {
|
|
70
60
|
"dotenv": "^17.2.3",
|