@im-nobsidian/core 0.9.2 → 0.10.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/dist/index.d.ts +21 -1
- package/dist/index.js +270 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -511,6 +511,20 @@ interface PullResult {
|
|
|
511
511
|
readonly linkCount: number;
|
|
512
512
|
/** 원격을 얼마나 훑었나. 원격을 보지 않은 실행(받기 방향이 아님 · 취소)에는 없다. */
|
|
513
513
|
readonly remoteScan?: RemoteScanInfo;
|
|
514
|
+
/** Notion 에서 옮긴 페이지를 볼트에서 옮긴 것(ADR-039) — dry-run 은 옮길 것. 없으면 옮긴 것이 없다. */
|
|
515
|
+
readonly moved?: readonly RemoteMove[];
|
|
516
|
+
/** Notion 에서 옮겼지만 볼트에서 옮기지 않은 것과 그 까닭. */
|
|
517
|
+
readonly moveSkipped?: readonly RemoteMoveSkip[];
|
|
518
|
+
}
|
|
519
|
+
/** Notion 에서 옮긴 페이지를 볼트에서 옮긴 것 하나 — 노트 하나거나 폴더 하나다. */
|
|
520
|
+
interface RemoteMove {
|
|
521
|
+
readonly from: string;
|
|
522
|
+
readonly to: string;
|
|
523
|
+
}
|
|
524
|
+
/** 옮기지 않고 둔 것과 그 까닭 — 볼트에서 직접 옮기면 다음 push 가 Notion 을 맞춘다. */
|
|
525
|
+
interface RemoteMoveSkip {
|
|
526
|
+
readonly path: string;
|
|
527
|
+
readonly reason: string;
|
|
514
528
|
}
|
|
515
529
|
interface SyncResult {
|
|
516
530
|
readonly push: PushResult;
|
|
@@ -2133,6 +2147,8 @@ interface VaultFS {
|
|
|
2133
2147
|
* 이 볼트(연결)가 맡은 경로인가 — 볼트 안의 다른 연결 폴더(ADR-038)는 false. 없으면 전부 맡는다.
|
|
2134
2148
|
*/
|
|
2135
2149
|
ownsPath?(path: string): boolean;
|
|
2150
|
+
/** 이 폴더 안에 다른 연결 폴더가 있나 — 폴더를 통째로 옮기기 전에 묻는다. 없으면 없다. */
|
|
2151
|
+
holdsOtherConnection?(folder: string): boolean;
|
|
2136
2152
|
/**
|
|
2137
2153
|
* 설정(`paths.include` · `paths.exclude`)이나 `.im-nobsidian-ignore` 가 동기화에서 뺀 경로인가 (이슈 #8).
|
|
2138
2154
|
* 뺀 노트는 목록에 없다 — 추적하던 노트라도 「볼트에서 지움」 으로 보지 않고, pull 이 받거나 되살리지도
|
|
@@ -2455,6 +2471,7 @@ declare class SyncOrchestrator {
|
|
|
2455
2471
|
private readonly placement;
|
|
2456
2472
|
private readonly databaseCreator;
|
|
2457
2473
|
private readonly planner;
|
|
2474
|
+
private readonly remoteMoves;
|
|
2458
2475
|
private readonly detector;
|
|
2459
2476
|
private readonly drift;
|
|
2460
2477
|
private readonly discovery;
|
|
@@ -2483,6 +2500,8 @@ declare class SyncOrchestrator {
|
|
|
2483
2500
|
/** 볼트의 노트가 마지막으로 올린 내용 그대로인가 — 읽지 못하면(지움 · 옮김) 아니다. */
|
|
2484
2501
|
private unchangedSincePush;
|
|
2485
2502
|
pull(options?: PullOptions): Promise<PullResult>;
|
|
2503
|
+
/** 감지가 본, Notion 에서 옮긴 페이지를 볼트에서 옮긴다 — 경로를 좁힌 pull 은 그 범위의 것만. */
|
|
2504
|
+
private followRemoteMoves;
|
|
2486
2505
|
private executePull;
|
|
2487
2506
|
sync(options?: SyncOptions): Promise<SyncResult>;
|
|
2488
2507
|
private executeSync;
|
|
@@ -4101,6 +4120,7 @@ declare class ScopedVaultFS implements VaultFS {
|
|
|
4101
4120
|
*/
|
|
4102
4121
|
constructor(base: VaultFS, folder: string, connections: readonly string[]);
|
|
4103
4122
|
ownsPath(path: string): boolean;
|
|
4123
|
+
holdsOtherConnection(folder: string): boolean;
|
|
4104
4124
|
excludesPath(path: string, kind?: "file" | "folder"): boolean;
|
|
4105
4125
|
/** 이 연결의 경로 → 볼트 경로. */
|
|
4106
4126
|
private toBase;
|
|
@@ -4283,4 +4303,4 @@ declare function readColumnLayout(lines: readonly string[]): (ColumnPlace | unde
|
|
|
4283
4303
|
/** 열 이름표 — `열 2/3 · 50%`. 비율이 없으면 `열 2/3`. */
|
|
4284
4304
|
declare function columnLabel(place: ColumnPlace): string;
|
|
4285
4305
|
|
|
4286
|
-
export { BaseFileGenerator, type BaseFileOptions, type BasePropertyOption, type BasePropertySchema, type BaseStatusGroup, type BaseSyncOptions, type BasesFilterNode, type BasesViewMapping, type BasesViewType, BlankLineCollapser, type BookmarkTarget, CONFIG_FILE, type CalendarEntry, type CalloutDesign, CalloutRestorer, CalloutTransformer, ChangeDetector, type ChangeDiff, ColorAnnotator, type ColumnPlace, type CompletenessFailure, type CompletenessLocalSource, type CompletenessOptions, type CompletenessRemoteSource, type CompletenessReport, type Config, ConfigManager, ConfigSchema, type Conflict, ConflictResolver, type ConflictStrategy, ConnectionBoundaryError, ConnectionScopeError, type ConversionContext, type ConversionPath, ConversionPipeline, type ConversionResult, type CoverConfig, type DBEntry, DB_VIEWS_FILE, DB_VIEWS_PATH, DEFAULT_CONFIG, DEFAULT_TITLE_PROPERTY, DEFAULT_WATCH_DEBOUNCE_MS, DIFF_SIGN, type DatabaseCompleteness, type DatabaseSyncConfig, type DatabaseSyncResult, DatabaseSyncer, type DatabaseViewsConfig, type DeferredCode, type DegradeNote, type DesignColor, type DesignLine, type DesignSegment, type DiffHunk, type DiffLine, EmbedResolver, EntryEditor, type FailedOperation, type FidelityClassification, type FidelityContext, type FidelityDefect, type FidelityDefectForm, type FidelitySummary, type FileDownloadResult, FileHandler, type FileInfo, type FileRegistryEntry, type FileStatInfo, type FileSystemEntry, type FileType, type FileUploadResult, FileWatcher, type FilterCondition, type FolderDatabaseReport, type FolderMoveChange, FrontmatterExtractor, FrontmatterGenerator, type FullScanReason, GITIGNORE_ENTRY, type GatedOperation, type GroupByConfig, type GroupedEntries, IGNORE_FILE, INTERNAL_DIR, INTERNAL_DIR_GLOB, type IStateDB, type ImageDownloadResult, ImageHandler, type ImageReference, type InferredProperty, InlineAnnotationPreserver, InlineDBParser, type InlineStyle, type LineDiffOptions, type LineRange, type LocalChange, LocalImageRestorer, type LocalMoveAdoption, type LocalScan, type LocalScanOptions, type Logger, MARKER_BRAND, type MappingResult, type MappingType, MathNormalizer, MentionToWikilink, type MergeConflictRegion, type MergeResult, NOTION_COLOR_NAMES, NOTION_TOKEN_PREFIX, NOTION_TO_BASES_VIEW, NodeVaultFS, type NonMdFileInfo, type NotionBlock, NotionBlockBuilder, NotionClient, type NotionClientOptions, type NotionColor, type NotionPageNode, type NotionRichText, type RichTextItem as NotionRichTextItem, type NotionSidecar, OBSIDIAN_CONFIG_DIR, type OperationType, type PageCompletenessRemoteSource, type PageCompletenessReport, type PageCover, type PageCreationPlan, type PageIcon, PathFilter, type PathFilterConfig, type PendingOperation, PendingWalError, type PlannedRow, type PreserveMarker, PreserveMarkerCollector, PreserveMarkerInjector, type Processor, type ProcessorInput, type ProcessorMetadata, type ProcessorOutput, type ProgressCallback, type ProgressItem, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, type PropertyOption, type PropertySchema, type PropertyValue, type PullOptions, type PullResult, type PushOptions, type PushResult, RENDER_LINE_RULES, type RecordPendingInput, type RegisterFileInput, type RemoteChange, type RemoteObservation, type RemoteScanInfo, type RenameHints, type RenameKind, type RenderFinding, type RenderLineRule, type ResolutionChoice, type ResolutionResult, type RichTextSegment, type RowProgress, SAVED_STATE_TABLES_QUERY, SEPARATOR_BODY, STATE_DB_FILE, STATE_DB_MIGRATIONS, STATE_DB_PATH, SavedStateDbError, ScopedVaultFS, SidecarGenerator, type SidecarProperty, type SidecarView, StateDB, StateDbLockedError, type StateDbMigration, type StateDbTool, StateDbUnavailableError, StateLock, type StateLockClaim, type StateLockOwner, type StatusOptions, type StatusResult, type StyledRange, SyncBusyError, type SyncDirection, type SyncOptions, SyncOrchestrator, type SyncRecord, type SyncResult, type SyncStatus, type TableLineDesign, type TranslationResult, TreeMapper, UnsupportedBlockStripper, type UpsertSyncRecord, type VaultCompletenessReport, type VaultFS, type ViewConfig, ViewDataProvider, type ViewPropertyConfig, type ViewRenderData, type ViewType, WIKILINK_LOOKUPS, type WatchSyncOptions, type WatchSyncScope, WatchSyncService, type WikilinkEntry, type WikilinkLookup, WikilinkResolver, applicableChoices, assertNoPendingWal, basesViewMappingOf, basesViewTypeOf, choiceForStrategy, classifyBodyFidelity, columnLabel, compactNotionId, computeBufferHash, computeHash, configDirFrom, connectionFolderOf, createDefaultPipeline, extractCalendarEntries, filterByMonth, filterEntries, findConnectionFolders, formatUnifiedDiff, generateColorCSS, generateId, getLogger, getNotionBgColor, getNotionColor, getVisibleProperties, groupEntries, hasColumnLayout, hasDesignMarker, inAnyPathScope, isInternalPath, isMarkdownPath, isRemoteDeletion, lineDiff, lintRenderedMarkdown, matchesPathScope, normalizeNotionId, normalizeVaultPath, noteNameFromPath, notionIdFromInput, notionIdsEqual, operationLabel, parseDesignColor, pendingWalBytes, readColumnLayout, readDesignLine, resolvePropertyName, sanitizeFileName, setLogger, sortEntries, stateDbLockPath, stateDbWalPath, stripMarkdownExtension, summarizeFidelity, systemTimeZone, threeWayMerge, toVaultPath, translateNotionFilter, verifyDatabaseCompleteness, verifyPageCompleteness, wikilinkLookupsIn };
|
|
4306
|
+
export { BaseFileGenerator, type BaseFileOptions, type BasePropertyOption, type BasePropertySchema, type BaseStatusGroup, type BaseSyncOptions, type BasesFilterNode, type BasesViewMapping, type BasesViewType, BlankLineCollapser, type BookmarkTarget, CONFIG_FILE, type CalendarEntry, type CalloutDesign, CalloutRestorer, CalloutTransformer, ChangeDetector, type ChangeDiff, ColorAnnotator, type ColumnPlace, type CompletenessFailure, type CompletenessLocalSource, type CompletenessOptions, type CompletenessRemoteSource, type CompletenessReport, type Config, ConfigManager, ConfigSchema, type Conflict, ConflictResolver, type ConflictStrategy, ConnectionBoundaryError, ConnectionScopeError, type ConversionContext, type ConversionPath, ConversionPipeline, type ConversionResult, type CoverConfig, type DBEntry, DB_VIEWS_FILE, DB_VIEWS_PATH, DEFAULT_CONFIG, DEFAULT_TITLE_PROPERTY, DEFAULT_WATCH_DEBOUNCE_MS, DIFF_SIGN, type DatabaseCompleteness, type DatabaseSyncConfig, type DatabaseSyncResult, DatabaseSyncer, type DatabaseViewsConfig, type DeferredCode, type DegradeNote, type DesignColor, type DesignLine, type DesignSegment, type DiffHunk, type DiffLine, EmbedResolver, EntryEditor, type FailedOperation, type FidelityClassification, type FidelityContext, type FidelityDefect, type FidelityDefectForm, type FidelitySummary, type FileDownloadResult, FileHandler, type FileInfo, type FileRegistryEntry, type FileStatInfo, type FileSystemEntry, type FileType, type FileUploadResult, FileWatcher, type FilterCondition, type FolderDatabaseReport, type FolderMoveChange, FrontmatterExtractor, FrontmatterGenerator, type FullScanReason, GITIGNORE_ENTRY, type GatedOperation, type GroupByConfig, type GroupedEntries, IGNORE_FILE, INTERNAL_DIR, INTERNAL_DIR_GLOB, type IStateDB, type ImageDownloadResult, ImageHandler, type ImageReference, type InferredProperty, InlineAnnotationPreserver, InlineDBParser, type InlineStyle, type LineDiffOptions, type LineRange, type LocalChange, LocalImageRestorer, type LocalMoveAdoption, type LocalScan, type LocalScanOptions, type Logger, MARKER_BRAND, type MappingResult, type MappingType, MathNormalizer, MentionToWikilink, type MergeConflictRegion, type MergeResult, NOTION_COLOR_NAMES, NOTION_TOKEN_PREFIX, NOTION_TO_BASES_VIEW, NodeVaultFS, type NonMdFileInfo, type NotionBlock, NotionBlockBuilder, NotionClient, type NotionClientOptions, type NotionColor, type NotionPageNode, type NotionRichText, type RichTextItem as NotionRichTextItem, type NotionSidecar, OBSIDIAN_CONFIG_DIR, type OperationType, type PageCompletenessRemoteSource, type PageCompletenessReport, type PageCover, type PageCreationPlan, type PageIcon, PathFilter, type PathFilterConfig, type PendingOperation, PendingWalError, type PlannedRow, type PreserveMarker, PreserveMarkerCollector, PreserveMarkerInjector, type Processor, type ProcessorInput, type ProcessorMetadata, type ProcessorOutput, type ProgressCallback, type ProgressItem, PropertiesTableInjector, PropertiesTableRestorer, PropertyMapper, type PropertyOption, type PropertySchema, type PropertyValue, type PullOptions, type PullResult, type PushOptions, type PushResult, RENDER_LINE_RULES, type RecordPendingInput, type RegisterFileInput, type RemoteChange, type RemoteMove, type RemoteMoveSkip, type RemoteObservation, type RemoteScanInfo, type RenameHints, type RenameKind, type RenderFinding, type RenderLineRule, type ResolutionChoice, type ResolutionResult, type RichTextSegment, type RowProgress, SAVED_STATE_TABLES_QUERY, SEPARATOR_BODY, STATE_DB_FILE, STATE_DB_MIGRATIONS, STATE_DB_PATH, SavedStateDbError, ScopedVaultFS, SidecarGenerator, type SidecarProperty, type SidecarView, StateDB, StateDbLockedError, type StateDbMigration, type StateDbTool, StateDbUnavailableError, StateLock, type StateLockClaim, type StateLockOwner, type StatusOptions, type StatusResult, type StyledRange, SyncBusyError, type SyncDirection, type SyncOptions, SyncOrchestrator, type SyncRecord, type SyncResult, type SyncStatus, type TableLineDesign, type TranslationResult, TreeMapper, UnsupportedBlockStripper, type UpsertSyncRecord, type VaultCompletenessReport, type VaultFS, type ViewConfig, ViewDataProvider, type ViewPropertyConfig, type ViewRenderData, type ViewType, WIKILINK_LOOKUPS, type WatchSyncOptions, type WatchSyncScope, WatchSyncService, type WikilinkEntry, type WikilinkLookup, WikilinkResolver, applicableChoices, assertNoPendingWal, basesViewMappingOf, basesViewTypeOf, choiceForStrategy, classifyBodyFidelity, columnLabel, compactNotionId, computeBufferHash, computeHash, configDirFrom, connectionFolderOf, createDefaultPipeline, extractCalendarEntries, filterByMonth, filterEntries, findConnectionFolders, formatUnifiedDiff, generateColorCSS, generateId, getLogger, getNotionBgColor, getNotionColor, getVisibleProperties, groupEntries, hasColumnLayout, hasDesignMarker, inAnyPathScope, isInternalPath, isMarkdownPath, isRemoteDeletion, lineDiff, lintRenderedMarkdown, matchesPathScope, normalizeNotionId, normalizeVaultPath, noteNameFromPath, notionIdFromInput, notionIdsEqual, operationLabel, parseDesignColor, pendingWalBytes, readColumnLayout, readDesignLine, resolvePropertyName, sanitizeFileName, setLogger, sortEntries, stateDbLockPath, stateDbWalPath, stripMarkdownExtension, summarizeFidelity, systemTimeZone, threeWayMerge, toVaultPath, translateNotionFilter, verifyDatabaseCompleteness, verifyPageCompleteness, wikilinkLookupsIn };
|
package/dist/index.js
CHANGED
|
@@ -4656,6 +4656,15 @@ function folderNoteOf(folder) {
|
|
|
4656
4656
|
function isFolderRecord(record) {
|
|
4657
4657
|
return (record.fileType === "folder-note" || record.fileType === "folder-only") && !isMarkdownPath(record.obsidianPath);
|
|
4658
4658
|
}
|
|
4659
|
+
function childFolderOf(record, lookup) {
|
|
4660
|
+
if (isFolderRecord(record) && !lookup.databaseAt(record.obsidianPath)) {
|
|
4661
|
+
return record.obsidianPath;
|
|
4662
|
+
}
|
|
4663
|
+
if (record.fileType === "folder-note" || record.fileType === "folder-only") {
|
|
4664
|
+
return parentFolderOf(record.obsidianPath);
|
|
4665
|
+
}
|
|
4666
|
+
return stripMarkdownExtension(record.obsidianPath);
|
|
4667
|
+
}
|
|
4659
4668
|
function parentFolderOf(path) {
|
|
4660
4669
|
const slash = path.lastIndexOf("/");
|
|
4661
4670
|
return slash < 0 ? "" : path.slice(0, slash);
|
|
@@ -5217,6 +5226,7 @@ var FrontmatterExtractor = class {
|
|
|
5217
5226
|
};
|
|
5218
5227
|
|
|
5219
5228
|
// src/utils/md-regions.ts
|
|
5229
|
+
var LIST_MARKER_SOURCE = String.raw`(?:[-*+]|\d{1,9}[.)])`;
|
|
5220
5230
|
var BLANK_LINE_RE = /\n[ \t]*\n/;
|
|
5221
5231
|
function computeAnchor(content, idx) {
|
|
5222
5232
|
const before = content.slice(Math.max(0, idx - 32), idx);
|
|
@@ -5255,7 +5265,9 @@ function closingFence(line, open) {
|
|
|
5255
5265
|
}
|
|
5256
5266
|
return { lead: m[1], bar: m[2] };
|
|
5257
5267
|
}
|
|
5258
|
-
var QUOTED_FENCE_OPEN_RE =
|
|
5268
|
+
var QUOTED_FENCE_OPEN_RE = new RegExp(
|
|
5269
|
+
String.raw`^((?:[\t ]*>)*)([\t ]*)(${LIST_MARKER_SOURCE}[\t ]+)?(\`{3,}|~{3,})(.*)\r?$`
|
|
5270
|
+
);
|
|
5259
5271
|
var QUOTE_MARK_RE = /^[\t ]*>/;
|
|
5260
5272
|
var LEADING_SPACE_RE = /^[\t ]*/;
|
|
5261
5273
|
function stripQuoteMarks(line, depth2) {
|
|
@@ -5352,7 +5364,7 @@ function codeLineMask(content) {
|
|
|
5352
5364
|
}
|
|
5353
5365
|
return inCode;
|
|
5354
5366
|
}
|
|
5355
|
-
var TOP_LIST_ITEM_RE =
|
|
5367
|
+
var TOP_LIST_ITEM_RE = new RegExp(String.raw`^ {0,3}${LIST_MARKER_SOURCE}(?:[\t ]|$)`);
|
|
5356
5368
|
var INDENTED_CODE_WIDTH = 4;
|
|
5357
5369
|
function indentedCodeLineMask(content) {
|
|
5358
5370
|
const lines = content.split("\n");
|
|
@@ -5683,7 +5695,9 @@ function withoutPrefix(line, prefix) {
|
|
|
5683
5695
|
return null;
|
|
5684
5696
|
}
|
|
5685
5697
|
var DOUBLE_DOLLAR_RE = /(?<![\\$])\$\$(?!\$)/g;
|
|
5686
|
-
var OWN_LINE_DOLLARS_RE =
|
|
5698
|
+
var OWN_LINE_DOLLARS_RE = new RegExp(
|
|
5699
|
+
String.raw`^(?:[ \t]*>)*[ \t]*(?:\[![^\]]*\][+-]?[ \t]*|${LIST_MARKER_SOURCE}[ \t]+)?\$\$[ \t]*$`
|
|
5700
|
+
);
|
|
5687
5701
|
function settleDollars(content, final) {
|
|
5688
5702
|
return mapOutsideCode(
|
|
5689
5703
|
content,
|
|
@@ -6208,7 +6222,7 @@ function widenAmbiguousFences(lines, blocks) {
|
|
|
6208
6222
|
}
|
|
6209
6223
|
return widened;
|
|
6210
6224
|
}
|
|
6211
|
-
var LIST_ITEM_LINE_RE =
|
|
6225
|
+
var LIST_ITEM_LINE_RE = new RegExp(String.raw`^[\t ]*${LIST_MARKER_SOURCE}(?:[\t ]|$)`);
|
|
6212
6226
|
function alignNestedCodeBodies(text) {
|
|
6213
6227
|
const lines = text.split("\n");
|
|
6214
6228
|
const blocks = scanContainerBlocks(lines);
|
|
@@ -8052,7 +8066,7 @@ function clampCalloutIndent(content) {
|
|
|
8052
8066
|
}
|
|
8053
8067
|
return out.join("\n");
|
|
8054
8068
|
}
|
|
8055
|
-
var BODY_INDENT_RE =
|
|
8069
|
+
var BODY_INDENT_RE = new RegExp(String.raw`^\t+(?![ \t]*${LIST_MARKER_SOURCE}(?:\s|$))`);
|
|
8056
8070
|
function clampBodyIndent(line) {
|
|
8057
8071
|
return line.replace(BODY_INDENT_RE, CLAMPED_INDENT);
|
|
8058
8072
|
}
|
|
@@ -8253,10 +8267,10 @@ function stripQuotes(s) {
|
|
|
8253
8267
|
|
|
8254
8268
|
// src/converter/lazy-continuation.ts
|
|
8255
8269
|
var QUOTE_PREFIX_RE2 = /^[\t >]*>/;
|
|
8256
|
-
var LIST_ITEM_RE =
|
|
8270
|
+
var LIST_ITEM_RE = new RegExp(String.raw`^${LIST_MARKER_SOURCE}(?:[\t ]|$)`);
|
|
8257
8271
|
var NOT_LAZY_RE = /^(?:#{1,6}(?:[\t ]|$)|>|\[!|(?:[-*_][\t ]*){3,}$|\||<br\s*\/?>[\t ]*$|%%)/;
|
|
8258
8272
|
var NOT_PARAGRAPH_RE = /^(?:#{1,6}(?:[\t ]|$)|(?:[-*_][\t ]*){3,}$|\||<br\s*\/?>[\t ]*$|\$\$|%%)/;
|
|
8259
|
-
var LIST_PARAGRAPH_RE =
|
|
8273
|
+
var LIST_PARAGRAPH_RE = new RegExp(String.raw`^${LIST_MARKER_SOURCE}[\t ]+\S`);
|
|
8260
8274
|
var FENCE_LINE_RE2 = /^(?:`{3,}|~{3,})/;
|
|
8261
8275
|
function continuesListParagraph(previous, line) {
|
|
8262
8276
|
const prev = splitQuoted(previous);
|
|
@@ -8327,7 +8341,7 @@ var BlockSpacer = class {
|
|
|
8327
8341
|
};
|
|
8328
8342
|
}
|
|
8329
8343
|
};
|
|
8330
|
-
var LIST_ITEM_RE2 =
|
|
8344
|
+
var LIST_ITEM_RE2 = new RegExp(String.raw`^[ \t]*${LIST_MARKER_SOURCE}(?:\s|$)`);
|
|
8331
8345
|
var QUOTE_RE = /^[ \t]*>/;
|
|
8332
8346
|
var CALLOUT_START_RE = /^[ \t]*>\s*\[![^\]]*\]/;
|
|
8333
8347
|
var TABLE_ROW_RE = /^[ \t]*\|/;
|
|
@@ -8702,7 +8716,7 @@ var RENDER_LINE_RULES = [
|
|
|
8702
8716
|
var SEPARATOR_BODY = "[ \\t:|-]*-{3,}";
|
|
8703
8717
|
var SEPARATOR_RE = new RegExp(`^[\\t ]*>?[\\t ]*\\|${SEPARATOR_BODY}[ \\t:|-]*\\|`);
|
|
8704
8718
|
var DEEP_FENCE_RE = /^ {4,}```/;
|
|
8705
|
-
var LIST_ITEM_RE3 =
|
|
8719
|
+
var LIST_ITEM_RE3 = new RegExp(String.raw`^([\t ]*)(${LIST_MARKER_SOURCE})(?:[\t ]|$)`);
|
|
8706
8720
|
var CODE_INDENT = 4;
|
|
8707
8721
|
var FOOTNOTE_CONTENT_WIDTH = 4;
|
|
8708
8722
|
var ATX_HEADING_RE = /^ {0,3}#{1,6}(?:[\t ]|$)/;
|
|
@@ -10209,7 +10223,7 @@ function pipeTablesToNfmTables(content) {
|
|
|
10209
10223
|
// src/converter/empty-block.ts
|
|
10210
10224
|
var EMPTY_BLOCK_LINE_RE = new RegExp(`^(${CONTAINER_PREFIX_SOURCE})<empty-block\\/>[\\t ]*$`);
|
|
10211
10225
|
var BREAK_LINE_RE = new RegExp(`^(${CONTAINER_PREFIX_SOURCE})<br\\s*\\/?>[\\t ]*$`);
|
|
10212
|
-
var LIST_ITEM_RE4 =
|
|
10226
|
+
var LIST_ITEM_RE4 = new RegExp(String.raw`^${LIST_MARKER_SOURCE}(?:\s|$)`);
|
|
10213
10227
|
function mapProseLines(content, map) {
|
|
10214
10228
|
const lines = content.split("\n");
|
|
10215
10229
|
const kinds = classifyContainerLines(lines.map((line) => splitContainerPrefix(line).body));
|
|
@@ -11196,7 +11210,7 @@ function unescapeAngleBrackets(content) {
|
|
|
11196
11210
|
(segment) => segment.split("\n").map(unescapeLine).join("\n")
|
|
11197
11211
|
);
|
|
11198
11212
|
}
|
|
11199
|
-
var BLOCK_PREFIX_RE =
|
|
11213
|
+
var BLOCK_PREFIX_RE = new RegExp(String.raw`^[ \t]*(?:(?:>|${LIST_MARKER_SOURCE})[ \t]*)*$`);
|
|
11200
11214
|
var TAG_START_RE = /[A-Za-z/!?]/;
|
|
11201
11215
|
function unescapeLine(line) {
|
|
11202
11216
|
if (!line.includes("\\<") && !line.includes("\\>")) return line;
|
|
@@ -15209,16 +15223,16 @@ var PageDecorations = class {
|
|
|
15209
15223
|
*
|
|
15210
15224
|
* @param baseName 내려받은 파일 이름의 앞부분 — 보통 노트 이름
|
|
15211
15225
|
*/
|
|
15212
|
-
async pulled(page,
|
|
15226
|
+
async pulled(page, baseName3, download) {
|
|
15213
15227
|
const out = {};
|
|
15214
15228
|
const cover = this.notionClient.extractCover(page);
|
|
15215
15229
|
if (cover) {
|
|
15216
|
-
const local = download ? await this.imageHandler.localizeNotionFileUrl(cover.url, `${
|
|
15230
|
+
const local = download ? await this.imageHandler.localizeNotionFileUrl(cover.url, `${baseName3}-cover`) : null;
|
|
15217
15231
|
out.cover = local ? `[[${local}]]` : cover.url;
|
|
15218
15232
|
}
|
|
15219
15233
|
const icon = this.notionClient.extractIcon(page);
|
|
15220
15234
|
if (icon) {
|
|
15221
|
-
const local = download && icon.type === "file" ? await this.imageHandler.localizeNotionFileUrl(icon.value, `${
|
|
15235
|
+
const local = download && icon.type === "file" ? await this.imageHandler.localizeNotionFileUrl(icon.value, `${baseName3}-icon`) : null;
|
|
15222
15236
|
out.icon = local ? `[[${local}]]` : icon.value;
|
|
15223
15237
|
}
|
|
15224
15238
|
return out;
|
|
@@ -16718,6 +16732,10 @@ var ScopedVaultFS = class {
|
|
|
16718
16732
|
const joined = normalizeVaultPath(this.folder === "" ? path : `${this.folder}/${path}`);
|
|
16719
16733
|
return joined !== null && matchesPathScope(joined, this.folder) && connectionFolderOf(joined, this.nested) === null;
|
|
16720
16734
|
}
|
|
16735
|
+
holdsOtherConnection(folder) {
|
|
16736
|
+
const joined = normalizeVaultPath(this.folder === "" ? folder : `${this.folder}/${folder}`);
|
|
16737
|
+
return joined !== null && this.nested.some((c) => matchesPathScope(c, joined));
|
|
16738
|
+
}
|
|
16721
16739
|
excludesPath(path, kind) {
|
|
16722
16740
|
const joined = normalizeVaultPath(this.folder === "" ? path : `${this.folder}/${path}`);
|
|
16723
16741
|
return joined !== null && (this.base.excludesPath?.(joined, kind) ?? false);
|
|
@@ -18587,6 +18605,199 @@ var LocalPlanner = class {
|
|
|
18587
18605
|
}
|
|
18588
18606
|
};
|
|
18589
18607
|
|
|
18608
|
+
// src/sync/remote-moves.ts
|
|
18609
|
+
var MOVABLE_TYPES = /* @__PURE__ */ new Set([
|
|
18610
|
+
"file",
|
|
18611
|
+
"folder-note",
|
|
18612
|
+
"folder-only"
|
|
18613
|
+
]);
|
|
18614
|
+
function movedInNotion(record, parentId) {
|
|
18615
|
+
return MOVABLE_TYPES.has(record.fileType) && record.notionParentId !== null && !notionIdsEqual(record.notionParentId, parentId);
|
|
18616
|
+
}
|
|
18617
|
+
var RemoteMoveFollower = class {
|
|
18618
|
+
constructor(config, stateDb, vaultFs, folderLookup) {
|
|
18619
|
+
this.config = config;
|
|
18620
|
+
this.stateDb = stateDb;
|
|
18621
|
+
this.vaultFs = vaultFs;
|
|
18622
|
+
this.folderLookup = folderLookup;
|
|
18623
|
+
}
|
|
18624
|
+
config;
|
|
18625
|
+
stateDb;
|
|
18626
|
+
vaultFs;
|
|
18627
|
+
folderLookup;
|
|
18628
|
+
/**
|
|
18629
|
+
* @param moved 페이지 id → Notion 의 새 부모 페이지 id.
|
|
18630
|
+
* @param options.dryRun 옮길 자리만 정하고 쓰지 않는다.
|
|
18631
|
+
*/
|
|
18632
|
+
async follow(moved, options = {}) {
|
|
18633
|
+
const result = { moved: [], skipped: [] };
|
|
18634
|
+
for (const [pageId, parentId] of moved) {
|
|
18635
|
+
const record = this.stateDb.getByNotionId(pageId);
|
|
18636
|
+
if (!record || !movedInNotion(record, parentId)) continue;
|
|
18637
|
+
if (this.vaultFs.excludesPath?.(record.obsidianPath)) continue;
|
|
18638
|
+
let outcome;
|
|
18639
|
+
try {
|
|
18640
|
+
outcome = await this.followOne(record, parentId, options.dryRun === true);
|
|
18641
|
+
} catch (error) {
|
|
18642
|
+
outcome = {
|
|
18643
|
+
path: record.obsidianPath,
|
|
18644
|
+
reason: `\uC62E\uAE30\uC9C0 \uBABB\uD568: ${error instanceof Error ? error.message : String(error)}`
|
|
18645
|
+
};
|
|
18646
|
+
}
|
|
18647
|
+
if ("reason" in outcome) {
|
|
18648
|
+
result.skipped.push(outcome);
|
|
18649
|
+
getLogger().warn(
|
|
18650
|
+
`[Im-Nobsidian] Notion \uC5D0\uC11C \uC62E\uAE34 \uD398\uC774\uC9C0\uB97C \uBCFC\uD2B8\uC5D0\uC11C \uC62E\uAE30\uC9C0 \uC54A\uC74C (${outcome.path}): ${outcome.reason}`
|
|
18651
|
+
);
|
|
18652
|
+
} else if (outcome.moves.length > 0) {
|
|
18653
|
+
result.moved.push(...outcome.moves);
|
|
18654
|
+
}
|
|
18655
|
+
}
|
|
18656
|
+
return result;
|
|
18657
|
+
}
|
|
18658
|
+
async followOne(record, parentId, dryRun) {
|
|
18659
|
+
const skip = (reason) => ({ path: record.obsidianPath, reason });
|
|
18660
|
+
if (this.stateDb.getIncompleteOpByState(record.id, "move")) {
|
|
18661
|
+
return skip(
|
|
18662
|
+
"\uBCFC\uD2B8\uC5D0\uC11C \uC62E\uAE34 \uAC83\uC744 \uC544\uC9C1 \uC62C\uB9AC\uC9C0 \uC54A\uC558\uB2E4 \u2014 \uB2E4\uC74C push \uAC00 Notion \uC744 \uBCFC\uD2B8\uC758 \uC790\uB9AC\uB85C \uC62E\uAE34\uB2E4"
|
|
18663
|
+
);
|
|
18664
|
+
}
|
|
18665
|
+
const folder = this.parentFolder(parentId);
|
|
18666
|
+
if (folder === null) return skip("Notion \uC758 \uC0C8 \uBD80\uBAA8\uAC00 \uBCFC\uD2B8\uC5D0 \uC5C6\uB2E4");
|
|
18667
|
+
const units = this.unitsOf(record);
|
|
18668
|
+
const moves = units.map((from) => ({ from, to: joinPath(folder, baseName2(from)) }));
|
|
18669
|
+
if (moves.every((move) => move.from === move.to)) {
|
|
18670
|
+
if (!dryRun) this.stateDb.setNotionParentId(record.id, parentId);
|
|
18671
|
+
return { moves: [] };
|
|
18672
|
+
}
|
|
18673
|
+
for (const move of moves) {
|
|
18674
|
+
const blocked = await this.blocked(move);
|
|
18675
|
+
if (blocked) return skip(blocked);
|
|
18676
|
+
}
|
|
18677
|
+
if (dryRun) return { moves };
|
|
18678
|
+
const done = [];
|
|
18679
|
+
try {
|
|
18680
|
+
for (const move of moves) {
|
|
18681
|
+
await this.vaultFs.moveFile(move.from, move.to);
|
|
18682
|
+
done.push(move);
|
|
18683
|
+
}
|
|
18684
|
+
} catch (error) {
|
|
18685
|
+
for (const move of done.reverse())
|
|
18686
|
+
await this.vaultFs.moveFile(move.to, move.from).catch(() => {
|
|
18687
|
+
});
|
|
18688
|
+
throw error;
|
|
18689
|
+
}
|
|
18690
|
+
this.stateDb.transaction(() => {
|
|
18691
|
+
for (const move of moves) this.rewritePaths(move);
|
|
18692
|
+
this.stateDb.setNotionParentId(record.id, parentId);
|
|
18693
|
+
});
|
|
18694
|
+
for (const move of moves) {
|
|
18695
|
+
getLogger().info(
|
|
18696
|
+
`[Im-Nobsidian] Notion \uC5D0\uC11C \uC62E\uAE34 \uD398\uC774\uC9C0\uB97C \uBCFC\uD2B8\uC5D0\uC11C \uC62E\uAE40: ${move.from} \u2192 ${move.to}`
|
|
18697
|
+
);
|
|
18698
|
+
}
|
|
18699
|
+
return { moves };
|
|
18700
|
+
}
|
|
18701
|
+
/** 새 부모 아래의 자식이 놓일 폴더 — 새 페이지를 받을 때와 같은 규칙. 루트면 `""`, 볼트에 없는 부모면 null. */
|
|
18702
|
+
parentFolder(parentId) {
|
|
18703
|
+
if (notionIdsEqual(parentId, this.config.notion.rootPageId)) return "";
|
|
18704
|
+
const parent = this.stateDb.getByNotionId(parentId);
|
|
18705
|
+
return parent ? childFolderOf(parent, this.folderLookup()) : null;
|
|
18706
|
+
}
|
|
18707
|
+
/**
|
|
18708
|
+
* 함께 옮길 것 — 폴더로 추적하는 페이지는 그 폴더, 폴더 노트(`X/X.md`)는 든 폴더, 노트는 그 파일과 옆의 자식
|
|
18709
|
+
* 폴더(`X/`, 있으면).
|
|
18710
|
+
*/
|
|
18711
|
+
unitsOf(record) {
|
|
18712
|
+
if (isFolderRecord(record)) return [record.obsidianPath];
|
|
18713
|
+
if (record.fileType !== "file") return [parentFolderOf(record.obsidianPath)];
|
|
18714
|
+
const children = stripMarkdownExtension(record.obsidianPath);
|
|
18715
|
+
const hasChildren = this.stateDb.getAll().some((r) => within(r.obsidianPath, children));
|
|
18716
|
+
return hasChildren ? [record.obsidianPath, children] : [record.obsidianPath];
|
|
18717
|
+
}
|
|
18718
|
+
/** 옮기지 못할 까닭 — 없으면 null. */
|
|
18719
|
+
async blocked(move) {
|
|
18720
|
+
if (within(move.to, move.from)) return "\uC81C \uC548\uC73C\uB85C \uC62E\uAE38 \uC218 \uC5C6\uB2E4";
|
|
18721
|
+
if (!await this.vaultFs.exists(move.from)) return "\uBCFC\uD2B8\uC5D0 \uB178\uD2B8\uAC00 \uC5C6\uB2E4";
|
|
18722
|
+
if (await this.vaultFs.exists(move.to) || this.stateDb.getAll().some((r) => within(r.obsidianPath, move.to))) {
|
|
18723
|
+
return `\uC62E\uAE38 \uC790\uB9AC(${move.to})\uC5D0 \uAC19\uC740 \uC774\uB984\uC774 \uC788\uB2E4`;
|
|
18724
|
+
}
|
|
18725
|
+
const isFolder = !isMarkdownPath(move.from);
|
|
18726
|
+
if (this.vaultFs.excludesPath?.(move.to, isFolder ? "folder" : "file")) {
|
|
18727
|
+
return `\uC62E\uAE38 \uC790\uB9AC(${move.to})\uAC00 \uB3D9\uAE30\uD654\uC5D0\uC11C \uBE60\uC9C4\uB2E4(paths \xB7 .im-nobsidian-ignore)`;
|
|
18728
|
+
}
|
|
18729
|
+
if (isFolder && this.holdsDatabaseOrConnection(move.from)) {
|
|
18730
|
+
return "\uD3F4\uB354 \uC548\uC5D0 DB \uB098 \uB2E4\uB978 \uC5F0\uACB0\uC774 \uC788\uB2E4 \u2014 DB \uBCF4\uAE30(.base)\uAC00 \uD3F4\uB354 \uACBD\uB85C\uB97C \uD488\uB294\uB2E4. Obsidian \uC5D0\uC11C \uC62E\uAE30\uBA74 \uB2E4\uC74C push \uAC00 Notion \uC744 \uB9DE\uCD98\uB2E4";
|
|
18731
|
+
}
|
|
18732
|
+
return null;
|
|
18733
|
+
}
|
|
18734
|
+
holdsDatabaseOrConnection(folder) {
|
|
18735
|
+
const databases = parseDiscoveredDbs(this.stateDb.getMeta(DISCOVERED_DBS_META_KEY));
|
|
18736
|
+
if (databases.some((db) => within(db.localFolder.replace(/\/+$/, ""), folder))) return true;
|
|
18737
|
+
const baseFiles = this.stateDb.getMeta(DB_BASE_FILES_META_KEY);
|
|
18738
|
+
if (baseFiles) {
|
|
18739
|
+
try {
|
|
18740
|
+
const entries = Object.values(
|
|
18741
|
+
JSON.parse(baseFiles)
|
|
18742
|
+
);
|
|
18743
|
+
if (entries.some((entry) => entry.basePath && within(entry.basePath, folder))) return true;
|
|
18744
|
+
} catch {
|
|
18745
|
+
}
|
|
18746
|
+
}
|
|
18747
|
+
if (this.stateDb.getAll().some((r) => r.fileType === "db-row" && within(r.obsidianPath, folder))) {
|
|
18748
|
+
return true;
|
|
18749
|
+
}
|
|
18750
|
+
return this.vaultFs.holdsOtherConnection?.(folder) ?? false;
|
|
18751
|
+
}
|
|
18752
|
+
/** 옮긴 것 아래의 레코드 · 위키링크 · 보존 마커 · 첨부 기록을 새 경로로 옮겨 적는다. */
|
|
18753
|
+
rewritePaths(move) {
|
|
18754
|
+
for (const record of this.stateDb.getAll()) {
|
|
18755
|
+
if (!within(record.obsidianPath, move.from)) continue;
|
|
18756
|
+
const from = record.obsidianPath;
|
|
18757
|
+
const to = move.to + from.slice(move.from.length);
|
|
18758
|
+
this.stateDb.updatePath(record.id, to);
|
|
18759
|
+
const entry = record.notionPageId ? this.stateDb.resolvePageId(record.notionPageId) : null;
|
|
18760
|
+
if (entry && entry.obsidianPath === from) {
|
|
18761
|
+
this.stateDb.deleteWikilink(from);
|
|
18762
|
+
this.stateDb.upsertWikilink({
|
|
18763
|
+
obsidianPath: to,
|
|
18764
|
+
notionPageId: entry.notionPageId,
|
|
18765
|
+
title: wikilinkTitleFromPath(to),
|
|
18766
|
+
aliases: entry.aliases
|
|
18767
|
+
});
|
|
18768
|
+
}
|
|
18769
|
+
const markers = this.stateDb.getPreserveMarkers(from);
|
|
18770
|
+
if (markers.length > 0) {
|
|
18771
|
+
this.stateDb.storePreserveMarkers(to, markers);
|
|
18772
|
+
this.stateDb.storePreserveMarkers(from, []);
|
|
18773
|
+
}
|
|
18774
|
+
if (record.notionPageId) {
|
|
18775
|
+
for (const file of this.stateDb.getFilesByPageId(record.notionPageId)) {
|
|
18776
|
+
if (!within(file.localPath, move.from)) continue;
|
|
18777
|
+
this.stateDb.registerFile({
|
|
18778
|
+
localPath: move.to + file.localPath.slice(move.from.length),
|
|
18779
|
+
notionPageId: file.notionPageId,
|
|
18780
|
+
fileUploadId: file.fileUploadId,
|
|
18781
|
+
fileType: file.fileType,
|
|
18782
|
+
fileHash: file.fileHash,
|
|
18783
|
+
fileSize: file.fileSize
|
|
18784
|
+
});
|
|
18785
|
+
this.stateDb.deleteFileRegistry(file.localPath);
|
|
18786
|
+
}
|
|
18787
|
+
}
|
|
18788
|
+
}
|
|
18789
|
+
}
|
|
18790
|
+
};
|
|
18791
|
+
function within(path, unit) {
|
|
18792
|
+
return path === unit || path.startsWith(`${unit}/`);
|
|
18793
|
+
}
|
|
18794
|
+
function baseName2(path) {
|
|
18795
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
18796
|
+
}
|
|
18797
|
+
function joinPath(folder, name) {
|
|
18798
|
+
return folder === "" ? name : `${folder}/${name}`;
|
|
18799
|
+
}
|
|
18800
|
+
|
|
18590
18801
|
// src/sync/change-inspector.ts
|
|
18591
18802
|
var ChangeInspector = class {
|
|
18592
18803
|
constructor(stateDb, vaultFs, notionClient, planner, puller) {
|
|
@@ -21660,17 +21871,7 @@ var PagePuller = class {
|
|
|
21660
21871
|
const parentId = await extractParentId(this.notionClient, page);
|
|
21661
21872
|
if (!parentId || notionIdsEqual(parentId, this.config.notion.rootPageId)) return "";
|
|
21662
21873
|
const parentRecord = this.stateDb.getByNotionId(parentId);
|
|
21663
|
-
if (parentRecord)
|
|
21664
|
-
if (isFolderRecord(parentRecord) && !this.placement.folderLookup().databaseAt(parentRecord.obsidianPath)) {
|
|
21665
|
-
return parentRecord.obsidianPath;
|
|
21666
|
-
}
|
|
21667
|
-
if (parentRecord.fileType === "folder-note" || parentRecord.fileType === "folder-only") {
|
|
21668
|
-
const pathParts = parentRecord.obsidianPath.split("/");
|
|
21669
|
-
pathParts.pop();
|
|
21670
|
-
return pathParts.join("/");
|
|
21671
|
-
}
|
|
21672
|
-
return stripMarkdownExtension(parentRecord.obsidianPath);
|
|
21673
|
-
}
|
|
21874
|
+
if (parentRecord) return childFolderOf(parentRecord, this.placement.folderLookup());
|
|
21674
21875
|
try {
|
|
21675
21876
|
const parentPage = await this.notionClient.getPage(parentId);
|
|
21676
21877
|
const parentTitle = this.notionClient.extractTitle(parentPage);
|
|
@@ -22048,6 +22249,9 @@ var RemoteDetector = class _RemoteDetector {
|
|
|
22048
22249
|
// 감지가 빠뜨린 자식을 {@link takeUndiscoveredChildren} 가 둘의 차로 고른다.
|
|
22049
22250
|
_bodyChildren = /* @__PURE__ */ new Map();
|
|
22050
22251
|
_queued = /* @__PURE__ */ new Set();
|
|
22252
|
+
// Notion 에서 부모가 바뀐 추적 페이지(정규화 id → 새 부모 페이지 id) — pull 이 볼트에서도 옮긴다(ADR-039).
|
|
22253
|
+
// 옮겨도 수정 시각이 바뀌지 않아(실측) 변경으로는 보이지 않는다. 감지마다 다시 채운다.
|
|
22254
|
+
_moved = /* @__PURE__ */ new Map();
|
|
22051
22255
|
/** 이번 감지에서 자식 페이지를 가진 것으로 본 페이지인가 — 폴더노트 판정의 1차 근거. */
|
|
22052
22256
|
hasChildPages(pageId) {
|
|
22053
22257
|
return this._childParentIds.has(normalizeNotionId(pageId));
|
|
@@ -22109,6 +22313,16 @@ var RemoteDetector = class _RemoteDetector {
|
|
|
22109
22313
|
}
|
|
22110
22314
|
return changes;
|
|
22111
22315
|
}
|
|
22316
|
+
/** 이번 감지가 본, Notion 에서 옮긴 추적 페이지 — 페이지 id → 새 부모 페이지 id. */
|
|
22317
|
+
get movedPages() {
|
|
22318
|
+
return this._moved;
|
|
22319
|
+
}
|
|
22320
|
+
/** 받은 부모가 상태 DB 의 것과 다르면 옮긴 페이지로 적는다. */
|
|
22321
|
+
noteParent(pageId, parentId) {
|
|
22322
|
+
const record = this.stateDb.getByNotionId(pageId);
|
|
22323
|
+
if (record && movedInNotion(record, parentId))
|
|
22324
|
+
this._moved.set(normalizeNotionId(pageId), parentId);
|
|
22325
|
+
}
|
|
22112
22326
|
/** 새 감지를 시작한다 — 지난 실행의 본문 자식과 받기로 한 페이지를 비우고 이번 변경을 적는다. */
|
|
22113
22327
|
beginDetection(changes) {
|
|
22114
22328
|
this._bodyChildren.clear();
|
|
@@ -22155,6 +22369,7 @@ var RemoteDetector = class _RemoteDetector {
|
|
|
22155
22369
|
const changes = [];
|
|
22156
22370
|
this._childParentIds.clear();
|
|
22157
22371
|
this._pages.clear();
|
|
22372
|
+
this._moved.clear();
|
|
22158
22373
|
const lastPull = this.stateDb.getMeta(LAST_PULL_META_KEY);
|
|
22159
22374
|
const syncedRecords = this.stateDb.getAll();
|
|
22160
22375
|
const trackedPageIds = new Set(
|
|
@@ -22212,7 +22427,10 @@ var RemoteDetector = class _RemoteDetector {
|
|
|
22212
22427
|
for (const page of underRoot) {
|
|
22213
22428
|
throwIfAborted(signal);
|
|
22214
22429
|
const parentId = await extractParentId(this.notionClient, page);
|
|
22215
|
-
if (parentId)
|
|
22430
|
+
if (parentId) {
|
|
22431
|
+
this._childParentIds.add(normalizeNotionId(parentId));
|
|
22432
|
+
this.noteParent(page.id, parentId);
|
|
22433
|
+
}
|
|
22216
22434
|
}
|
|
22217
22435
|
for (const page of underRoot) {
|
|
22218
22436
|
titles.set(normalizeNotionId(page.id), this.notionClient.extractTitle(page));
|
|
@@ -22297,6 +22515,7 @@ var RemoteDetector = class _RemoteDetector {
|
|
|
22297
22515
|
const databaseIds = /* @__PURE__ */ new Set();
|
|
22298
22516
|
this._childParentIds.clear();
|
|
22299
22517
|
this._pages.clear();
|
|
22518
|
+
this._moved.clear();
|
|
22300
22519
|
const searchSince = incrementalSearchSince(since);
|
|
22301
22520
|
const recentPages = await this.notionClient.searchRecentPages(searchSince, options.signal);
|
|
22302
22521
|
const untracked = [];
|
|
@@ -22319,6 +22538,7 @@ var RemoteDetector = class _RemoteDetector {
|
|
|
22319
22538
|
}
|
|
22320
22539
|
continue;
|
|
22321
22540
|
}
|
|
22541
|
+
if (page.parent.type === "page_id") this.noteParent(page.id, page.parent.page_id);
|
|
22322
22542
|
const modified = this.observation.modification(record, page);
|
|
22323
22543
|
const rowDatabase = page.parentDatabaseId ?? (record.fileType === "db-row" ? record.notionParentId : null);
|
|
22324
22544
|
if (modified && rowDatabase) databaseIds.add(compactNotionId(rowDatabase));
|
|
@@ -22888,6 +23108,12 @@ var SyncOrchestrator = class {
|
|
|
22888
23108
|
);
|
|
22889
23109
|
this.placement.useDatabaseCreator(this.databaseCreator);
|
|
22890
23110
|
this.planner = new LocalPlanner(config, stateDb, vaultFs, changeDetector, this.placement);
|
|
23111
|
+
this.remoteMoves = new RemoteMoveFollower(
|
|
23112
|
+
config,
|
|
23113
|
+
stateDb,
|
|
23114
|
+
vaultFs,
|
|
23115
|
+
() => this.placement.folderLookup()
|
|
23116
|
+
);
|
|
22891
23117
|
this.puller = new PagePuller(
|
|
22892
23118
|
config,
|
|
22893
23119
|
stateDb,
|
|
@@ -22955,6 +23181,7 @@ var SyncOrchestrator = class {
|
|
|
22955
23181
|
databaseCreator;
|
|
22956
23182
|
// 이번 실행의 로컬 변경 — 옮긴 노트 · 폴더를 짝짓고 상태 DB 에 옮겨 적는다(S-11).
|
|
22957
23183
|
planner;
|
|
23184
|
+
remoteMoves;
|
|
22958
23185
|
// 원격에서 바뀐 것 — 전체 대조 · 증분 감지(ADR-027).
|
|
22959
23186
|
detector;
|
|
22960
23187
|
// 지난번에 본 뒤로 원격이 바뀌었나(N-05) — 덮어쓰기 · 지우기 전에, 변경을 보이기 전에 본다.
|
|
@@ -23299,6 +23526,17 @@ var SyncOrchestrator = class {
|
|
|
23299
23526
|
async pull(options) {
|
|
23300
23527
|
return this.gate.run("pull", () => this.executePull(options));
|
|
23301
23528
|
}
|
|
23529
|
+
/** 감지가 본, Notion 에서 옮긴 페이지를 볼트에서 옮긴다 — 경로를 좁힌 pull 은 그 범위의 것만. */
|
|
23530
|
+
async followRemoteMoves(options) {
|
|
23531
|
+
const moved = new Map(
|
|
23532
|
+
[...this.detector.movedPages].filter(([pageId]) => {
|
|
23533
|
+
const record = this.stateDb.getByNotionId(pageId);
|
|
23534
|
+
return !!record && (!options?.paths || inAnyPathScope(record.obsidianPath, options.paths));
|
|
23535
|
+
})
|
|
23536
|
+
);
|
|
23537
|
+
if (moved.size === 0) return { moved: [], skipped: [] };
|
|
23538
|
+
return await this.remoteMoves.follow(moved, { dryRun: options?.dryRun });
|
|
23539
|
+
}
|
|
23302
23540
|
async executePull(options) {
|
|
23303
23541
|
const startTime = Date.now();
|
|
23304
23542
|
this.assertConnectionScope();
|
|
@@ -23358,6 +23596,7 @@ var SyncOrchestrator = class {
|
|
|
23358
23596
|
}
|
|
23359
23597
|
const remoteChanges = detection.changes;
|
|
23360
23598
|
detected = remoteChanges;
|
|
23599
|
+
const remoteMoves = await this.followRemoteMoves(options);
|
|
23361
23600
|
const pendingDatabases = parsePendingDatabases(
|
|
23362
23601
|
this.stateDb.getMeta(PENDING_DATABASES_META_KEY)
|
|
23363
23602
|
);
|
|
@@ -23407,7 +23646,9 @@ var SyncOrchestrator = class {
|
|
|
23407
23646
|
this.config.sync.deleteSync
|
|
23408
23647
|
),
|
|
23409
23648
|
skippedDatabases: ledger.skipped
|
|
23410
|
-
}
|
|
23649
|
+
},
|
|
23650
|
+
moved: remoteMoves.moved,
|
|
23651
|
+
moveSkipped: remoteMoves.skipped
|
|
23411
23652
|
};
|
|
23412
23653
|
};
|
|
23413
23654
|
const excludes = (path) => this.vaultFs.excludesPath?.(path) ?? false;
|
|
@@ -23438,7 +23679,7 @@ var SyncOrchestrator = class {
|
|
|
23438
23679
|
}) : [];
|
|
23439
23680
|
const workItems = [...filtered, ...restoreChanges, ...rerenderChanges];
|
|
23440
23681
|
if (options?.dryRun) {
|
|
23441
|
-
|
|
23682
|
+
const plan = await this.pullPlanner.planPull(
|
|
23442
23683
|
filtered,
|
|
23443
23684
|
restoreChanges,
|
|
23444
23685
|
restoreIds,
|
|
@@ -23448,6 +23689,7 @@ var SyncOrchestrator = class {
|
|
|
23448
23689
|
scan,
|
|
23449
23690
|
ledger
|
|
23450
23691
|
);
|
|
23692
|
+
return { ...plan, moved: remoteMoves.moved, moveSkipped: remoteMoves.skipped };
|
|
23451
23693
|
}
|
|
23452
23694
|
let pullTotal = workItems.length;
|
|
23453
23695
|
let pullCompleted = 0;
|