fix: phase 8 refactoring

This commit is contained in:
2026-02-16 06:57:35 +01:00
parent 6ec25d2705
commit e7c395e1bd
6 changed files with 99 additions and 42 deletions

View File

@@ -5,6 +5,11 @@ import { app } from 'electron';
import { eq } from 'drizzle-orm';
import { getDatabase } from '../database';
import { posts, projects } from '../database/schema';
import {
normalizeTaxonomyTerm,
normalizeNonEmptyTaxonomyTerm,
collectNormalizedTermsFromJsonValues,
} from './taxonomyUtils';
/**
* Project metadata stored in meta/project.json
@@ -144,7 +149,7 @@ export class MetaEngine extends EventEmitter {
* Note: Tag persistence is handled by TagEngine.
*/
async addTag(tag: string): Promise<void> {
const normalizedTag = tag.trim().toLowerCase();
const normalizedTag = normalizeTaxonomyTerm(tag);
if (normalizedTag && !this.tags.has(normalizedTag)) {
this.tags.add(normalizedTag);
this.emit('tagsChanged', await this.getTags());
@@ -156,7 +161,7 @@ export class MetaEngine extends EventEmitter {
* Note: Tag persistence is handled by TagEngine.
*/
async removeTag(tag: string): Promise<void> {
const normalizedTag = tag.trim().toLowerCase();
const normalizedTag = normalizeTaxonomyTerm(tag);
if (this.tags.delete(normalizedTag)) {
this.emit('tagsChanged', await this.getTags());
}
@@ -166,7 +171,7 @@ export class MetaEngine extends EventEmitter {
* Add a new category to the available categories list.
*/
async addCategory(category: string): Promise<void> {
const normalizedCategory = category.trim().toLowerCase();
const normalizedCategory = normalizeTaxonomyTerm(category);
if (normalizedCategory && !this.categories.has(normalizedCategory)) {
this.categories.add(normalizedCategory);
this.emit('categoriesChanged', await this.getCategories());
@@ -178,7 +183,7 @@ export class MetaEngine extends EventEmitter {
* Remove a category from the available categories list.
*/
async removeCategory(category: string): Promise<void> {
const normalizedCategory = category.trim().toLowerCase();
const normalizedCategory = normalizeTaxonomyTerm(category);
if (this.categories.delete(normalizedCategory)) {
this.emit('categoriesChanged', await this.getCategories());
await this.saveCategories();
@@ -244,7 +249,10 @@ export class MetaEngine extends EventEmitter {
const parsed = JSON.parse(content) as string[];
this.categories.clear();
for (const cat of parsed) {
this.categories.add(cat.trim().toLowerCase());
const normalizedCategory = normalizeNonEmptyTaxonomyTerm(cat);
if (normalizedCategory) {
this.categories.add(normalizedCategory);
}
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
@@ -266,21 +274,7 @@ export class MetaEngine extends EventEmitter {
.where(eq(posts.projectId, this.currentProjectId))
.all();
const allTags = new Set<string>();
for (const row of dbPosts) {
if (row.tags) {
try {
const parsed: string[] = JSON.parse(row.tags);
for (const tag of parsed) {
allTags.add(tag.trim().toLowerCase());
}
} catch {
// Invalid JSON, skip
}
}
}
return Array.from(allTags).sort();
return collectNormalizedTermsFromJsonValues(dbPosts.map((row) => row.tags));
}
/**
@@ -294,21 +288,7 @@ export class MetaEngine extends EventEmitter {
.where(eq(posts.projectId, this.currentProjectId))
.all();
const allCategories = new Set<string>();
for (const row of dbPosts) {
if (row.categories) {
try {
const parsed: string[] = JSON.parse(row.categories);
for (const cat of parsed) {
allCategories.add(cat.trim().toLowerCase());
}
} catch {
// Invalid JSON, skip
}
}
}
return Array.from(allCategories).sort();
return collectNormalizedTermsFromJsonValues(dbPosts.map((row) => row.categories));
}
/**

View File

@@ -8,6 +8,7 @@ import { getDatabase } from '../database';
import { tags, posts } from '../database/schema';
import { taskManager } from './TaskManager';
import { getPostEngine } from './PostEngine';
import { normalizeTaxonomyTerm, normalizeNonEmptyTaxonomyTerm } from './taxonomyUtils';
/**
* Tag data stored in the database
@@ -325,7 +326,7 @@ export class TagEngine extends EventEmitter {
async createTag(input: CreateTagInput): Promise<TagData> {
const db = this.getDb();
const name = input.name.trim().toLowerCase();
const name = normalizeTaxonomyTerm(input.name);
if (!name) {
throw new Error('Tag name is required');
}
@@ -574,7 +575,7 @@ export class TagEngine extends EventEmitter {
async renameTag(id: string, newName: string): Promise<RenameTagResult> {
const db = this.getDb();
newName = newName.trim().toLowerCase();
newName = normalizeTaxonomyTerm(newName);
if (!newName) {
throw new Error('New name is required');
}
@@ -678,7 +679,7 @@ export class TagEngine extends EventEmitter {
*/
async getTagByName(name: string): Promise<TagData | null> {
const db = this.getDb();
const normalizedName = name.trim().toLowerCase();
const normalizedName = normalizeTaxonomyTerm(name);
const rows = await db
.select()
@@ -763,8 +764,9 @@ export class TagEngine extends EventEmitter {
for (const row of postRows) {
const postTags: string[] = JSON.parse(row.tags || '[]');
for (const tag of postTags) {
if (tag.trim()) {
discoveredTags.add(tag.trim().toLowerCase());
const normalizedTag = normalizeNonEmptyTaxonomyTerm(tag);
if (normalizedTag) {
discoveredTags.add(normalizedTag);
}
}
}
@@ -861,7 +863,7 @@ export class TagEngine extends EventEmitter {
for (const tag of rawTags) {
// Support both portable format { name, color? } and legacy format with id
const name = (tag.name || '').trim().toLowerCase();
const name = normalizeTaxonomyTerm(tag.name || '');
if (!name) continue;
const color = tag.color || null;

View File

@@ -0,0 +1,42 @@
export function normalizeTaxonomyTerm(term: string): string {
return term.trim().toLowerCase();
}
export function normalizeNonEmptyTaxonomyTerm(term: string): string | null {
const normalized = normalizeTaxonomyTerm(term);
return normalized ? normalized : null;
}
export function collectNormalizedTermsFromJsonValues(
values: Array<string | null | undefined>
): string[] {
const terms = new Set<string>();
for (const value of values) {
if (!value) {
continue;
}
try {
const parsed = JSON.parse(value) as unknown;
if (!Array.isArray(parsed)) {
continue;
}
for (const entry of parsed) {
if (typeof entry !== 'string') {
continue;
}
const normalized = normalizeNonEmptyTaxonomyTerm(entry);
if (normalized) {
terms.add(normalized);
}
}
} catch {
// Invalid JSON: skip
}
}
return Array.from(terms).sort();
}