- Add type guard for recursive parameter in notifications.ts to ensure only boolean values are stringified (prevents [object Object] output) - Remove unused error variables from catch blocks across 5 files: - vault-tools.ts (5 instances) - frontmatter-utils.ts (3 instances) - search-utils.ts (2 instances) - waypoint-utils.ts (1 instance)
192 lines
5.0 KiB
TypeScript
192 lines
5.0 KiB
TypeScript
import { TFile } from 'obsidian';
|
|
import { IVaultAdapter } from '../adapters/interfaces';
|
|
|
|
/**
|
|
* Waypoint block information
|
|
*/
|
|
export interface WaypointBlock {
|
|
hasWaypoint: boolean;
|
|
waypointRange?: { start: number; end: number };
|
|
links?: string[];
|
|
rawContent?: string;
|
|
}
|
|
|
|
/**
|
|
* Folder note detection result
|
|
*/
|
|
export interface FolderNoteInfo {
|
|
isFolderNote: boolean;
|
|
reason: 'basename_match' | 'waypoint_marker' | 'both' | 'none';
|
|
folderPath?: string;
|
|
}
|
|
|
|
/**
|
|
* Utilities for working with Waypoint plugin markers
|
|
*/
|
|
export class WaypointUtils {
|
|
private static readonly WAYPOINT_START = /%% Begin Waypoint %%/;
|
|
private static readonly WAYPOINT_END = /%% End Waypoint %%/;
|
|
private static readonly LINK_PATTERN = /\[\[([^\]]+)\]\]/g;
|
|
|
|
/**
|
|
* Extract waypoint block from file content
|
|
*/
|
|
static extractWaypointBlock(content: string): WaypointBlock {
|
|
const lines = content.split('\n');
|
|
let inWaypoint = false;
|
|
let waypointStart = -1;
|
|
let waypointContent: string[] = [];
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
if (this.WAYPOINT_START.test(line)) {
|
|
inWaypoint = true;
|
|
waypointStart = i + 1; // 1-indexed, line after marker
|
|
waypointContent = [];
|
|
} else if (this.WAYPOINT_END.test(line) && inWaypoint) {
|
|
// Extract links from waypoint content
|
|
const contentStr = waypointContent.join('\n');
|
|
const links: string[] = [];
|
|
let linkMatch: RegExpExecArray | null;
|
|
|
|
// Reset lastIndex for global regex
|
|
this.LINK_PATTERN.lastIndex = 0;
|
|
|
|
while ((linkMatch = this.LINK_PATTERN.exec(contentStr)) !== null) {
|
|
links.push(linkMatch[1]);
|
|
}
|
|
|
|
return {
|
|
hasWaypoint: true,
|
|
waypointRange: {
|
|
start: waypointStart,
|
|
end: i + 1 // 1-indexed, line of end marker
|
|
},
|
|
links,
|
|
rawContent: contentStr
|
|
};
|
|
} else if (inWaypoint) {
|
|
waypointContent.push(line);
|
|
}
|
|
}
|
|
|
|
// No waypoint found or unclosed waypoint
|
|
return { hasWaypoint: false };
|
|
}
|
|
|
|
/**
|
|
* Check if content contains a waypoint block
|
|
*/
|
|
static hasWaypointMarker(content: string): boolean {
|
|
return this.WAYPOINT_START.test(content) && this.WAYPOINT_END.test(content);
|
|
}
|
|
|
|
/**
|
|
* Check if a file is a folder note
|
|
* A folder note is a note that:
|
|
* 1. Has the same basename as its parent folder, OR
|
|
* 2. Contains waypoint markers
|
|
*/
|
|
static async isFolderNote(vault: IVaultAdapter, file: TFile): Promise<FolderNoteInfo> {
|
|
const basename = file.basename;
|
|
const parentFolder = file.parent;
|
|
|
|
// Check basename match
|
|
const basenameMatch = parentFolder && parentFolder.name === basename;
|
|
|
|
// Check for waypoint markers
|
|
let hasWaypoint = false;
|
|
try {
|
|
const content = await vault.read(file);
|
|
hasWaypoint = this.hasWaypointMarker(content);
|
|
} catch {
|
|
// If we can't read the file, we can't check for waypoints
|
|
}
|
|
|
|
// Determine result
|
|
let reason: 'basename_match' | 'waypoint_marker' | 'both' | 'none';
|
|
if (basenameMatch && hasWaypoint) {
|
|
reason = 'both';
|
|
} else if (basenameMatch) {
|
|
reason = 'basename_match';
|
|
} else if (hasWaypoint) {
|
|
reason = 'waypoint_marker';
|
|
} else {
|
|
reason = 'none';
|
|
}
|
|
|
|
return {
|
|
isFolderNote: basenameMatch || hasWaypoint,
|
|
reason,
|
|
folderPath: parentFolder?.path
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if an edit would affect a waypoint block
|
|
* Returns true if the edit should be blocked
|
|
*/
|
|
static wouldAffectWaypoint(
|
|
content: string,
|
|
newContent: string
|
|
): { affected: boolean; waypointRange?: { start: number; end: number } } {
|
|
const waypointBlock = this.extractWaypointBlock(content);
|
|
|
|
if (!waypointBlock.hasWaypoint) {
|
|
return { affected: false };
|
|
}
|
|
|
|
// Check if the waypoint block still exists in the new content
|
|
const newWaypointBlock = this.extractWaypointBlock(newContent);
|
|
|
|
if (!newWaypointBlock.hasWaypoint) {
|
|
// Waypoint was removed
|
|
return {
|
|
affected: true,
|
|
waypointRange: waypointBlock.waypointRange
|
|
};
|
|
}
|
|
|
|
// Check if waypoint content changed
|
|
if (waypointBlock.rawContent !== newWaypointBlock.rawContent) {
|
|
return {
|
|
affected: true,
|
|
waypointRange: waypointBlock.waypointRange
|
|
};
|
|
}
|
|
|
|
// Check if waypoint range changed (lines were added/removed before it)
|
|
if (
|
|
waypointBlock.waypointRange!.start !== newWaypointBlock.waypointRange!.start ||
|
|
waypointBlock.waypointRange!.end !== newWaypointBlock.waypointRange!.end
|
|
) {
|
|
// This is acceptable - waypoint content is the same, just moved
|
|
return { affected: false };
|
|
}
|
|
|
|
return { affected: false };
|
|
}
|
|
|
|
/**
|
|
* Get the parent folder path for a file path
|
|
*/
|
|
static getParentFolderPath(filePath: string): string | null {
|
|
const lastSlash = filePath.lastIndexOf('/');
|
|
if (lastSlash === -1) {
|
|
return null; // File is in root
|
|
}
|
|
return filePath.substring(0, lastSlash);
|
|
}
|
|
|
|
/**
|
|
* Get the basename without extension
|
|
*/
|
|
static getBasename(filePath: string): string {
|
|
const lastSlash = filePath.lastIndexOf('/');
|
|
const filename = lastSlash === -1 ? filePath : filePath.substring(lastSlash + 1);
|
|
const lastDot = filename.lastIndexOf('.');
|
|
return lastDot === -1 ? filename : filename.substring(0, lastDot);
|
|
}
|
|
}
|