mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 03:01:24 +02:00
- Rename entire quote project to zitare (German name) - Add global search page with quote and author search - Add search to navigation with Cmd/Ctrl+K shortcut - Add missing icons to PillNavigation (heart, list, compass) - Update all package names from @quote/* to @zitare/* - Update env variables from QUOTE_* to ZITARE_* - Update CLAUDE.md documentation - Fix layout with flex container structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
154 lines
4.2 KiB
TypeScript
154 lines
4.2 KiB
TypeScript
/**
|
|
* Shared Utility Functions
|
|
*
|
|
* Generic utilities that work with any content type (quotes, proverbs, poems, etc.)
|
|
*/
|
|
|
|
import type { ContentItem, ContentAuthor } from '../types';
|
|
|
|
// ============================================================================
|
|
// AUTHOR/CREATOR UTILITIES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Format author/creator name
|
|
*/
|
|
export function formatAuthorName(author: ContentAuthor): string {
|
|
return author.name;
|
|
}
|
|
|
|
/**
|
|
* Get author/creator lifespan string
|
|
*/
|
|
export function getAuthorLifespan(author: ContentAuthor): string {
|
|
if (!author.lifespan) return '';
|
|
const { birth, death } = author.lifespan;
|
|
return death ? `${birth} - ${death}` : `${birth} - Present`;
|
|
}
|
|
|
|
// ============================================================================
|
|
// CONTENT FILTERING - Generic functions that work with any ContentItem
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Filter content items by category
|
|
*/
|
|
export function filterContentByCategory<T extends ContentItem>(items: T[], category: string): T[] {
|
|
return items.filter((item) => item.category === category || item.categories?.includes(category));
|
|
}
|
|
|
|
/**
|
|
* Filter content items by tag
|
|
*/
|
|
export function filterContentByTag<T extends ContentItem>(items: T[], tag: string): T[] {
|
|
return items.filter((item) => item.tags?.includes(tag));
|
|
}
|
|
|
|
/**
|
|
* Filter content items by author
|
|
*/
|
|
export function filterContentByAuthor<T extends ContentItem>(items: T[], authorId: string): T[] {
|
|
return items.filter((item) => item.authorId === authorId);
|
|
}
|
|
|
|
/**
|
|
* Get random content item from array
|
|
*/
|
|
export function getRandomContent<T extends ContentItem>(items: T[]): T | undefined {
|
|
if (items.length === 0) return undefined;
|
|
return items[Math.floor(Math.random() * items.length)];
|
|
}
|
|
|
|
/**
|
|
* Search content items by text
|
|
*/
|
|
export function searchContent<T extends ContentItem>(items: T[], searchTerm: string): T[] {
|
|
const term = searchTerm.toLowerCase();
|
|
return items.filter(
|
|
(item) =>
|
|
item.text.toLowerCase().includes(term) ||
|
|
item.tags?.some((tag) => tag.toLowerCase().includes(term))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get unique categories from content items
|
|
*/
|
|
export function getUniqueCategories<T extends ContentItem>(items: T[]): string[] {
|
|
const categories = new Set<string>();
|
|
items.forEach((item) => {
|
|
if (item.category) categories.add(item.category);
|
|
item.categories?.forEach((cat) => categories.add(cat));
|
|
});
|
|
return Array.from(categories).sort();
|
|
}
|
|
|
|
/**
|
|
* Get unique tags from content items
|
|
*/
|
|
export function getUniqueTags<T extends ContentItem>(items: T[]): string[] {
|
|
const tags = new Set<string>();
|
|
items.forEach((item) => {
|
|
item.tags?.forEach((tag) => tags.add(tag));
|
|
});
|
|
return Array.from(tags).sort();
|
|
}
|
|
|
|
/**
|
|
* Group content items by category
|
|
*/
|
|
export function groupContentByCategory<T extends ContentItem>(items: T[]): Record<string, T[]> {
|
|
const grouped: Record<string, T[]> = {};
|
|
|
|
items.forEach((item) => {
|
|
const categories = item.categories || (item.category ? [item.category] : ['uncategorized']);
|
|
categories.forEach((category) => {
|
|
if (!grouped[category]) {
|
|
grouped[category] = [];
|
|
}
|
|
grouped[category].push(item);
|
|
});
|
|
});
|
|
|
|
return grouped;
|
|
}
|
|
|
|
/**
|
|
* Get featured content items
|
|
*/
|
|
export function getFeaturedContent<T extends ContentItem>(items: T[]): T[] {
|
|
return items.filter((item) => item.featured === true);
|
|
}
|
|
|
|
/**
|
|
* Get favorite content items
|
|
*/
|
|
export function getFavoriteContent<T extends ContentItem>(items: T[]): T[] {
|
|
return items.filter((item) => item.isFavorite === true);
|
|
}
|
|
|
|
// ============================================================================
|
|
// BACKWARD COMPATIBILITY - Quote-specific aliases
|
|
// ============================================================================
|
|
|
|
import type { Quote } from '../types';
|
|
|
|
/**
|
|
* @deprecated Use filterContentByCategory instead
|
|
*/
|
|
export const filterQuotesByCategory = filterContentByCategory<Quote>;
|
|
|
|
/**
|
|
* @deprecated Use filterContentByTag instead
|
|
*/
|
|
export const filterQuotesByTag = filterContentByTag<Quote>;
|
|
|
|
/**
|
|
* @deprecated Use getRandomContent instead
|
|
*/
|
|
export const getRandomQuote = getRandomContent<Quote>;
|
|
|
|
/**
|
|
* @deprecated Use searchContent instead
|
|
*/
|
|
export const searchQuotes = searchContent<Quote>;
|