Files
awesome/lib/custom-lists.js
valknarness 700c73bcbf a new start
2025-10-25 15:52:06 +02:00

415 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const inquirer = require('inquirer');
const chalk = require('chalk');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { purpleGold, goldPink, sectionHeader } = require('./banner');
const { getDb } = require('./database');
// Manage custom lists
async function manage() {
console.clear();
sectionHeader('MY CUSTOM LISTS', '📝');
const db = getDb();
const lists = db.prepare('SELECT * FROM custom_lists ORDER BY updated_at DESC').all();
if (lists.length === 0) {
console.log(chalk.yellow(' No custom lists yet. Create your first awesome list!\n'));
} else {
console.log(chalk.hex('#FFD700')(` ${lists.length} custom lists\n`));
lists.forEach((list, idx) => {
const items = db.prepare('SELECT COUNT(*) as count FROM custom_list_items WHERE custom_list_id = ?').get(list.id);
console.log(` ${chalk.gray((idx + 1) + '.')} ${list.icon} ${chalk.hex('#FF69B4')(list.title)} ${chalk.gray(`(${items.count} items)`)}`);
if (list.description) {
console.log(` ${chalk.gray(list.description)}`);
}
});
console.log();
}
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
{ name: chalk.hex('#DA22FF')(' Create new list'), value: 'create' },
...(lists.length > 0 ? [
{ name: chalk.hex('#FF69B4')('📋 View/Edit list'), value: 'view' },
{ name: chalk.hex('#FFD700')('💾 Export list'), value: 'export' }
] : []),
{ name: chalk.gray('← Back'), value: 'back' }
]
}
]);
switch (action) {
case 'create':
await createList();
await manage();
break;
case 'view':
const { selectedList } = await inquirer.prompt([
{
type: 'list',
name: 'selectedList',
message: 'Select a list:',
choices: lists.map(list => ({
name: `${list.icon} ${list.title}`,
value: list
}))
}
]);
await viewList(selectedList);
await manage();
break;
case 'export':
const { listToExport } = await inquirer.prompt([
{
type: 'list',
name: 'listToExport',
message: 'Select a list to export:',
choices: lists.map(list => ({
name: `${list.icon} ${list.title}`,
value: list
}))
}
]);
await exportList(listToExport);
await manage();
break;
case 'back':
break;
}
}
// Create new custom list
async function createList() {
const { title, description, author, icon, badgeStyle, badgeColor } = await inquirer.prompt([
{
type: 'input',
name: 'title',
message: 'List title:',
validate: input => input.trim() ? true : 'Title is required'
},
{
type: 'input',
name: 'description',
message: 'Description:',
default: ''
},
{
type: 'input',
name: 'author',
message: 'Author name:',
default: os.userInfo().username
},
{
type: 'input',
name: 'icon',
message: 'Icon (emoji):',
default: '📚'
},
{
type: 'list',
name: 'badgeStyle',
message: 'Badge style:',
choices: [
{ name: 'Flat', value: 'flat' },
{ name: 'Flat Square', value: 'flat-square' },
{ name: 'Plastic', value: 'plastic' },
{ name: 'For the Badge', value: 'for-the-badge' }
],
default: 'flat'
},
{
type: 'list',
name: 'badgeColor',
message: 'Badge color:',
choices: [
{ name: 'Purple', value: 'blueviolet' },
{ name: 'Pink', value: 'ff69b4' },
{ name: 'Gold', value: 'FFD700' },
{ name: 'Blue', value: 'informational' },
{ name: 'Green', value: 'success' }
],
default: 'blueviolet'
}
]);
const db = getDb();
const stmt = db.prepare(`
INSERT INTO custom_lists (title, description, author, icon, badge_style, badge_color)
VALUES (?, ?, ?, ?, ?, ?)
`);
stmt.run(title, description, author, icon, badgeStyle, badgeColor);
console.log(chalk.green('\n ✓ Custom list created!\n'));
await new Promise(resolve => setTimeout(resolve, 1000));
}
// View/edit custom list
async function viewList(list) {
const db = getDb();
const items = db.prepare(`
SELECT cli.*, r.name, r.url, r.description, r.stars
FROM custom_list_items cli
JOIN repositories r ON r.id = cli.repository_id
WHERE cli.custom_list_id = ?
ORDER BY cli.order_index, cli.added_at
`).all(list.id);
console.clear();
console.log(purpleGold(`\n${list.icon} ${list.title}\n`));
console.log(chalk.gray('━'.repeat(70)));
if (list.description) console.log(chalk.gray(list.description));
if (list.author) console.log(chalk.gray(`By ${list.author}`));
console.log(chalk.gray('━'.repeat(70)));
console.log(chalk.hex('#FFD700')(`\n ${items.length} items\n`));
if (items.length > 0) {
items.forEach((item, idx) => {
console.log(` ${chalk.gray((idx + 1) + '.')} ${chalk.hex('#FF69B4')(item.name)} ${chalk.gray(`(⭐ ${item.stars || 0})`)}`);
if (item.notes) {
console.log(` ${chalk.gray(item.notes)}`);
}
});
console.log();
}
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
{ name: chalk.hex('#DA22FF')(' Add item'), value: 'add' },
...(items.length > 0 ? [{ name: chalk.hex('#FF69B4')('🗑️ Remove item'), value: 'remove' }] : []),
{ name: chalk.hex('#FFD700')('💾 Export'), value: 'export' },
{ name: chalk.hex('#9733EE')('🗑️ Delete list'), value: 'delete' },
{ name: chalk.gray('← Back'), value: 'back' }
]
}
]);
switch (action) {
case 'add':
await addToList(list.id);
await viewList(list);
break;
case 'remove':
const { itemToRemove } = await inquirer.prompt([
{
type: 'list',
name: 'itemToRemove',
message: 'Select item to remove:',
choices: items.map(item => ({
name: item.name,
value: item
}))
}
]);
const removeStmt = db.prepare('DELETE FROM custom_list_items WHERE id = ?');
removeStmt.run(itemToRemove.id);
console.log(chalk.green('\n ✓ Item removed\n'));
await new Promise(resolve => setTimeout(resolve, 1000));
await viewList(list);
break;
case 'export':
await exportList(list);
await viewList(list);
break;
case 'delete':
const { confirmDelete } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmDelete',
message: 'Delete this list?',
default: false
}
]);
if (confirmDelete) {
const deleteStmt = db.prepare('DELETE FROM custom_lists WHERE id = ?');
deleteStmt.run(list.id);
console.log(chalk.green('\n ✓ List deleted\n'));
await new Promise(resolve => setTimeout(resolve, 1000));
}
break;
case 'back':
break;
}
}
// Add repository to custom list
async function addToList(customListId) {
const dbOps = require('./db-operations');
const bookmarks = dbOps.getBookmarks();
if (bookmarks.length === 0) {
console.log(chalk.yellow('\n No bookmarks to add. Bookmark some repositories first!\n'));
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter...' }]);
return;
}
const { selectedRepo } = await inquirer.prompt([
{
type: 'list',
name: 'selectedRepo',
message: 'Select a repository:',
choices: bookmarks.map(bookmark => ({
name: `${bookmark.name} ${chalk.gray(`(⭐ ${bookmark.stars || 0})`)}`,
value: bookmark
})),
pageSize: 15
}
]);
const { notes } = await inquirer.prompt([
{
type: 'input',
name: 'notes',
message: 'Notes (optional):',
default: ''
}
]);
const db = getDb();
const stmt = db.prepare(`
INSERT OR REPLACE INTO custom_list_items (custom_list_id, repository_id, notes)
VALUES (?, ?, ?)
`);
stmt.run(customListId, selectedRepo.repository_id, notes);
console.log(chalk.green('\n ✓ Added to list!\n'));
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Export custom list
async function exportList(list) {
const { format } = await inquirer.prompt([
{
type: 'list',
name: 'format',
message: 'Export format:',
choices: [
{ name: 'Markdown (.md)', value: 'markdown' },
{ name: 'JSON (.json)', value: 'json' },
{ name: chalk.gray('PDF (not yet implemented)'), value: 'pdf', disabled: true },
{ name: chalk.gray('EPUB (not yet implemented)'), value: 'epub', disabled: true }
]
}
]);
const db = getDb();
const items = db.prepare(`
SELECT cli.*, r.name, r.url, r.description, r.stars, r.language
FROM custom_list_items cli
JOIN repositories r ON r.id = cli.repository_id
WHERE cli.custom_list_id = ?
ORDER BY cli.order_index, cli.added_at
`).all(list.id);
const defaultPath = path.join(os.homedir(), 'Downloads', `${list.title.replace(/[^a-z0-9]/gi, '-').toLowerCase()}.${format === 'markdown' ? 'md' : format}`);
const { outputPath } = await inquirer.prompt([
{
type: 'input',
name: 'outputPath',
message: 'Output path:',
default: defaultPath
}
]);
if (format === 'markdown') {
await exportMarkdown(list, items, outputPath);
} else if (format === 'json') {
await exportJSON(list, items, outputPath);
}
console.log(chalk.green(`\n ✓ Exported to: ${outputPath}\n`));
await new Promise(resolve => setTimeout(resolve, 2000));
}
// Export as Markdown
async function exportMarkdown(list, items, outputPath) {
const badgeUrl = `https://img.shields.io/badge/awesome-list-${list.badge_color}?style=${list.badge_style}`;
let content = `# ${list.icon} ${list.title}\n\n`;
content += `![Awesome](${badgeUrl})\n\n`;
if (list.description) {
content += `> ${list.description}\n\n`;
}
if (list.author) {
content += `**Author:** ${list.author}\n\n`;
}
content += `## Contents\n\n`;
items.forEach(item => {
content += `### [${item.name}](${item.url})`;
if (item.stars) {
content += ` ![Stars](https://img.shields.io/badge/⭐-${item.stars}-yellow)`;
}
if (item.language) {
content += ` ![Language](https://img.shields.io/badge/lang-${item.language}-blue)`;
}
content += `\n\n`;
if (item.description) {
content += `${item.description}\n\n`;
}
if (item.notes) {
content += `*${item.notes}*\n\n`;
}
});
content += `\n---\n\n`;
content += `*Generated with [Awesome CLI](https://github.com/yourusername/awesome) 💜*\n`;
fs.writeFileSync(outputPath, content, 'utf8');
}
// Export as JSON
async function exportJSON(list, items, outputPath) {
const data = {
title: list.title,
description: list.description,
author: list.author,
icon: list.icon,
createdAt: list.created_at,
updatedAt: list.updated_at,
items: items.map(item => ({
name: item.name,
url: item.url,
description: item.description,
stars: item.stars,
language: item.language,
notes: item.notes
}))
};
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
}
module.exports = {
manage,
createList,
viewList,
addToList,
exportList
};