415 lines
11 KiB
JavaScript
415 lines
11 KiB
JavaScript
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 += `\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 += ` `;
|
||
}
|
||
if (item.language) {
|
||
content += ` `;
|
||
}
|
||
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
|
||
};
|