@im-nobsidian/core 0.1.3 → 0.1.4
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/dist/index.d.ts +165 -5
- package/dist/index.js +700 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,6 +12,13 @@ import { readFileSync } from 'fs';
|
|
|
12
12
|
import { watch } from 'chokidar';
|
|
13
13
|
|
|
14
14
|
// src/types/config.ts
|
|
15
|
+
var DatabaseSyncSchema = z.object({
|
|
16
|
+
databaseId: z.string(),
|
|
17
|
+
localFolder: z.string(),
|
|
18
|
+
propertyMapping: z.record(z.string()).optional(),
|
|
19
|
+
pullFilter: z.unknown().optional(),
|
|
20
|
+
titleProperty: z.string().default("Name")
|
|
21
|
+
});
|
|
15
22
|
var ConfigSchema = z.object({
|
|
16
23
|
version: z.literal(1),
|
|
17
24
|
notion: z.object({
|
|
@@ -19,14 +26,16 @@ var ConfigSchema = z.object({
|
|
|
19
26
|
rootPageId: z.string(),
|
|
20
27
|
workspaceId: z.string().optional(),
|
|
21
28
|
parentMode: z.enum(["page", "database"]).default("page"),
|
|
22
|
-
databaseId: z.string().optional()
|
|
29
|
+
databaseId: z.string().optional(),
|
|
30
|
+
databases: z.array(DatabaseSyncSchema).default([])
|
|
23
31
|
}),
|
|
24
32
|
sync: z.object({
|
|
25
33
|
direction: z.enum(["push", "pull", "both"]).default("both"),
|
|
26
34
|
conflictStrategy: z.enum(["local-first", "remote-first", "manual", "duplicate"]).default("manual"),
|
|
27
35
|
autoSync: z.boolean().default(false),
|
|
28
36
|
autoSyncInterval: z.number().min(30).max(3600).default(300),
|
|
29
|
-
deleteSync: z.boolean().default(false)
|
|
37
|
+
deleteSync: z.boolean().default(false),
|
|
38
|
+
syncFiles: z.boolean().default(true)
|
|
30
39
|
}),
|
|
31
40
|
paths: z.object({
|
|
32
41
|
include: z.array(z.string()).default(["**/*"]),
|
|
@@ -51,14 +60,16 @@ var DEFAULT_CONFIG = {
|
|
|
51
60
|
notion: {
|
|
52
61
|
token: "",
|
|
53
62
|
rootPageId: "",
|
|
54
|
-
parentMode: "page"
|
|
63
|
+
parentMode: "page",
|
|
64
|
+
databases: []
|
|
55
65
|
},
|
|
56
66
|
sync: {
|
|
57
67
|
direction: "both",
|
|
58
68
|
conflictStrategy: "manual",
|
|
59
69
|
autoSync: false,
|
|
60
70
|
autoSyncInterval: 300,
|
|
61
|
-
deleteSync: false
|
|
71
|
+
deleteSync: false,
|
|
72
|
+
syncFiles: true
|
|
62
73
|
},
|
|
63
74
|
paths: {
|
|
64
75
|
include: ["**/*"],
|
|
@@ -257,6 +268,28 @@ CREATE TABLE IF NOT EXISTS sync_metadata (
|
|
|
257
268
|
INSERT OR IGNORE INTO sync_metadata (key, value) VALUES ('schema_version', '1');
|
|
258
269
|
`;
|
|
259
270
|
|
|
271
|
+
// src/state/migrations/002-file-registry.ts
|
|
272
|
+
var FILE_REGISTRY_MIGRATION = `
|
|
273
|
+
CREATE TABLE IF NOT EXISTS file_registry (
|
|
274
|
+
id TEXT PRIMARY KEY,
|
|
275
|
+
local_path TEXT NOT NULL,
|
|
276
|
+
notion_page_id TEXT NOT NULL,
|
|
277
|
+
file_upload_id TEXT NOT NULL,
|
|
278
|
+
file_type TEXT NOT NULL DEFAULT 'file',
|
|
279
|
+
file_hash TEXT NOT NULL,
|
|
280
|
+
file_size INTEGER NOT NULL DEFAULT 0,
|
|
281
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
282
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
283
|
+
|
|
284
|
+
CONSTRAINT chk_file_type CHECK (file_type IN ('image', 'pdf', 'video', 'audio', 'file'))
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_file_registry_path ON file_registry(local_path);
|
|
288
|
+
CREATE INDEX IF NOT EXISTS idx_file_registry_page ON file_registry(notion_page_id);
|
|
289
|
+
|
|
290
|
+
UPDATE sync_metadata SET value = '2' WHERE key = 'schema_version';
|
|
291
|
+
`;
|
|
292
|
+
|
|
260
293
|
// src/state/state-db.ts
|
|
261
294
|
var StateDB = class _StateDB {
|
|
262
295
|
db;
|
|
@@ -276,6 +309,9 @@ var StateDB = class _StateDB {
|
|
|
276
309
|
if (version < 1) {
|
|
277
310
|
this.db.exec(INITIAL_MIGRATION);
|
|
278
311
|
}
|
|
312
|
+
if (version < 2) {
|
|
313
|
+
this.db.exec(FILE_REGISTRY_MIGRATION);
|
|
314
|
+
}
|
|
279
315
|
}
|
|
280
316
|
getSchemaVersion() {
|
|
281
317
|
try {
|
|
@@ -406,6 +442,49 @@ var StateDB = class _StateDB {
|
|
|
406
442
|
setMeta(key, value) {
|
|
407
443
|
this.db.prepare("INSERT OR REPLACE INTO sync_metadata (key, value) VALUES (?, ?)").run(key, value);
|
|
408
444
|
}
|
|
445
|
+
// --- file_registry ---
|
|
446
|
+
isFileRegistered(localPath) {
|
|
447
|
+
const row = this.db.prepare("SELECT 1 FROM file_registry WHERE local_path = ?").get(localPath);
|
|
448
|
+
return !!row;
|
|
449
|
+
}
|
|
450
|
+
getFileRegistry(localPath) {
|
|
451
|
+
const row = this.db.prepare("SELECT * FROM file_registry WHERE local_path = ?").get(localPath);
|
|
452
|
+
return row ? this.mapFileRegistryRow(row) : null;
|
|
453
|
+
}
|
|
454
|
+
getFilesByPageId(notionPageId) {
|
|
455
|
+
const rows = this.db.prepare("SELECT * FROM file_registry WHERE notion_page_id = ?").all(notionPageId);
|
|
456
|
+
return rows.map((r) => this.mapFileRegistryRow(r));
|
|
457
|
+
}
|
|
458
|
+
registerFile(entry) {
|
|
459
|
+
const id = generateId();
|
|
460
|
+
this.db.prepare(
|
|
461
|
+
`INSERT OR REPLACE INTO file_registry
|
|
462
|
+
(id, local_path, notion_page_id, file_upload_id, file_type, file_hash, file_size)
|
|
463
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
464
|
+
).run(
|
|
465
|
+
id,
|
|
466
|
+
entry.localPath,
|
|
467
|
+
entry.notionPageId,
|
|
468
|
+
entry.fileUploadId,
|
|
469
|
+
entry.fileType,
|
|
470
|
+
entry.fileHash,
|
|
471
|
+
entry.fileSize
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
deleteFileRegistry(localPath) {
|
|
475
|
+
this.db.prepare("DELETE FROM file_registry WHERE local_path = ?").run(localPath);
|
|
476
|
+
}
|
|
477
|
+
mapFileRegistryRow(row) {
|
|
478
|
+
return {
|
|
479
|
+
id: row.id,
|
|
480
|
+
localPath: row.local_path,
|
|
481
|
+
notionPageId: row.notion_page_id,
|
|
482
|
+
fileUploadId: row.file_upload_id,
|
|
483
|
+
fileType: row.file_type,
|
|
484
|
+
fileHash: row.file_hash,
|
|
485
|
+
fileSize: row.file_size
|
|
486
|
+
};
|
|
487
|
+
}
|
|
409
488
|
// --- preserve markers ---
|
|
410
489
|
storePreserveMarkers(path, markers) {
|
|
411
490
|
if (markers.length === 0) {
|
|
@@ -817,7 +896,8 @@ var NotionClient = class {
|
|
|
817
896
|
() => this.client.dataSources.query({
|
|
818
897
|
data_source_id: databaseId,
|
|
819
898
|
start_cursor: options?.startCursor,
|
|
820
|
-
page_size: options?.pageSize ?? 100
|
|
899
|
+
page_size: options?.pageSize ?? 100,
|
|
900
|
+
filter: options?.filter
|
|
821
901
|
})
|
|
822
902
|
);
|
|
823
903
|
return {
|
|
@@ -825,6 +905,27 @@ var NotionClient = class {
|
|
|
825
905
|
nextCursor: response.next_cursor
|
|
826
906
|
};
|
|
827
907
|
}
|
|
908
|
+
async queryAllDatabasePages(databaseId, filter) {
|
|
909
|
+
const all = [];
|
|
910
|
+
let cursor;
|
|
911
|
+
do {
|
|
912
|
+
const response = await this.queryDatabase(databaseId, {
|
|
913
|
+
startCursor: cursor,
|
|
914
|
+
pageSize: 100,
|
|
915
|
+
filter
|
|
916
|
+
});
|
|
917
|
+
all.push(...response.results);
|
|
918
|
+
cursor = response.nextCursor ?? void 0;
|
|
919
|
+
} while (cursor);
|
|
920
|
+
return all;
|
|
921
|
+
}
|
|
922
|
+
async getDataSourceId(databaseId) {
|
|
923
|
+
const db = await this.withRateLimit(
|
|
924
|
+
() => this.client.databases.retrieve({ database_id: databaseId })
|
|
925
|
+
);
|
|
926
|
+
const dataSources = db.data_sources;
|
|
927
|
+
return dataSources?.[0]?.id ?? databaseId;
|
|
928
|
+
}
|
|
828
929
|
async archivePage(pageId) {
|
|
829
930
|
await this.withRateLimit(() => this.client.pages.update({ page_id: pageId, archived: true }));
|
|
830
931
|
}
|
|
@@ -873,15 +974,18 @@ var NotionClient = class {
|
|
|
873
974
|
() => this.client.fileUploads.create({ filename, content_type: contentType })
|
|
874
975
|
);
|
|
875
976
|
const fileUploadId = upload.id;
|
|
876
|
-
await this.withRateLimit(
|
|
977
|
+
const sendResult = await this.withRateLimit(
|
|
877
978
|
() => this.client.fileUploads.send({
|
|
878
979
|
file_upload_id: fileUploadId,
|
|
879
980
|
file: { data: fileData, filename }
|
|
880
981
|
})
|
|
881
982
|
);
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
983
|
+
const status = sendResult.status;
|
|
984
|
+
if (status === "pending") {
|
|
985
|
+
await this.withRateLimit(
|
|
986
|
+
() => this.client.fileUploads.complete({ file_upload_id: fileUploadId })
|
|
987
|
+
);
|
|
988
|
+
}
|
|
885
989
|
return fileUploadId;
|
|
886
990
|
}
|
|
887
991
|
// ─── Search & Navigation ───
|
|
@@ -2855,6 +2959,265 @@ var ImageHandler = class {
|
|
|
2855
2959
|
return ext ? `.${ext[1].toLowerCase()}` : null;
|
|
2856
2960
|
}
|
|
2857
2961
|
};
|
|
2962
|
+
var EXTENSION_TO_BLOCK_TYPE = {
|
|
2963
|
+
".png": "image",
|
|
2964
|
+
".jpg": "image",
|
|
2965
|
+
".jpeg": "image",
|
|
2966
|
+
".gif": "image",
|
|
2967
|
+
".svg": "image",
|
|
2968
|
+
".webp": "image",
|
|
2969
|
+
".ico": "image",
|
|
2970
|
+
".bmp": "image",
|
|
2971
|
+
".tiff": "image",
|
|
2972
|
+
".tif": "image",
|
|
2973
|
+
".avif": "image",
|
|
2974
|
+
".apng": "image",
|
|
2975
|
+
".heic": "image",
|
|
2976
|
+
".pdf": "pdf",
|
|
2977
|
+
".mp4": "video",
|
|
2978
|
+
".mov": "video",
|
|
2979
|
+
".webm": "video",
|
|
2980
|
+
".avi": "video",
|
|
2981
|
+
".mkv": "video",
|
|
2982
|
+
".flv": "video",
|
|
2983
|
+
".wmv": "video",
|
|
2984
|
+
".m4v": "video",
|
|
2985
|
+
".mpeg": "video",
|
|
2986
|
+
".ogv": "video",
|
|
2987
|
+
".3gp": "video",
|
|
2988
|
+
".mp3": "audio",
|
|
2989
|
+
".wav": "audio",
|
|
2990
|
+
".ogg": "audio",
|
|
2991
|
+
".m4a": "audio",
|
|
2992
|
+
".flac": "audio",
|
|
2993
|
+
".aac": "audio",
|
|
2994
|
+
".wma": "audio",
|
|
2995
|
+
".opus": "audio",
|
|
2996
|
+
".weba": "audio"
|
|
2997
|
+
};
|
|
2998
|
+
var EXTENSION_TO_MIME2 = {
|
|
2999
|
+
".png": "image/png",
|
|
3000
|
+
".jpg": "image/jpeg",
|
|
3001
|
+
".jpeg": "image/jpeg",
|
|
3002
|
+
".gif": "image/gif",
|
|
3003
|
+
".webp": "image/webp",
|
|
3004
|
+
".svg": "image/svg+xml",
|
|
3005
|
+
".bmp": "image/bmp",
|
|
3006
|
+
".ico": "image/x-icon",
|
|
3007
|
+
".tiff": "image/tiff",
|
|
3008
|
+
".tif": "image/tiff",
|
|
3009
|
+
".avif": "image/avif",
|
|
3010
|
+
".heic": "image/heic",
|
|
3011
|
+
".pdf": "application/pdf",
|
|
3012
|
+
".mp4": "video/mp4",
|
|
3013
|
+
".mov": "video/quicktime",
|
|
3014
|
+
".webm": "video/webm",
|
|
3015
|
+
".avi": "video/x-msvideo",
|
|
3016
|
+
".mkv": "video/x-matroska",
|
|
3017
|
+
".mp3": "audio/mpeg",
|
|
3018
|
+
".wav": "audio/wav",
|
|
3019
|
+
".ogg": "audio/ogg",
|
|
3020
|
+
".m4a": "audio/mp4",
|
|
3021
|
+
".flac": "audio/flac",
|
|
3022
|
+
".aac": "audio/aac",
|
|
3023
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
3024
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
3025
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
3026
|
+
".zip": "application/zip",
|
|
3027
|
+
".gz": "application/gzip",
|
|
3028
|
+
".tar": "application/x-tar",
|
|
3029
|
+
".rar": "application/vnd.rar",
|
|
3030
|
+
".7z": "application/x-7z-compressed",
|
|
3031
|
+
".py": "text/x-python",
|
|
3032
|
+
".js": "text/javascript",
|
|
3033
|
+
".ts": "text/typescript",
|
|
3034
|
+
".json": "application/json",
|
|
3035
|
+
".csv": "text/csv",
|
|
3036
|
+
".txt": "text/plain",
|
|
3037
|
+
".html": "text/html",
|
|
3038
|
+
".xml": "application/xml",
|
|
3039
|
+
".hwp": "application/x-hwp",
|
|
3040
|
+
".xls": "application/vnd.ms-excel",
|
|
3041
|
+
".doc": "application/msword",
|
|
3042
|
+
".ppt": "application/vnd.ms-powerpoint"
|
|
3043
|
+
};
|
|
3044
|
+
function getBlockType(filename) {
|
|
3045
|
+
const ext = filename.match(/\.[^.]+$/)?.[0]?.toLowerCase();
|
|
3046
|
+
if (!ext) return "file";
|
|
3047
|
+
return EXTENSION_TO_BLOCK_TYPE[ext] ?? "file";
|
|
3048
|
+
}
|
|
3049
|
+
function getMimeType(filename) {
|
|
3050
|
+
const ext = filename.match(/\.[^.]+$/)?.[0]?.toLowerCase();
|
|
3051
|
+
if (!ext) return "application/octet-stream";
|
|
3052
|
+
return EXTENSION_TO_MIME2[ext] ?? "application/octet-stream";
|
|
3053
|
+
}
|
|
3054
|
+
function getFolderPath(filePath) {
|
|
3055
|
+
const parts = filePath.split("/");
|
|
3056
|
+
return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
|
|
3057
|
+
}
|
|
3058
|
+
var FileHandler = class {
|
|
3059
|
+
constructor(vaultFs, notionClient, stateDb) {
|
|
3060
|
+
this.vaultFs = vaultFs;
|
|
3061
|
+
this.notionClient = notionClient;
|
|
3062
|
+
this.stateDb = stateDb;
|
|
3063
|
+
}
|
|
3064
|
+
vaultFs;
|
|
3065
|
+
notionClient;
|
|
3066
|
+
stateDb;
|
|
3067
|
+
sema = new Sema(2);
|
|
3068
|
+
async pushFilesForFolder(folderPageId, folderPath) {
|
|
3069
|
+
const allFiles = await this.vaultFs.listNonMarkdownFiles();
|
|
3070
|
+
const folderFiles = allFiles.filter((f) => getFolderPath(f.path) === folderPath);
|
|
3071
|
+
if (folderFiles.length === 0) return [];
|
|
3072
|
+
const results = [];
|
|
3073
|
+
for (const file of folderFiles) {
|
|
3074
|
+
if (this.stateDb.isFileRegistered(file.path)) {
|
|
3075
|
+
const existing = this.stateDb.getFileRegistry(file.path);
|
|
3076
|
+
if (existing) {
|
|
3077
|
+
const buffer = await this.vaultFs.readBinary(file.path);
|
|
3078
|
+
const currentHash = createHash("sha256").update(buffer).digest("hex");
|
|
3079
|
+
if (existing.fileHash === currentHash) continue;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
await this.sema.acquire();
|
|
3083
|
+
try {
|
|
3084
|
+
const result = await this.uploadFile(file, folderPageId);
|
|
3085
|
+
if (result) results.push(result);
|
|
3086
|
+
} catch (error) {
|
|
3087
|
+
getLogger().warn(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${file.path}):`, error);
|
|
3088
|
+
} finally {
|
|
3089
|
+
this.sema.release();
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
return results;
|
|
3093
|
+
}
|
|
3094
|
+
async pushAllFiles() {
|
|
3095
|
+
const allFiles = await this.vaultFs.listNonMarkdownFiles();
|
|
3096
|
+
if (allFiles.length === 0) return [];
|
|
3097
|
+
const filesByFolder = /* @__PURE__ */ new Map();
|
|
3098
|
+
for (const file of allFiles) {
|
|
3099
|
+
const folder = getFolderPath(file.path);
|
|
3100
|
+
const list = filesByFolder.get(folder) ?? [];
|
|
3101
|
+
list.push(file);
|
|
3102
|
+
filesByFolder.set(folder, list);
|
|
3103
|
+
}
|
|
3104
|
+
const results = [];
|
|
3105
|
+
for (const [folderPath, files] of filesByFolder) {
|
|
3106
|
+
const folderRecord = folderPath ? this.stateDb.getByPath(folderPath) : this.stateDb.getByPath("__root__");
|
|
3107
|
+
const folderPageId = folderRecord?.notionPageId;
|
|
3108
|
+
if (!folderPageId) {
|
|
3109
|
+
getLogger().warn(
|
|
3110
|
+
`\uD3F4\uB354 \uD398\uC774\uC9C0 \uC5C6\uC74C, \uD30C\uC77C \uC2A4\uD0B5: ${folderPath || "(\uB8E8\uD2B8)"} (${files.length}\uAC1C)`
|
|
3111
|
+
);
|
|
3112
|
+
continue;
|
|
3113
|
+
}
|
|
3114
|
+
for (const file of files) {
|
|
3115
|
+
if (this.stateDb.isFileRegistered(file.path)) {
|
|
3116
|
+
const existing = this.stateDb.getFileRegistry(file.path);
|
|
3117
|
+
if (existing) {
|
|
3118
|
+
const buffer = await this.vaultFs.readBinary(file.path);
|
|
3119
|
+
const currentHash = createHash("sha256").update(buffer).digest("hex");
|
|
3120
|
+
if (existing.fileHash === currentHash) continue;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
await this.sema.acquire();
|
|
3124
|
+
try {
|
|
3125
|
+
const result = await this.uploadFile(file, folderPageId);
|
|
3126
|
+
if (result) results.push(result);
|
|
3127
|
+
} catch (error) {
|
|
3128
|
+
getLogger().warn(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${file.path}):`, error);
|
|
3129
|
+
} finally {
|
|
3130
|
+
this.sema.release();
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
return results;
|
|
3135
|
+
}
|
|
3136
|
+
async downloadFileBlock(url, filename, targetFolder) {
|
|
3137
|
+
const response = await fetch(url);
|
|
3138
|
+
if (!response.ok) {
|
|
3139
|
+
throw new Error(`\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: ${response.status} ${response.statusText}`);
|
|
3140
|
+
}
|
|
3141
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
3142
|
+
const hash = createHash("sha256").update(buffer).digest("hex");
|
|
3143
|
+
const localPath = targetFolder ? `${targetFolder}/${filename}` : filename;
|
|
3144
|
+
await this.vaultFs.ensureFolder(targetFolder || ".");
|
|
3145
|
+
await this.vaultFs.writeBinary(localPath, buffer);
|
|
3146
|
+
return { url, localPath, hash, size: buffer.length };
|
|
3147
|
+
}
|
|
3148
|
+
async downloadFileBlocks(blocks, targetFolder) {
|
|
3149
|
+
const results = [];
|
|
3150
|
+
for (const block of blocks) {
|
|
3151
|
+
const filename = this.extractFilename(block.url, block.caption);
|
|
3152
|
+
if (this.stateDb.isFileRegistered(`${targetFolder}/${filename}`)) continue;
|
|
3153
|
+
await this.sema.acquire();
|
|
3154
|
+
try {
|
|
3155
|
+
const result = await this.downloadFileBlock(block.url, filename, targetFolder);
|
|
3156
|
+
results.push(result);
|
|
3157
|
+
} catch (error) {
|
|
3158
|
+
getLogger().warn(`\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328 (${block.url}):`, error);
|
|
3159
|
+
} finally {
|
|
3160
|
+
this.sema.release();
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
return results;
|
|
3164
|
+
}
|
|
3165
|
+
async uploadFile(file, parentPageId) {
|
|
3166
|
+
const buffer = await this.vaultFs.readBinary(file.path);
|
|
3167
|
+
const filename = file.path.split("/").pop() ?? "file";
|
|
3168
|
+
const contentType = getMimeType(filename);
|
|
3169
|
+
const blockType = getBlockType(filename);
|
|
3170
|
+
const hash = createHash("sha256").update(buffer).digest("hex");
|
|
3171
|
+
const blob = new Blob([buffer], { type: contentType });
|
|
3172
|
+
const fileUploadId = await this.notionClient.uploadFile(blob, filename, contentType);
|
|
3173
|
+
const block = this.createFileBlock(blockType, fileUploadId);
|
|
3174
|
+
await this.notionClient.appendChildren(parentPageId, [block]);
|
|
3175
|
+
this.stateDb.registerFile({
|
|
3176
|
+
localPath: file.path,
|
|
3177
|
+
notionPageId: parentPageId,
|
|
3178
|
+
fileUploadId,
|
|
3179
|
+
fileType: blockType,
|
|
3180
|
+
fileHash: hash,
|
|
3181
|
+
fileSize: file.size
|
|
3182
|
+
});
|
|
3183
|
+
getLogger().info(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC: ${file.path} \u2192 ${blockType} \uBE14\uB85D`);
|
|
3184
|
+
return { localPath: file.path, fileUploadId, blockType };
|
|
3185
|
+
}
|
|
3186
|
+
createFileBlock(blockType, fileUploadId) {
|
|
3187
|
+
const fileRef = {
|
|
3188
|
+
type: "file_upload",
|
|
3189
|
+
file_upload: { id: fileUploadId }
|
|
3190
|
+
};
|
|
3191
|
+
switch (blockType) {
|
|
3192
|
+
case "image":
|
|
3193
|
+
return { type: "image", image: fileRef };
|
|
3194
|
+
case "pdf":
|
|
3195
|
+
return { type: "pdf", pdf: fileRef };
|
|
3196
|
+
case "video":
|
|
3197
|
+
return { type: "video", video: fileRef };
|
|
3198
|
+
case "audio":
|
|
3199
|
+
return { type: "audio", audio: fileRef };
|
|
3200
|
+
case "file":
|
|
3201
|
+
default:
|
|
3202
|
+
return { type: "file", file: fileRef };
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
extractFilename(url, caption) {
|
|
3206
|
+
if (caption) {
|
|
3207
|
+
const safe = caption.replace(/[^a-zA-Z0-9가-힣._\s-]/g, "").trim();
|
|
3208
|
+
if (safe) return safe;
|
|
3209
|
+
}
|
|
3210
|
+
try {
|
|
3211
|
+
const pathname = new URL(url).pathname;
|
|
3212
|
+
const segments = pathname.split("/");
|
|
3213
|
+
const last = segments[segments.length - 1];
|
|
3214
|
+
if (last && last.includes(".")) return decodeURIComponent(last);
|
|
3215
|
+
} catch {
|
|
3216
|
+
}
|
|
3217
|
+
const ext = url.match(/\.(png|jpg|jpeg|gif|pdf|mp4|mp3|docx|xlsx|zip|webp)(\?|$)/i);
|
|
3218
|
+
return `file-${Date.now()}${ext ? `.${ext[1]}` : ""}`;
|
|
3219
|
+
}
|
|
3220
|
+
};
|
|
2858
3221
|
|
|
2859
3222
|
// src/utils/sanitize.ts
|
|
2860
3223
|
var INVALID_CHARS = /[<>:"/\\|?*\x00-\x1f]/g;
|
|
@@ -3244,6 +3607,253 @@ function unescapePipes(content) {
|
|
|
3244
3607
|
}
|
|
3245
3608
|
return result.join("\n");
|
|
3246
3609
|
}
|
|
3610
|
+
var DatabaseSyncer = class {
|
|
3611
|
+
constructor(config, stateDb, notionClient, vaultFs, pipeline, imageHandler) {
|
|
3612
|
+
this.config = config;
|
|
3613
|
+
this.stateDb = stateDb;
|
|
3614
|
+
this.notionClient = notionClient;
|
|
3615
|
+
this.vaultFs = vaultFs;
|
|
3616
|
+
this.pipeline = pipeline;
|
|
3617
|
+
this.imageHandler = imageHandler;
|
|
3618
|
+
this.propertyMapper.setWikilinkResolver({
|
|
3619
|
+
resolve: (title) => stateDb.resolveWikilink(title)?.notionPageId ?? null,
|
|
3620
|
+
resolvePageId: (pageId) => stateDb.resolvePageId(pageId)?.title ?? null
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
config;
|
|
3624
|
+
stateDb;
|
|
3625
|
+
notionClient;
|
|
3626
|
+
vaultFs;
|
|
3627
|
+
pipeline;
|
|
3628
|
+
imageHandler;
|
|
3629
|
+
propertyMapper = new PropertyMapper();
|
|
3630
|
+
async pullAll() {
|
|
3631
|
+
const databases = this.config.notion.databases;
|
|
3632
|
+
if (!databases || databases.length === 0) {
|
|
3633
|
+
return { created: 0, updated: 0, failed: [] };
|
|
3634
|
+
}
|
|
3635
|
+
let created = 0;
|
|
3636
|
+
let updated = 0;
|
|
3637
|
+
const failed = [];
|
|
3638
|
+
for (const dbConfig of databases) {
|
|
3639
|
+
try {
|
|
3640
|
+
const result = await this.pullDatabase(dbConfig);
|
|
3641
|
+
created += result.created;
|
|
3642
|
+
updated += result.updated;
|
|
3643
|
+
failed.push(...result.failed);
|
|
3644
|
+
} catch (error) {
|
|
3645
|
+
getLogger().warn(`[DB Sync] DB ${dbConfig.databaseId} pull \uC2E4\uD328:`, error);
|
|
3646
|
+
failed.push({
|
|
3647
|
+
path: dbConfig.localFolder,
|
|
3648
|
+
operation: "create",
|
|
3649
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3650
|
+
});
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
return { created, updated, failed };
|
|
3654
|
+
}
|
|
3655
|
+
async pushAll() {
|
|
3656
|
+
const databases = this.config.notion.databases;
|
|
3657
|
+
if (!databases || databases.length === 0) {
|
|
3658
|
+
return { created: 0, updated: 0, failed: [] };
|
|
3659
|
+
}
|
|
3660
|
+
let created = 0;
|
|
3661
|
+
let updated = 0;
|
|
3662
|
+
const failed = [];
|
|
3663
|
+
for (const dbConfig of databases) {
|
|
3664
|
+
try {
|
|
3665
|
+
const result = await this.pushDatabase(dbConfig);
|
|
3666
|
+
created += result.created;
|
|
3667
|
+
updated += result.updated;
|
|
3668
|
+
failed.push(...result.failed);
|
|
3669
|
+
} catch (error) {
|
|
3670
|
+
getLogger().warn(`[DB Sync] DB ${dbConfig.databaseId} push \uC2E4\uD328:`, error);
|
|
3671
|
+
failed.push({
|
|
3672
|
+
path: dbConfig.localFolder,
|
|
3673
|
+
operation: "create",
|
|
3674
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
return { created, updated, failed };
|
|
3679
|
+
}
|
|
3680
|
+
async pullDatabase(dbConfig) {
|
|
3681
|
+
const schema = await this.notionClient.getDatabaseSchema(dbConfig.databaseId);
|
|
3682
|
+
this.propertyMapper.loadSchema(schema);
|
|
3683
|
+
await this.vaultFs.ensureFolder(dbConfig.localFolder);
|
|
3684
|
+
const pages = await this.notionClient.queryAllDatabasePages(
|
|
3685
|
+
dbConfig.databaseId,
|
|
3686
|
+
dbConfig.pullFilter
|
|
3687
|
+
);
|
|
3688
|
+
let created = 0;
|
|
3689
|
+
let updated = 0;
|
|
3690
|
+
const failed = [];
|
|
3691
|
+
for (const page of pages) {
|
|
3692
|
+
try {
|
|
3693
|
+
const record = this.stateDb.getByNotionId(page.id);
|
|
3694
|
+
if (record) {
|
|
3695
|
+
if (page.last_edited_time === record.notionLastEdited) continue;
|
|
3696
|
+
await this.pullDatabasePage(page, dbConfig);
|
|
3697
|
+
updated++;
|
|
3698
|
+
} else {
|
|
3699
|
+
await this.pullDatabasePage(page, dbConfig);
|
|
3700
|
+
created++;
|
|
3701
|
+
}
|
|
3702
|
+
} catch (error) {
|
|
3703
|
+
failed.push({
|
|
3704
|
+
path: `${dbConfig.localFolder}/${page.id}`,
|
|
3705
|
+
operation: "create",
|
|
3706
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3707
|
+
});
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
getLogger().info(`[DB Sync] ${dbConfig.localFolder}: ${created} \uC0DD\uC131, ${updated} \uC5C5\uB370\uC774\uD2B8`);
|
|
3711
|
+
return { created, updated, failed };
|
|
3712
|
+
}
|
|
3713
|
+
async pullDatabasePage(page, dbConfig) {
|
|
3714
|
+
const title = this.notionClient.extractTitle(page);
|
|
3715
|
+
const safeName = sanitizeFileName(title);
|
|
3716
|
+
const properties = this.propertyMapper.fromNotionProperties(
|
|
3717
|
+
page.properties
|
|
3718
|
+
);
|
|
3719
|
+
properties.title = title;
|
|
3720
|
+
let markdown = "";
|
|
3721
|
+
try {
|
|
3722
|
+
const mdResult = await this.notionClient.getPageMarkdown(page.id);
|
|
3723
|
+
markdown = notionEnhancedToObsidian(mdResult.markdown);
|
|
3724
|
+
} catch {
|
|
3725
|
+
}
|
|
3726
|
+
if (this.config.conversion.imageDownload === "immediate" && markdown) {
|
|
3727
|
+
const imageResult = await this.imageHandler.downloadAllImages(markdown, title);
|
|
3728
|
+
markdown = imageResult.content;
|
|
3729
|
+
}
|
|
3730
|
+
const existingRecord = this.stateDb.getByNotionId(page.id);
|
|
3731
|
+
const filePath = existingRecord?.obsidianPath ?? `${dbConfig.localFolder}/${safeName}.md`;
|
|
3732
|
+
const finalContent = this.pipeline.convertToMarkdown(
|
|
3733
|
+
markdown,
|
|
3734
|
+
{
|
|
3735
|
+
direction: "pull",
|
|
3736
|
+
path: "markdown-api",
|
|
3737
|
+
filePath,
|
|
3738
|
+
parentMode: "database"
|
|
3739
|
+
},
|
|
3740
|
+
{ properties }
|
|
3741
|
+
);
|
|
3742
|
+
await this.vaultFs.ensureFolder(dbConfig.localFolder);
|
|
3743
|
+
await this.vaultFs.writeFile(filePath, finalContent);
|
|
3744
|
+
const hash = computeHash(finalContent);
|
|
3745
|
+
this.stateDb.transaction(() => {
|
|
3746
|
+
this.stateDb.upsert({
|
|
3747
|
+
obsidianPath: filePath,
|
|
3748
|
+
notionPageId: page.id,
|
|
3749
|
+
notionParentId: dbConfig.databaseId,
|
|
3750
|
+
contentHash: hash,
|
|
3751
|
+
notionLastEdited: page.last_edited_time,
|
|
3752
|
+
localLastModified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3753
|
+
syncDirection: "both",
|
|
3754
|
+
fileType: "db-row",
|
|
3755
|
+
status: "synced",
|
|
3756
|
+
baseSnapshot: Buffer.from(finalContent, "utf-8")
|
|
3757
|
+
});
|
|
3758
|
+
this.stateDb.upsertWikilink({
|
|
3759
|
+
obsidianPath: filePath,
|
|
3760
|
+
notionPageId: page.id,
|
|
3761
|
+
title,
|
|
3762
|
+
aliases: extractAliases(properties)
|
|
3763
|
+
});
|
|
3764
|
+
});
|
|
3765
|
+
return filePath;
|
|
3766
|
+
}
|
|
3767
|
+
async pushDatabase(dbConfig) {
|
|
3768
|
+
const schema = await this.notionClient.getDatabaseSchema(dbConfig.databaseId);
|
|
3769
|
+
this.propertyMapper.loadSchema(schema);
|
|
3770
|
+
const allFiles = await this.vaultFs.listMarkdownFiles();
|
|
3771
|
+
const prefix = dbConfig.localFolder.endsWith("/") ? dbConfig.localFolder : dbConfig.localFolder + "/";
|
|
3772
|
+
const dbFiles = allFiles.filter((f) => f.path.startsWith(prefix));
|
|
3773
|
+
let created = 0;
|
|
3774
|
+
let updated = 0;
|
|
3775
|
+
const failed = [];
|
|
3776
|
+
for (const file of dbFiles) {
|
|
3777
|
+
try {
|
|
3778
|
+
const content = await this.vaultFs.readFile(file.path);
|
|
3779
|
+
const hash = computeHash(content);
|
|
3780
|
+
const record = this.stateDb.getByPath(file.path);
|
|
3781
|
+
if (record?.contentHash === hash) continue;
|
|
3782
|
+
const parsed = matter(content);
|
|
3783
|
+
const frontmatter = parsed.data;
|
|
3784
|
+
const body = parsed.content;
|
|
3785
|
+
const title = frontmatter.title ?? extractTitleFromPath(file.path);
|
|
3786
|
+
delete frontmatter.title;
|
|
3787
|
+
const notionProps = this.propertyMapper.toNotionProperties(frontmatter, title);
|
|
3788
|
+
const enhanced = obsidianToNotionEnhanced(body.trim());
|
|
3789
|
+
if (record?.notionPageId) {
|
|
3790
|
+
await this.notionClient.updatePageProperties(record.notionPageId, notionProps);
|
|
3791
|
+
try {
|
|
3792
|
+
await this.notionClient.replacePageMarkdown(record.notionPageId, enhanced);
|
|
3793
|
+
} catch {
|
|
3794
|
+
}
|
|
3795
|
+
const updatedPage = await this.notionClient.getPage(record.notionPageId);
|
|
3796
|
+
this.stateDb.transaction(() => {
|
|
3797
|
+
this.stateDb.updateHash(record.id, hash, Buffer.from(content, "utf-8"));
|
|
3798
|
+
this.stateDb.updateStatus(record.id, "synced");
|
|
3799
|
+
this.stateDb.setNotionLastEdited(record.id, updatedPage.last_edited_time);
|
|
3800
|
+
});
|
|
3801
|
+
updated++;
|
|
3802
|
+
} else {
|
|
3803
|
+
const page = await this.notionClient.createPageWithMarkdown({
|
|
3804
|
+
parentId: dbConfig.databaseId,
|
|
3805
|
+
parentType: "database",
|
|
3806
|
+
title,
|
|
3807
|
+
markdown: enhanced,
|
|
3808
|
+
properties: notionProps
|
|
3809
|
+
});
|
|
3810
|
+
this.stateDb.transaction(() => {
|
|
3811
|
+
this.stateDb.upsert({
|
|
3812
|
+
obsidianPath: file.path,
|
|
3813
|
+
notionPageId: page.id,
|
|
3814
|
+
notionParentId: dbConfig.databaseId,
|
|
3815
|
+
contentHash: hash,
|
|
3816
|
+
notionLastEdited: page.last_edited_time,
|
|
3817
|
+
localLastModified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3818
|
+
syncDirection: "both",
|
|
3819
|
+
fileType: "db-row",
|
|
3820
|
+
status: "synced",
|
|
3821
|
+
baseSnapshot: Buffer.from(content, "utf-8")
|
|
3822
|
+
});
|
|
3823
|
+
this.stateDb.upsertWikilink({
|
|
3824
|
+
obsidianPath: file.path,
|
|
3825
|
+
notionPageId: page.id,
|
|
3826
|
+
title,
|
|
3827
|
+
aliases: extractAliases(frontmatter)
|
|
3828
|
+
});
|
|
3829
|
+
});
|
|
3830
|
+
created++;
|
|
3831
|
+
}
|
|
3832
|
+
} catch (error) {
|
|
3833
|
+
failed.push({
|
|
3834
|
+
path: file.path,
|
|
3835
|
+
operation: "create",
|
|
3836
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
getLogger().info(`[DB Sync] ${dbConfig.localFolder}: ${created} \uC0DD\uC131, ${updated} \uC5C5\uB370\uC774\uD2B8`);
|
|
3841
|
+
return { created, updated, failed };
|
|
3842
|
+
}
|
|
3843
|
+
};
|
|
3844
|
+
function extractAliases(properties) {
|
|
3845
|
+
const raw = properties.aliases ?? properties.alias;
|
|
3846
|
+
if (!raw) return [];
|
|
3847
|
+
if (Array.isArray(raw)) return raw.filter((a) => typeof a === "string");
|
|
3848
|
+
if (typeof raw === "string")
|
|
3849
|
+
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3850
|
+
return [];
|
|
3851
|
+
}
|
|
3852
|
+
function extractTitleFromPath(filePath) {
|
|
3853
|
+
const parts = filePath.split("/");
|
|
3854
|
+
const filename = parts[parts.length - 1] ?? "";
|
|
3855
|
+
return filename.replace(/\.md$/, "");
|
|
3856
|
+
}
|
|
3247
3857
|
|
|
3248
3858
|
// src/sync/orchestrator.ts
|
|
3249
3859
|
var SyncOrchestrator = class {
|
|
@@ -3258,7 +3868,16 @@ var SyncOrchestrator = class {
|
|
|
3258
3868
|
});
|
|
3259
3869
|
this.blockConverter = new BlockConverter();
|
|
3260
3870
|
this.imageHandler = new ImageHandler(vaultFs, config.paths.attachments, notionClient);
|
|
3871
|
+
this.fileHandler = new FileHandler(vaultFs, notionClient, stateDb);
|
|
3261
3872
|
this.propertyMapper = new PropertyMapper();
|
|
3873
|
+
this.databaseSyncer = new DatabaseSyncer(
|
|
3874
|
+
config,
|
|
3875
|
+
stateDb,
|
|
3876
|
+
notionClient,
|
|
3877
|
+
vaultFs,
|
|
3878
|
+
this.pipeline,
|
|
3879
|
+
this.imageHandler
|
|
3880
|
+
);
|
|
3262
3881
|
this.propertyMapper.setWikilinkResolver({
|
|
3263
3882
|
resolve: (title) => stateDb.resolveWikilink(title)?.notionPageId ?? null,
|
|
3264
3883
|
resolvePageId: (pageId) => stateDb.resolvePageId(pageId)?.title ?? null
|
|
@@ -3273,6 +3892,8 @@ var SyncOrchestrator = class {
|
|
|
3273
3892
|
pipeline;
|
|
3274
3893
|
blockConverter;
|
|
3275
3894
|
imageHandler;
|
|
3895
|
+
fileHandler;
|
|
3896
|
+
databaseSyncer;
|
|
3276
3897
|
propertyMapper;
|
|
3277
3898
|
dbSchemaLoaded = false;
|
|
3278
3899
|
async push(options) {
|
|
@@ -3357,6 +3978,26 @@ var SyncOrchestrator = class {
|
|
|
3357
3978
|
}
|
|
3358
3979
|
});
|
|
3359
3980
|
await Promise.all(tasks.map((t) => t()));
|
|
3981
|
+
if (this.config.sync.syncFiles !== false) {
|
|
3982
|
+
try {
|
|
3983
|
+
const fileResults = await this.fileHandler.pushAllFiles();
|
|
3984
|
+
if (fileResults.length > 0) {
|
|
3985
|
+
getLogger().info(`[Im-Nobsidian] ${fileResults.length}\uAC1C \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC`);
|
|
3986
|
+
}
|
|
3987
|
+
} catch (error) {
|
|
3988
|
+
getLogger().warn("[Im-Nobsidian] \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911 \uC624\uB958:", error);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
if ((this.config.notion.databases?.length ?? 0) > 0) {
|
|
3992
|
+
try {
|
|
3993
|
+
const dbResult = await this.databaseSyncer.pushAll();
|
|
3994
|
+
created += dbResult.created;
|
|
3995
|
+
updated += dbResult.updated;
|
|
3996
|
+
failed.push(...dbResult.failed);
|
|
3997
|
+
} catch (error) {
|
|
3998
|
+
getLogger().warn("[Im-Nobsidian] DB Push \uC911 \uC624\uB958:", error);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
3360
4001
|
this.stateDb.setMeta("last_push_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
3361
4002
|
this.stateDb.setMeta("last_sync_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
3362
4003
|
this.stateDb.setMeta("push_in_progress", "");
|
|
@@ -3450,6 +4091,20 @@ var SyncOrchestrator = class {
|
|
|
3450
4091
|
}
|
|
3451
4092
|
});
|
|
3452
4093
|
await Promise.all(tasks.map((t) => t()));
|
|
4094
|
+
if ((this.config.notion.databases?.length ?? 0) > 0) {
|
|
4095
|
+
try {
|
|
4096
|
+
const dbResult = await this.databaseSyncer.pullAll();
|
|
4097
|
+
created += dbResult.created;
|
|
4098
|
+
updated += dbResult.updated;
|
|
4099
|
+
failed.push(...dbResult.failed);
|
|
4100
|
+
if (dbResult.created + dbResult.updated > 0) {
|
|
4101
|
+
const dbPaths = this.stateDb.getByStatus("synced").filter((r) => r.fileType === "db-row").map((r) => r.obsidianPath);
|
|
4102
|
+
writtenPaths.push(...dbPaths.slice(-dbResult.created - dbResult.updated));
|
|
4103
|
+
}
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
getLogger().warn("[Im-Nobsidian] DB Pull \uC911 \uC624\uB958:", error);
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
3453
4108
|
this.stateDb.setMeta("last_pull_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
3454
4109
|
this.stateDb.setMeta("last_sync_at", (/* @__PURE__ */ new Date()).toISOString());
|
|
3455
4110
|
this.stateDb.setMeta("pull_in_progress", "");
|
|
@@ -3594,7 +4249,7 @@ var SyncOrchestrator = class {
|
|
|
3594
4249
|
status: "synced",
|
|
3595
4250
|
baseSnapshot: Buffer.from(content, "utf-8")
|
|
3596
4251
|
});
|
|
3597
|
-
const aliases =
|
|
4252
|
+
const aliases = extractAliases2(conversionResult.properties);
|
|
3598
4253
|
this.stateDb.upsertWikilink({
|
|
3599
4254
|
obsidianPath: path,
|
|
3600
4255
|
notionPageId: page.id,
|
|
@@ -3634,7 +4289,7 @@ var SyncOrchestrator = class {
|
|
|
3634
4289
|
const updatedPage = await this.notionClient.getPage(record.notionPageId);
|
|
3635
4290
|
const hash = computeHash(content);
|
|
3636
4291
|
const title = extractTitle(path);
|
|
3637
|
-
const aliases =
|
|
4292
|
+
const aliases = extractAliases2(conversionResult.properties);
|
|
3638
4293
|
this.stateDb.transaction(() => {
|
|
3639
4294
|
this.stateDb.updateHash(record.id, hash, Buffer.from(content, "utf-8"));
|
|
3640
4295
|
this.stateDb.updateStatus(record.id, "synced");
|
|
@@ -3798,7 +4453,7 @@ var SyncOrchestrator = class {
|
|
|
3798
4453
|
status: "synced",
|
|
3799
4454
|
baseSnapshot: Buffer.from(finalContent, "utf-8")
|
|
3800
4455
|
});
|
|
3801
|
-
const aliases =
|
|
4456
|
+
const aliases = extractAliases2(properties);
|
|
3802
4457
|
this.stateDb.upsertWikilink({
|
|
3803
4458
|
obsidianPath: filePath,
|
|
3804
4459
|
notionPageId: pageId,
|
|
@@ -4017,8 +4672,13 @@ var SyncOrchestrator = class {
|
|
|
4017
4672
|
const baseEnhanced = obsidianToNotionEnhanced(baseSnapshot.toString("utf-8"));
|
|
4018
4673
|
const patches = this.computePatches(baseEnhanced, enhanced);
|
|
4019
4674
|
if (patches.length > 0 && patches.length <= 20) {
|
|
4020
|
-
|
|
4021
|
-
|
|
4675
|
+
try {
|
|
4676
|
+
await this.notionClient.updatePageMarkdownPartial(pageId, patches);
|
|
4677
|
+
return;
|
|
4678
|
+
} catch {
|
|
4679
|
+
await this.notionClient.replacePageMarkdown(pageId, enhanced);
|
|
4680
|
+
return;
|
|
4681
|
+
}
|
|
4022
4682
|
}
|
|
4023
4683
|
}
|
|
4024
4684
|
await this.notionClient.replacePageMarkdown(pageId, enhanced);
|
|
@@ -4133,7 +4793,7 @@ function extractTitle(filePath) {
|
|
|
4133
4793
|
const filename = parts[parts.length - 1] ?? "";
|
|
4134
4794
|
return filename.replace(/\.md$/, "");
|
|
4135
4795
|
}
|
|
4136
|
-
function
|
|
4796
|
+
function extractAliases2(properties) {
|
|
4137
4797
|
const raw = properties.aliases ?? properties.alias;
|
|
4138
4798
|
if (!raw) return [];
|
|
4139
4799
|
if (Array.isArray(raw)) return raw.filter((a) => typeof a === "string");
|
|
@@ -4205,6 +4865,11 @@ var NodeVaultFS = class {
|
|
|
4205
4865
|
await this.walkDir(this.rootPath, files);
|
|
4206
4866
|
return files;
|
|
4207
4867
|
}
|
|
4868
|
+
async listNonMarkdownFiles() {
|
|
4869
|
+
const files = [];
|
|
4870
|
+
await this.walkDirNonMd(this.rootPath, files);
|
|
4871
|
+
return files;
|
|
4872
|
+
}
|
|
4208
4873
|
matchesPattern(relativePath, pattern) {
|
|
4209
4874
|
if (pattern === "**/*" || pattern === "**") return true;
|
|
4210
4875
|
if (pattern.endsWith("/**")) {
|
|
@@ -4227,6 +4892,25 @@ var NodeVaultFS = class {
|
|
|
4227
4892
|
}
|
|
4228
4893
|
return this.excludePatterns.some((p) => this.matchesPattern(relativePath, p));
|
|
4229
4894
|
}
|
|
4895
|
+
async walkDirNonMd(dir, result) {
|
|
4896
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
4897
|
+
for (const entry of entries) {
|
|
4898
|
+
const fullPath = join(dir, entry.name);
|
|
4899
|
+
if (entry.name.startsWith(".")) continue;
|
|
4900
|
+
const relativePath = relative(this.rootPath, fullPath);
|
|
4901
|
+
if (this.isExcluded(relativePath)) continue;
|
|
4902
|
+
if (entry.isDirectory()) {
|
|
4903
|
+
await this.walkDirNonMd(fullPath, result);
|
|
4904
|
+
} else if (entry.isFile() && !entry.name.endsWith(".md")) {
|
|
4905
|
+
const fileStat = await stat(fullPath);
|
|
4906
|
+
result.push({
|
|
4907
|
+
path: relativePath,
|
|
4908
|
+
size: fileStat.size,
|
|
4909
|
+
mtime: fileStat.mtime.toISOString()
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4230
4914
|
async walkDir(dir, result) {
|
|
4231
4915
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
4232
4916
|
for (const entry of entries) {
|
|
@@ -4707,6 +5391,6 @@ var WatchSyncService = class {
|
|
|
4707
5391
|
}
|
|
4708
5392
|
};
|
|
4709
5393
|
|
|
4710
|
-
export { BlockConverter, CalloutRestorer, CalloutTransformer, ChangeDetector, ColorAnnotator, ConfigManager, ConfigSchema, ConflictResolver, ConversionPipeline, DEFAULT_CONFIG, EmbedResolver, FileWatcher, FrontmatterExtractor, FrontmatterGenerator, HtmlAnnotationStripper, ImageHandler, InlineDBParser, LocalImageRestorer, MathNormalizer, MentionToWikilink, NodeVaultFS, NotionBlockBuilder, NotionClient, PreserveMarkerCollector, PreserveMarkerInjector, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, StateDB, SyncOrchestrator, TreeMapper, UnsupportedBlockStripper, WatchSyncService, WikilinkResolver, computeBufferHash, computeHash, createDefaultPipeline, generateId, getLogger, normalizeNotionId, notionIdsEqual, sanitizeFileName, setLogger, threeWayMerge };
|
|
5394
|
+
export { BlockConverter, CalloutRestorer, CalloutTransformer, ChangeDetector, ColorAnnotator, ConfigManager, ConfigSchema, ConflictResolver, ConversionPipeline, DEFAULT_CONFIG, DatabaseSyncer, EmbedResolver, FileHandler, FileWatcher, FrontmatterExtractor, FrontmatterGenerator, HtmlAnnotationStripper, ImageHandler, InlineDBParser, LocalImageRestorer, MathNormalizer, MentionToWikilink, NodeVaultFS, NotionBlockBuilder, NotionClient, PreserveMarkerCollector, PreserveMarkerInjector, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, StateDB, SyncOrchestrator, TreeMapper, UnsupportedBlockStripper, WatchSyncService, WikilinkResolver, computeBufferHash, computeHash, createDefaultPipeline, generateId, getLogger, normalizeNotionId, notionIdsEqual, sanitizeFileName, setLogger, threeWayMerge };
|
|
4711
5395
|
//# sourceMappingURL=index.js.map
|
|
4712
5396
|
//# sourceMappingURL=index.js.map
|