fix: importer bugs and editor bugs

This commit is contained in:
2026-02-14 21:24:01 +01:00
parent b036cf3c46
commit 4b31d9d421
9 changed files with 281 additions and 24 deletions

View File

@@ -637,6 +637,10 @@ export class ImportExecutionEngine extends EventEmitter {
// Unescape double-bracket macros that TurndownService escaped
// \[\[ becomes [[ and \]\] becomes ]]
markdown = markdown.replace(/\\\[\\\[/g, '[[').replace(/\\\]\\\]/g, ']]');
// Remove backslash escapes inside [[macro]] blocks (e.g. photo\_archive → photo_archive)
markdown = markdown.replace(/\[\[([^\]]*?)\]\]/g, (_match, inner: string) => {
return '[[' + inner.replace(/\\(.)/g, '$1') + ']]';
});
return markdown;
}

View File

@@ -839,6 +839,18 @@ export class MediaEngine extends EventEmitter {
return path.join(this.getMediaDir(), id);
}
/**
* Get the relative path for a media item (e.g. media/2025/01/uuid.jpg).
* This is the path format used in markdown content for image references.
*/
async getRelativePath(id: string): Promise<string | null> {
const db = getDatabase().getLocal();
const dbMedia = await db.select().from(media).where(eq(media.id, id)).get();
if (!dbMedia?.filePath) return null;
const dataDir = this.getDataDir();
return path.relative(dataDir, dbMedia.filePath);
}
async rebuildDatabaseFromFiles(): Promise<void> {
const mediaBaseDir = this.getMediaBaseDir();
console.log(`[MediaEngine] rebuildDatabaseFromFiles: scanning mediaBaseDir=${mediaBaseDir}`);

View File

@@ -4,7 +4,7 @@ import * as fs from 'fs/promises';
import * as path from 'path';
import * as crypto from 'crypto';
import matter from 'gray-matter';
import { eq, and, desc, gte, lte, like, inArray } from 'drizzle-orm';
import { eq, and, desc, gte, lte, like, inArray, ne } from 'drizzle-orm';
import { app } from 'electron';
import { getDatabase } from '../database';
import { posts, Post, NewPost, postLinks } from '../database/schema';
@@ -532,19 +532,74 @@ export class PostEngine extends EventEmitter {
.all();
const total = countResult.length;
// Drafts must ALWAYS be included regardless of pagination.
// On the first page (offset=0), fetch all drafts and fill remaining slots with non-drafts.
// On subsequent pages, only paginate non-draft posts (drafts were already returned).
if (offset === 0) {
// Fetch ALL drafts (typically few)
const draftPosts = await db
.select()
.from(posts)
.where(and(
eq(posts.projectId, this.currentProjectId),
eq(posts.status, 'draft')
))
.orderBy(desc(posts.createdAt))
.all();
// Fill remaining slots with non-draft posts
const remainingSlots = Math.max(0, limit - draftPosts.length);
const nonDraftPosts = remainingSlots > 0 ? await db
.select()
.from(posts)
.where(and(
eq(posts.projectId, this.currentProjectId),
ne(posts.status, 'draft')
))
.orderBy(desc(posts.createdAt))
.limit(remainingSlots)
.all() : [];
const allDbPosts = [...draftPosts, ...nonDraftPosts];
const items: PostData[] = allDbPosts.map(dbPost =>
this.dbRowToPostData(dbPost, dbPost.content || '')
);
return {
items,
hasMore: allDbPosts.length < total,
total,
};
}
// Subsequent pages: only paginate non-draft posts
// Count drafts to calculate correct offset into non-draft posts
const draftCount = await db
.select({ count: posts.id })
.from(posts)
.where(and(
eq(posts.projectId, this.currentProjectId),
eq(posts.status, 'draft')
))
.all();
const numDrafts = draftCount.length;
// Adjust offset: the first page returned numDrafts + (limit - numDrafts) non-draft posts
// So for page 2+, offset into non-draft posts = offset - numDrafts
const nonDraftOffset = offset - numDrafts;
const dbPosts = await db
.select()
.from(posts)
.where(eq(posts.projectId, this.currentProjectId))
.where(and(
eq(posts.projectId, this.currentProjectId),
ne(posts.status, 'draft')
))
.orderBy(desc(posts.createdAt))
.limit(limit)
.offset(offset)
.offset(nonDraftOffset)
.all();
// For listing, we don't need to load content from filesystem.
// Use DB content for drafts, empty string for published posts.
// This avoids expensive filesystem reads for each post.
const items: PostData[] = dbPosts.map(dbPost =>
const items: PostData[] = dbPosts.map(dbPost =>
this.dbRowToPostData(dbPost, dbPost.content || '')
);

View File

@@ -336,8 +336,11 @@ export function registerIpcHandlers(): void {
});
safeHandle('media:getUrl', async (_, id: string) => {
// Returns the bds-media:// protocol URL for a media item
return `bds-media://${id}`;
// Returns the relative path for a media item (e.g. media/2025/01/uuid.jpg)
// This is the format used in markdown content for image references
const engine = getMediaEngine();
const relativePath = await engine.getRelativePath(id);
return relativePath ?? `media/${id}`;
});
safeHandle('media:getFilePath', async (_, id: string) => {