Files
Font_Avasome_Logseg/index.html

2561 lines
86 KiB
HTML

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FontAwesome Pro - Logseq</title>
<!-- FontAwesome local -->
<link rel="stylesheet" href="assets/fontawesome/css/all.min.css">
<!-- CSS dinámico generado (se crea automáticamente) -->
<link rel="stylesheet" href="assets/dynamic-styles.css">
<!-- CSS personalizado para características avanzadas -->
<link rel="stylesheet" href="assets/custom-styles.css">
<style>
/* ===== VARIABLES Y RESET ===== */
:root {
/* Colores del tema Logseq */
--ls-primary-background-color: #ffffff;
--ls-secondary-background-color: #f5f5f5;
--ls-tertiary-background-color: #e8e8e8;
--ls-primary-text-color: #000000;
--ls-secondary-text-color: #666666;
--ls-tertiary-text-color: #999999;
--ls-link-text-color: #0451a5;
--ls-border-color: #dddddd;
--ls-success-color: #10b981;
--ls-warning-color: #f59e0b;
--ls-error-color: #ef4444;
/* Variables específicas del plugin */
--fa-border-radius: 8px;
--fa-transition: 0.2s ease;
--fa-shadow: 0 2px 8px rgba(0,0,0,0.1);
--fa-shadow-hover: 0 4px 16px rgba(0,0,0,0.15);
/* Colores para estilos */
--fa-solid-color: #000000;
--fa-regular-color: #666666;
--fa-light-color: #999999;
--fa-thin-color: #cccccc;
--fa-duotone-color: #8b5cf6;
--fa-sharp-solid-color: #7c3aed;
--fa-sharp-regular-color: #8b5cf6;
--fa-sharp-light-color: #a78bfa;
--fa-sharp-thin-color: #c4b5fd;
--fa-sharp-duotone-color: #7c3aed;
--fa-brands-color: #1877f2;
}
@media (prefers-color-scheme: dark) {
:root {
--ls-primary-background-color: #1a1a1a;
--ls-secondary-background-color: #2d2d2d;
--ls-tertiary-background-color: #3d3d3d;
--ls-primary-text-color: #ffffff;
--ls-secondary-text-color: #aaaaaa;
--ls-tertiary-text-color: #888888;
--ls-link-text-color: #58a6ff;
--ls-border-color: #444444;
--fa-shadow: 0 2px 8px rgba(0,0,0,0.3);
--fa-shadow-hover: 0 4px 16px rgba(0,0,0,0.4);
--fa-solid-color: #ffffff;
--fa-regular-color: #cccccc;
--fa-light-color: #aaaaaa;
--fa-thin-color: #888888;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif;
}
body {
background: var(--ls-primary-background-color);
color: var(--ls-primary-text-color);
padding: 16px;
height: 95vh;
overflow: hidden;
font-size: 14px;
line-height: 1.5;
}
/* ===== LAYOUT PRINCIPAL ===== */
.app-container {
display: flex;
flex-direction: column;
height: 100%;
gap: 16px;
}
/* ===== HEADER ===== */
.app-header {
flex-shrink: 0;
padding-bottom: 12px;
border-bottom: 1px solid var(--ls-border-color);
}
.header-title {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.header-title h1 {
font-size: 1.5em;
font-weight: 600;
color: var(--ls-primary-text-color);
}
.header-title .fa-icon {
font-size: 1.8em;
color: var(--ls-link-text-color);
}
.search-container {
position: relative;
margin-bottom: 12px;
}
.search-input {
width: 100%;
padding: 10px 16px 10px 44px;
border: 1px solid var(--ls-border-color);
border-radius: var(--fa-border-radius);
background: var(--ls-secondary-background-color);
color: var(--ls-primary-text-color);
font-size: 14px;
transition: all var(--fa-transition);
}
.search-input:focus {
outline: none;
border-color: var(--ls-link-text-color);
box-shadow: 0 0 0 3px rgba(5, 81, 165, 0.1);
}
.search-icon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--ls-secondary-text-color);
pointer-events: none;
}
.quick-filters {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.filter-btn {
padding: 6px 12px;
border: 1px solid var(--ls-border-color);
border-radius: 6px;
background: var(--ls-secondary-background-color);
color: var(--ls-primary-text-color);
font-size: 12px;
cursor: pointer;
transition: all var(--fa-transition);
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.filter-btn:hover {
background: var(--ls-tertiary-background-color);
}
.filter-btn.active {
background: var(--ls-link-text-color);
color: white;
border-color: var(--ls-link-text-color);
}
/* ===== CONTENIDO PRINCIPAL ===== */
.app-content {
display: flex;
flex: 1;
min-height: 0;
gap: 16px;
}
/* ===== SIDEBAR DE CATEGORÍAS ===== */
.categories-sidebar {
width: 220px;
flex-shrink: 0;
display: flex;
flex-direction: column;
border-right: 1px solid var(--ls-border-color);
padding-right: 12px;
}
.categories-title {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--ls-secondary-text-color);
margin-bottom: 12px;
padding: 4px 0;
}
.categories-list {
flex: 1;
overflow-y: auto;
list-style: none;
}
.category-item {
padding: 10px 12px;
margin-bottom: 4px;
border-radius: 6px;
cursor: pointer;
transition: all var(--fa-transition);
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
}
.category-item:hover {
background: var(--ls-tertiary-background-color);
}
.category-item.active {
background: var(--ls-link-text-color);
color: white;
}
.category-icon {
width: 20px;
text-align: center;
font-size: 14px;
}
.category-name {
flex: 1;
}
.category-count {
font-size: 11px;
color: var(--ls-tertiary-text-color);
background: var(--ls-secondary-background-color);
padding: 2px 6px;
border-radius: 10px;
min-width: 24px;
text-align: center;
}
.category-item.active .category-count {
background: rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.9);
}
/* ===== PANEL DE ÍCONOS ===== */
.icons-panel {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.styles-filter {
display: flex;
gap: 6px;
margin-bottom: 16px;
flex-wrap: wrap;
padding-bottom: 12px;
border-bottom: 1px solid var(--ls-border-color);
}
.style-btn {
padding: 6px 12px;
border: 1px solid var(--ls-border-color);
border-radius: 6px;
background: var(--ls-secondary-background-color);
color: var(--ls-primary-text-color);
font-size: 12px;
cursor: pointer;
transition: all var(--fa-transition);
display: flex;
align-items: center;
gap: 6px;
}
.style-btn:hover {
transform: translateY(-1px);
}
.style-btn.active {
border-color: var(--ls-link-text-color);
box-shadow: var(--fa-shadow);
}
/* Estilos para diferenciar tipos */
.style-btn[data-style^="sharp"] {
border-left: 3px solid var(--fa-sharp-solid-color);
}
.style-btn[data-style="brands"] {
border-left: 3px solid var(--fa-brands-color);
}
.style-btn[data-style="duotone"],
.style-btn[data-style="sharp-duotone"] {
border-left: 3px solid var(--fa-duotone-color);
}
.icons-grid-container {
flex: 1;
overflow: hidden;
position: relative;
}
.icons-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 12px;
padding: 4px;
overflow-y: auto;
height: 100%;
}
.icon-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 8px;
border-radius: var(--fa-border-radius);
background: var(--ls-secondary-background-color);
cursor: pointer;
transition: all var(--fa-transition);
border: 1px solid transparent;
position: relative;
}
.icon-card:hover {
border-color: var(--ls-link-text-color);
transform: translateY(-2px);
box-shadow: var(--fa-shadow-hover);
}
.icon-card.selected {
border-color: var(--ls-link-text-color);
background: var(--ls-tertiary-background-color);
}
.icon-preview {
font-size: 24px;
margin-bottom: 10px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: var(--ls-primary-text-color);
}
.icon-name {
font-size: 11px;
text-align: center;
color: var(--ls-secondary-text-color);
line-height: 1.3;
word-break: break-word;
max-width: 100%;
}
.icon-styles {
position: absolute;
top: 6px;
right: 6px;
display: flex;
gap: 3px;
}
.style-dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
.style-dot.solid { background: var(--fa-solid-color); }
.style-dot.regular { background: var(--fa-regular-color); }
.style-dot.light { background: var(--fa-light-color); }
.style-dot.thin { background: var(--fa-thin-color); }
.style-dot.duotone {
background: linear-gradient(45deg, var(--fa-duotone-color), #ec4899);
}
.style-dot.sharp-solid { background: var(--fa-sharp-solid-color); }
.style-dot.sharp-regular { background: var(--fa-sharp-regular-color); }
.style-dot.sharp-light { background: var(--fa-sharp-light-color); }
.style-dot.sharp-thin { background: var(--fa-sharp-thin-color); }
.style-dot.sharp-duotone {
background: linear-gradient(45deg, var(--fa-sharp-duotone-color), #ec4899);
}
.style-dot.brands { background: var(--fa-brands-color); }
/* ===== PANEL DE DETALLES CON PERSONALIZACIÓN ===== */
.details-panel {
width: 320px;
flex-shrink: 0;
padding: 16px;
border-left: 1px solid var(--ls-border-color);
display: flex;
flex-direction: column;
gap: 20px;
overflow-y: auto;
}
.details-panel.hidden {
display: none;
}
.selected-icon-display {
text-align: center;
padding: 20px;
border-radius: var(--fa-border-radius);
background: var(--ls-secondary-background-color);
}
.selected-icon-large {
font-size: 48px;
margin-bottom: 12px;
color: var(--ls-link-text-color);
}
.selected-icon-name {
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
}
.selected-icon-id {
font-size: 11px;
color: var(--ls-tertiary-text-color);
font-family: 'Monaco', 'Menlo', monospace;
}
.styles-selector {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.style-option {
padding: 10px;
border: 1px solid var(--ls-border-color);
border-radius: 6px;
text-align: center;
cursor: pointer;
transition: all var(--fa-transition);
}
.style-option:hover {
border-color: var(--ls-link-text-color);
background: var(--ls-tertiary-background-color);
}
.style-option.active {
background: var(--ls-link-text-color);
color: white;
border-color: var(--ls-link-text-color);
}
.style-option-icon {
font-size: 20px;
margin-bottom: 6px;
}
.style-option-name {
font-size: 11px;
}
/* ===== SECCIÓN DE PERSONALIZACIÓN ===== */
.customization-section {
background: var(--ls-secondary-background-color);
padding: 15px;
border-radius: var(--fa-border-radius);
margin: 15px 0;
}
.customization-section h4 {
font-size: 13px;
font-weight: 600;
margin-bottom: 12px;
color: var(--ls-secondary-text-color);
display: flex;
align-items: center;
gap: 8px;
}
.customization-section h4 i {
color: var(--ls-link-text-color);
}
.customization-section label {
display: block;
font-size: 12px;
color: var(--ls-secondary-text-color);
margin-bottom: 8px;
font-weight: 500;
}
/* Selector de tamaño */
.size-selector {
margin-bottom: 15px;
}
.size-options {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.size-btn {
padding: 6px 10px;
border: 1px solid var(--ls-border-color);
border-radius: 4px;
background: var(--ls-tertiary-background-color);
color: var(--ls-primary-text-color);
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all var(--fa-transition);
min-width: 36px;
text-align: center;
}
.size-btn:hover {
border-color: var(--ls-link-text-color);
transform: translateY(-1px);
}
.size-btn.active {
background: var(--ls-link-text-color);
color: white;
border-color: var(--ls-link-text-color);
}
/* Selector de color */
.color-selector {
margin-bottom: 15px;
}
.color-options {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.color-btn {
width: 24px;
height: 24px;
border: 2px solid var(--ls-border-color);
border-radius: 50%;
cursor: pointer;
transition: all var(--fa-transition);
display: flex;
align-items: center;
justify-content: center;
}
.color-btn:hover {
transform: scale(1.1);
border-color: var(--ls-link-text-color);
}
.color-btn.active {
border-color: var(--ls-primary-text-color);
box-shadow: 0 0 0 2px var(--ls-link-text-color);
}
.color-btn i {
color: white;
font-size: 12px;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
/* Input de color personalizado */
.color-input-container {
display: flex;
gap: 8px;
margin-top: 8px;
align-items: center;
}
.color-input {
flex: 1;
padding: 6px 10px;
border: 1px solid var(--ls-border-color);
border-radius: 4px;
background: var(--ls-primary-background-color);
color: var(--ls-primary-text-color);
font-size: 12px;
font-family: 'Monaco', 'Menlo', monospace;
}
.color-input:focus {
outline: none;
border-color: var(--ls-link-text-color);
}
/* Selector de animación */
.animation-selector {
margin: 15px 0;
}
.animation-options {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 8px;
}
.animation-btn {
padding: 8px;
border: 1px solid var(--ls-border-color);
border-radius: 4px;
background: var(--ls-tertiary-background-color);
color: var(--ls-primary-text-color);
font-size: 11px;
cursor: pointer;
transition: all var(--fa-transition);
text-align: center;
}
.animation-btn:hover {
border-color: var(--ls-link-text-color);
}
.animation-btn.active {
background: var(--ls-link-text-color);
color: white;
border-color: var(--ls-link-text-color);
}
.animation-btn i {
display: block;
margin-bottom: 4px;
font-size: 14px;
}
/* Controles de transformación */
.transform-controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 15px;
}
.rotation-options,
.flip-options {
display: flex;
gap: 6px;
margin-top: 8px;
}
/* Vista previa en tiempo real */
.live-preview {
padding: 12px;
background: var(--ls-tertiary-background-color);
border-radius: 6px;
margin-top: 15px;
}
.preview-box {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: var(--ls-secondary-background-color);
border-radius: 6px;
min-height: 80px;
margin-top: 8px;
}
.preview-box i {
transition: all 0.3s ease;
}
/* ===== CÓDIGOS ===== */
.code-snippets {
display: flex;
flex-direction: column;
gap: 10px;
}
.snippet-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.snippet-label {
font-size: 12px;
font-weight: 600;
color: var(--ls-secondary-text-color);
}
.code-snippet {
padding: 10px;
border-radius: 6px;
background: var(--ls-tertiary-background-color);
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
color: var(--ls-primary-text-color);
word-break: break-all;
cursor: pointer;
transition: all var(--fa-transition);
}
.code-snippet:hover {
background: var(--ls-secondary-background-color);
}
/* ===== BOTONES DE ACCIÓN ===== */
.action-buttons {
display: flex;
gap: 8px;
margin-top: auto;
}
.action-btn {
flex: 1;
padding: 10px;
border: none;
border-radius: 6px;
background: var(--ls-link-text-color);
color: white;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all var(--fa-transition);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.action-btn:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.action-btn.secondary {
background: var(--ls-secondary-background-color);
color: var(--ls-primary-text-color);
border: 1px solid var(--ls-border-color);
}
.action-btn.save-btn {
background: var(--ls-success-color);
flex: 0;
padding: 10px;
min-width: 40px;
}
/* ===== ESTADOS Y MENSAJES ===== */
.empty-state {
grid-column: 1 / -1;
text-align: center;
padding: 60px 20px;
color: var(--ls-secondary-text-color);
}
.empty-state-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.loading-state {
grid-column: 1 / -1;
text-align: center;
padding: 60px 20px;
}
.spinner {
display: inline-block;
width: 24px;
height: 24px;
border: 3px solid var(--ls-border-color);
border-radius: 50%;
border-top-color: var(--ls-link-text-color);
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.stats-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
font-size: 12px;
color: var(--ls-secondary-text-color);
border-top: 1px solid var(--ls-border-color);
margin-top: 12px;
}
/* ===== NOTIFICACIONES ===== */
.notification {
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 16px;
background: var(--ls-success-color);
color: white;
border-radius: 6px;
font-size: 13px;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* ===== BADGES ===== */
.favorite-badge,
.recent-badge {
position: absolute;
top: 4px;
left: 4px;
font-size: 10px;
width: 18px;
height: 18px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.favorite-badge {
background: var(--ls-error-color);
color: white;
}
.recent-badge {
background: var(--ls-warning-color);
color: white;
}
/* ===== RESPONSIVE ===== */
@media (max-width: 1024px) {
.details-panel {
position: fixed;
right: 0;
top: 0;
height: 100%;
background: var(--ls-primary-background-color);
z-index: 100;
box-shadow: -2px 0 20px rgba(0,0,0,0.1);
transform: translateX(100%);
transition: transform 0.3s ease;
}
.details-panel.visible {
transform: translateX(0);
}
}
@media (max-width: 768px) {
.app-content {
flex-direction: column;
}
.categories-sidebar {
width: 100%;
border-right: none;
border-bottom: 1px solid var(--ls-border-color);
padding-right: 0;
padding-bottom: 12px;
max-height: 200px;
}
.icons-grid {
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
}
.details-panel {
width: 100%;
position: static;
transform: none;
border-left: none;
border-top: 1px solid var(--ls-border-color);
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- HEADER -->
<div class="app-header">
<div class="header-title">
<i class="fas fa-palette fa-icon"></i>
<h1>FontAwesome 6 Pro</h1>
<span id="versionBadge" style="
font-size: 11px;
background: var(--ls-link-text-color);
color: white;
padding: 2px 6px;
border-radius: 10px;
margin-left: auto;
">v2.0.0</span>
</div>
<div class="search-container">
<i class="fas fa-search search-icon"></i>
<input type="text"
class="search-input"
id="searchInput"
placeholder="Buscar íconos (nombre, categoría, palabra clave)..."
autocomplete="off">
</div>
<div class="quick-filters" id="quickFilters">
<!-- Filtros rápidos se cargarán dinámicamente -->
</div>
</div>
<!-- CONTENIDO PRINCIPAL -->
<div class="app-content">
<!-- SIDEBAR DE CATEGORÍAS -->
<div class="categories-sidebar">
<div class="categories-title">Categorías</div>
<ul class="categories-list" id="categoriesList">
<!-- Categorías se cargarán dinámicamente -->
</ul>
</div>
<!-- PANEL DE ÍCONOS -->
<div class="icons-panel">
<div class="styles-filter" id="stylesFilter">
<!-- Filtros de estilo se cargarán dinámicamente -->
</div>
<div class="icons-grid-container">
<div class="icons-grid" id="iconsGrid">
<div class="loading-state">
<div class="spinner"></div>
<p>Cargando íconos...</p>
</div>
</div>
</div>
<div class="stats-bar">
<span id="iconsCount">0 íconos</span>
<span id="selectedCategory">Todos los íconos</span>
<span id="availableStyles" style="font-size: 11px; color: var(--ls-tertiary-text-color);">
<!-- Estilos detectados se mostrarán aquí -->
</span>
</div>
</div>
<!-- PANEL DE DETALLES -->
<div class="details-panel hidden" id="detailsPanel">
<!-- Contenido dinámico -->
</div>
</div>
</div>
<!-- NOTIFICACIONES -->
<div id="notificationContainer"></div>
<!-- SCRIPTS -->
<script>
// ================= CONFIGURACIÓN =================
const CONFIG = {
dbPath: 'assets/icons.json',
maxRecentIcons: 20,
defaultStyle: 'solid',
defaultSize: 'md',
defaultColor: 'inherit',
defaultAnimation: 'none',
defaultRotate: 0,
defaultFlip: 'none'
};
// ================= ESTADO GLOBAL =================
let state = {
icons: [],
categories: [],
styles: [],
filteredIcons: [],
currentCategory: 'all',
currentStyle: 'all',
currentSearch: '',
selectedIcon: null,
selectedStyle: CONFIG.defaultStyle,
recentIcons: [],
favorites: new Set(),
iconConfigs: {},
currentConfig: {
size: CONFIG.defaultSize,
color: CONFIG.defaultColor,
animation: CONFIG.defaultAnimation,
rotate: CONFIG.defaultRotate,
flip: CONFIG.defaultFlip,
style: CONFIG.defaultStyle
}
};
// ================= INICIALIZACIÓN =================
document.addEventListener('DOMContentLoaded', async () => {
console.log('🚀 Iniciando FontAwesome Pro Plugin v2.0...');
console.log('✨ Características: Todos los estilos, color, tamaño, animaciones');
// Cargar estado guardado
loadSavedState();
// Cargar base de datos
await loadIconDatabase();
// Inicializar UI
initializeUI();
// Configurar event listeners
setupEventListeners();
// Renderizar contenido inicial
renderCategories();
renderStylesFilter();
renderQuickFilters();
filterIcons();
// Mostrar información de estilos disponibles
showAvailableStylesInfo();
console.log('✅ Plugin iniciado correctamente');
});
// ================= CARGA DE DATOS =================
/**
* Cargar base de datos de íconos
*/
async function loadIconDatabase() {
try {
console.log('📥 Cargando base de datos de íconos...');
const response = await fetch(CONFIG.dbPath);
if (!response.ok) {
throw new Error(`Error ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Validar estructura
if (!data.icons || !data.categories || !data.styles) {
throw new Error('Estructura de datos inválida');
}
state.icons = data.icons;
state.categories = data.categories;
state.styles = data.styles;
console.log(`✅ Base de datos cargada:`);
console.log(` • Íconos: ${state.icons.length}`);
console.log(` • Categorías: ${state.categories.length}`);
console.log(` • Estilos disponibles: ${state.styles.length}`);
// Mostrar estilos disponibles
const availableStyles = state.styles.filter(s => s.available).map(s => s.name);
console.log(` • Estilos: ${availableStyles.join(', ')}`);
} catch (error) {
console.error('❌ Error cargando base de datos:', error);
showError('No se pudo cargar la base de datos. Ejecuta "npm run build" primero.');
// Datos de respaldo
state.icons = getFallbackIcons();
state.categories = getFallbackCategories();
state.styles = getFallbackStyles();
}
}
/**
* Cargar estado guardado
*/
function loadSavedState() {
try {
const saved = localStorage.getItem('fontawesome-pro-state');
if (saved) {
const parsed = JSON.parse(saved);
state.recentIcons = parsed.recentIcons || [];
state.favorites = new Set(parsed.favorites || []);
state.iconConfigs = parsed.iconConfigs || {};
}
} catch (error) {
console.warn('⚠️ No se pudo cargar el estado guardado:', error);
}
}
/**
* Guardar estado
*/
function saveState() {
try {
const stateToSave = {
recentIcons: state.recentIcons,
favorites: Array.from(state.favorites),
iconConfigs: state.iconConfigs
};
localStorage.setItem('fontawesome-pro-state', JSON.stringify(stateToSave));
} catch (error) {
console.warn('⚠️ No se pudo guardar el estado:', error);
}
}
// ================= INTERFAZ DE USUARIO =================
/**
* Inicializar UI
*/
function initializeUI() {
// Atajos de teclado
document.addEventListener('keydown', handleKeyboardShortcuts);
// Integración con Logseq
if (typeof logseq !== 'undefined') {
setupLogseqIntegration();
}
}
/**
* Configurar integración con Logseq
*/
function setupLogseqIntegration() {
console.log('🔗 Configurando integración con Logseq...');
// Exponer funciones
window.FontAwesomePlugin = {
insertIcon: insertIconIntoEditor,
showPanel: () => logseq.showMainUI(),
hidePanel: () => logseq.hideMainUI(),
searchIcons: (query) => {
document.getElementById('searchInput').value = query;
state.currentSearch = query;
filterIcons();
},
getAvailableStyles: () => state.styles.filter(s => s.available).map(s => s.name)
};
// Registrar comandos
logseq.App.registerCommandPalette({
key: 'fontawesome-insert-icon',
label: 'Insertar ícono FontAwesome',
keybinding: { binding: 'mod+shift+i' }
}, () => {
logseq.showMainUI();
document.getElementById('searchInput').focus();
});
console.log('✅ Integración con Logseq configurada');
}
/**
* Configurar event listeners
*/
function setupEventListeners() {
// Búsqueda
const searchInput = document.getElementById('searchInput');
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
state.currentSearch = e.target.value.toLowerCase();
filterIcons();
}, 300);
});
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.target.value = '';
state.currentSearch = '';
filterIcons();
}
});
}
/**
* Manejar atajos de teclado
*/
function handleKeyboardShortcuts(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return;
}
switch (e.key) {
case 'Escape':
if (typeof logseq !== 'undefined') {
logseq.hideMainUI();
}
break;
case 'f':
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
document.getElementById('searchInput').focus();
}
break;
case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
if (e.altKey) {
e.preventDefault();
const index = parseInt(e.key) - 1;
const availableStyles = state.styles.filter(s => s.available);
if (availableStyles[index]) {
selectStyle(availableStyles[index].id);
}
}
break;
}
}
/**
* Mostrar información de estilos disponibles
*/
function showAvailableStylesInfo() {
const availableCount = state.styles.filter(s => s.available).length;
const totalCount = state.styles.length;
const element = document.getElementById('availableStyles');
if (element && availableCount > 0) {
element.textContent = `${availableCount}/${totalCount} estilos`;
if (availableCount < totalCount) {
element.title = 'Faltan algunos estilos. Ejecuta "npm run verify" para más información.';
element.style.color = 'var(--ls-warning-color)';
element.style.cursor = 'help';
}
}
}
// ================= RENDERIZADO =================
/**
* Renderizar categorías
*/
function renderCategories() {
const container = document.getElementById('categoriesList');
if (!container) return;
let html = '';
state.categories.forEach(category => {
const isActive = state.currentCategory === category.id;
const count = category.id === 'all'
? state.icons.length
: (category.count || 0);
html += `
<li class="category-item ${isActive ? 'active' : ''}"
data-category="${category.id}">
<div class="category-icon">
<i class="${category.icon}"></i>
</div>
<div class="category-name">${category.name}</div>
<div class="category-count">${count}</div>
</li>
`;
});
container.innerHTML = html;
// Event listeners
container.querySelectorAll('.category-item').forEach(item => {
item.addEventListener('click', () => {
const categoryId = item.dataset.category;
selectCategory(categoryId);
});
});
}
/**
* Renderizar filtros de estilo
*/
function renderStylesFilter() {
const container = document.getElementById('stylesFilter');
if (!container) return;
let html = '';
// Todos los estilos
html += `
<button class="style-btn ${state.currentStyle === 'all' ? 'active' : ''}"
data-style="all"
title="Todos los estilos">
<i class="fas fa-layer-group"></i>
<span>Todos</span>
</button>
`;
// Estilos disponibles
const availableStyles = state.styles.filter(s => s.available);
// Agrupar por tipo
const classicStyles = availableStyles.filter(s =>
!s.id.includes('sharp') && s.id !== 'brands'
);
const sharpStyles = availableStyles.filter(s =>
s.id.includes('sharp')
);
const brandsStyles = availableStyles.filter(s =>
s.id === 'brands'
);
// Estilos clásicos
classicStyles.forEach(style => {
const isActive = state.currentStyle === style.id;
const count = state.icons.filter(icon =>
icon.styles.includes(style.id)
).length;
html += `
<button class="style-btn ${isActive ? 'active' : ''}"
data-style="${style.id}"
title="${style.name} (${count} íconos)">
<i class="${style.prefix} fa-circle"></i>
<span>${style.name}</span>
</button>
`;
});
// Separador para Sharp (si hay)
if (sharpStyles.length > 0) {
html += `<div style="width: 100%; height: 1px; background: var(--ls-border-color); margin: 4px 0;"></div>`;
sharpStyles.forEach(style => {
const isActive = state.currentStyle === style.id;
const count = state.icons.filter(icon =>
icon.styles.includes(style.id)
).length;
html += `
<button class="style-btn ${isActive ? 'active' : ''}"
data-style="${style.id}"
title="${style.name} (${count} íconos)">
<i class="${style.prefix} fa-bolt"></i>
<span>${style.name.replace('Sharp ', '')}</span>
</button>
`;
});
}
// Separador para Brands (si hay)
if (brandsStyles.length > 0) {
html += `<div style="width: 100%; height: 1px; background: var(--ls-border-color); margin: 4px 0;"></div>`;
brandsStyles.forEach(style => {
const isActive = state.currentStyle === style.id;
const count = state.icons.filter(icon =>
icon.styles.includes(style.id)
).length;
html += `
<button class="style-btn ${isActive ? 'active' : ''}"
data-style="${style.id}"
title="${style.name} (${count} íconos)">
<i class="${style.prefix} fa-font-awesome"></i>
<span>${style.name}</span>
</button>
`;
});
}
container.innerHTML = html;
// Event listeners
container.querySelectorAll('.style-btn').forEach(btn => {
btn.addEventListener('click', () => {
const styleId = btn.dataset.style;
selectStyle(styleId);
});
});
}
/**
* Renderizar filtros rápidos
*/
function renderQuickFilters() {
const container = document.getElementById('quickFilters');
if (!container) return;
const filters = [
{ id: 'pro', label: 'Pro Only', icon: 'fas fa-crown' },
{ id: 'free', label: 'Free', icon: 'fas fa-star' },
{ id: 'recent', label: 'Recientes', icon: 'fas fa-history' },
{ id: 'favorites', label: 'Favoritos', icon: 'fas fa-heart' }
];
let html = '';
filters.forEach(filter => {
const count = filter.id === 'favorites'
? state.favorites.size
: filter.id === 'recent'
? state.recentIcons.length
: '';
html += `
<button class="filter-btn" data-filter="${filter.id}">
<i class="${filter.icon}"></i>
${filter.label}
${count ? `<span style="margin-left: 4px;">(${count})</span>` : ''}
</button>
`;
});
container.innerHTML = html;
// Event listeners
container.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
const filterId = btn.dataset.filter;
applyQuickFilter(filterId);
});
});
}
/**
* Renderizar cuadrícula de íconos
*/
function renderIconsGrid() {
const container = document.getElementById('iconsGrid');
if (!container) return;
if (state.filteredIcons.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">
<i class="fas fa-search"></i>
</div>
<h3>No se encontraron íconos</h3>
<p>Intenta con otros términos de búsqueda o cambia los filtros</p>
</div>
`;
return;
}
let html = '';
state.filteredIcons.forEach(icon => {
const isFavorite = state.favorites.has(icon.id);
const isRecent = state.recentIcons.includes(icon.id);
// Estilo por defecto para este ícono
const defaultStyle = icon.styles.includes(state.selectedStyle)
? state.selectedStyle
: (icon.styles.includes('solid') ? 'solid' : icon.styles[0]);
// Puntos de estilo disponibles
const styleDots = icon.styles.map(style => {
const styleInfo = state.styles.find(s => s.id === style);
if (!styleInfo || !styleInfo.available) return '';
return `<span class="style-dot ${style}"></span>`;
}).filter(Boolean).join('');
html += `
<div class="icon-card"
data-icon="${icon.id}"
data-style="${defaultStyle}"
title="${icon.label} (${icon.styles.length} estilos)">
${isFavorite ? '<div class="favorite-badge">♥</div>' : ''}
${isRecent ? '<div class="recent-badge">🕒</div>' : ''}
${styleDots ? `
<div class="icon-styles">
${styleDots}
</div>
` : ''}
<div class="icon-preview">
<i class="${getStyleClass(defaultStyle)} fa-${icon.name}"></i>
</div>
<div class="icon-name">
${icon.label}
</div>
</div>
`;
});
container.innerHTML = html;
// Actualizar estadísticas
updateStats();
// Event listeners
container.querySelectorAll('.icon-card').forEach(card => {
card.addEventListener('click', (e) => {
const iconId = card.dataset.icon;
const style = card.dataset.style;
selectIcon(iconId, style);
});
// Menú contextual
card.addEventListener('contextmenu', (e) => {
e.preventDefault();
const iconId = card.dataset.icon;
showIconContextMenu(iconId, e.clientX, e.clientY);
});
});
}
/**
* Renderizar panel de detalles
*/
function renderDetailsPanel() {
const container = document.getElementById('detailsPanel');
if (!container) return;
if (!state.selectedIcon) {
container.classList.add('hidden');
return;
}
const icon = state.selectedIcon;
const currentStyle = state.selectedStyle;
// Cargar configuración guardada o usar valores por defecto
const savedConfig = state.iconConfigs[icon.id] || {};
state.currentConfig = {
size: savedConfig.size || CONFIG.defaultSize,
color: savedConfig.color || CONFIG.defaultColor,
animation: savedConfig.animation || CONFIG.defaultAnimation,
rotate: savedConfig.rotate || CONFIG.defaultRotate,
flip: savedConfig.flip || CONFIG.defaultFlip,
style: currentStyle
};
// Generar códigos
const iconCode = generateIconCode(icon, state.currentConfig);
const htmlCode = generateHTMLCode(icon, state.currentConfig);
const cssCode = generateCSSCode(icon, state.currentConfig);
// Estilos disponibles para este ícono
const availableStyles = state.styles.filter(style =>
icon.styles.includes(style.id) && style.available
);
// Opciones de tamaño
const sizeOptions = [
{ id: 'xs', label: 'XS' },
{ id: 'sm', label: 'SM' },
{ id: 'md', label: 'MD' },
{ id: 'lg', label: 'LG' },
{ id: 'xl', label: 'XL' },
{ id: '2x', label: '2X' },
{ id: '3x', label: '3X' },
{ id: '4x', label: '4X' }
];
// Opciones de animación
const animationOptions = [
{ id: 'none', label: 'Ninguna', icon: 'fas fa-circle' },
{ id: 'spin', label: 'Spin', icon: 'fas fa-sync-alt' },
{ id: 'pulse', label: 'Pulse', icon: 'fas fa-spinner' },
{ id: 'beat', label: 'Beat', icon: 'fas fa-heartbeat' },
{ id: 'fade', label: 'Fade', icon: 'fas fa-cloud' },
{ id: 'bounce', label: 'Bounce', icon: 'fas fa-basketball-ball' },
{ id: 'flip', label: 'Flip', icon: 'fas fa-sync' },
{ id: 'shake', label: 'Shake', icon: 'fas fa-star' }
];
// Opciones de color predefinidas
const colorOptions = [
{ id: 'inherit', label: 'Color actual', value: 'inherit' },
{ id: 'primary', label: 'Primario', value: 'var(--ls-link-text-color, #0451a5)' },
{ id: 'success', label: 'Éxito', value: '#10b981' },
{ id: 'danger', label: 'Peligro', value: '#ef4444' },
{ id: 'warning', label: 'Advertencia', value: '#f59e0b' },
{ id: 'info', label: 'Info', value: '#3b82f6' },
{ id: 'purple', label: 'Púrpura', value: '#8b5cf6' },
{ id: 'pink', label: 'Rosa', value: '#ec4899' },
{ id: 'gray', label: 'Gris', value: '#6b7280' }
];
// Opciones de rotación
const rotationOptions = [
{ id: '0', label: '0°', degrees: 0, icon: 'fas fa-redo' },
{ id: '90', label: '90°', degrees: 90, icon: 'fas fa-redo' },
{ id: '180', label: '180°', degrees: 180, icon: 'fas fa-redo' },
{ id: '270', label: '270°', degrees: 270, icon: 'fas fa-redo' }
];
// Opciones de flip
const flipOptions = [
{ id: 'none', label: 'Ninguno', icon: 'fas fa-arrows-alt-h' },
{ id: 'horizontal', label: 'Horizontal', icon: 'fas fa-arrows-alt-h' },
{ id: 'vertical', label: 'Vertical', icon: 'fas fa-arrows-alt-v' },
{ id: 'both', label: 'Ambos', icon: 'fas fa-arrows-alt' }
];
// Construir HTML
let html = `
<div class="selected-icon-display">
<div class="selected-icon-large">
<i class="${getStyleClass(currentStyle)} fa-${icon.name} ${getSizeClass(state.currentConfig.size)}"
style="color: ${state.currentConfig.color === 'inherit' ? 'var(--ls-link-text-color)' : state.currentConfig.color};">
</i>
</div>
<div class="selected-icon-name">${icon.label}</div>
<div class="selected-icon-id">${icon.name}</div>
</div>
<!-- Estilos -->
<div class="styles-selector" id="stylesSelector">
${availableStyles.map(style => {
const isActive = style.id === currentStyle;
return `
<div class="style-option ${isActive ? 'active' : ''}"
data-style="${style.id}">
<div class="style-option-icon">
<i class="${style.prefix} fa-${icon.name}"></i>
</div>
<div class="style-option-name">
${style.name}
</div>
</div>
`;
}).join('')}
</div>
<!-- Personalización -->
<div class="customization-section">
<h4><i class="fas fa-sliders-h"></i> Personalización</h4>
<!-- Tamaño -->
<div class="size-selector">
<label>Tamaño:</label>
<div class="size-options">
${sizeOptions.map(opt => `
<button class="size-btn ${state.currentConfig.size === opt.id ? 'active' : ''}"
data-size="${opt.id}"
title="${opt.label}">
${opt.label}
</button>
`).join('')}
</div>
</div>
<!-- Color -->
<div class="color-selector">
<label>Color:</label>
<div class="color-options">
${colorOptions.map(opt => {
const isActive = state.currentConfig.color === opt.value;
const style = opt.value === 'inherit'
? 'background: var(--ls-primary-text-color)'
: `background: ${opt.value}`;
return `
<button class="color-btn ${isActive ? 'active' : ''}"
data-color="${opt.value}"
title="${opt.label}"
style="${style}">
</button>
`;
}).join('')}
<button class="color-btn" id="customColorBtn" title="Color personalizado">
<i class="fas fa-palette"></i>
</button>
<input type="color" id="customColorPicker" style="display: none">
</div>
<div class="color-input-container">
<input type="text"
id="colorInput"
class="color-input"
placeholder="#hex, rgb(), o nombre"
value="${state.currentConfig.color}">
</div>
</div>
<!-- Animación -->
<div class="animation-selector">
<label>Animación:</label>
<div class="animation-options">
${animationOptions.map(opt => {
const isActive = state.currentConfig.animation === opt.id;
const animationClass = opt.id !== 'none' ? `fa-${opt.id}` : '';
return `
<button class="animation-btn ${isActive ? 'active' : ''}"
data-animation="${opt.id}">
<i class="${opt.icon} ${animationClass}"></i>
${opt.label}
</button>
`;
}).join('')}
</div>
</div>
<!-- Transformaciones -->
<div class="transform-controls">
<div>
<label>Rotación:</label>
<div class="rotation-options">
${rotationOptions.map(opt => {
const isActive = state.currentConfig.rotate === opt.degrees;
return `
<button class="size-btn ${isActive ? 'active' : ''}"
data-rotate="${opt.degrees}"
style="transform: rotate(${opt.degrees}deg)"
title="${opt.label}">
<i class="${opt.icon}"></i>
</button>
`;
}).join('')}
</div>
</div>
<div>
<label>Flip:</label>
<div class="flip-options">
${flipOptions.map(opt => {
const isActive = state.currentConfig.flip === opt.id;
return `
<button class="size-btn ${isActive ? 'active' : ''}"
data-flip="${opt.id}"
title="${opt.label}">
<i class="${opt.icon}"></i>
</button>
`;
}).join('')}
</div>
</div>
</div>
<!-- Vista previa -->
<div class="live-preview">
<label>Vista previa:</label>
<div class="preview-box" id="iconPreview">
<i class="${getStyleClass(currentStyle)} fa-${icon.name} ${getSizeClass(state.currentConfig.size)} ${state.currentConfig.animation !== 'none' ? 'fa-' + state.currentConfig.animation : ''}"
style="color: ${state.currentConfig.color === 'inherit' ? 'var(--ls-primary-text-color)' : state.currentConfig.color};
transform: rotate(${state.currentConfig.rotate}deg) ${getFlipTransform(state.currentConfig.flip)};">
</i>
</div>
</div>
</div>
<!-- Códigos -->
<div class="code-snippets">
<div class="snippet-group">
<div class="snippet-label">Para Logseq</div>
<div class="code-snippet" data-code="${iconCode}">
${escapeHTML(iconCode)}
</div>
</div>
<div class="snippet-group">
<div class="snippet-label">HTML</div>
<div class="code-snippet" data-code="${htmlCode}">
${escapeHTML(htmlCode)}
</div>
</div>
<div class="snippet-group">
<div class="snippet-label">CSS</div>
<div class="code-snippet" data-code="${cssCode}">
${escapeHTML(cssCode)}
</div>
</div>
</div>
<!-- Botones de acción -->
<div class="action-buttons">
<button class="action-btn" id="insertBtn">
<i class="fas fa-plus"></i>
Insertar
</button>
<button class="action-btn secondary" id="copyBtn">
<i class="fas fa-copy"></i>
Copiar
</button>
<button class="action-btn save-btn" id="savePresetBtn" title="Guardar configuración">
<i class="fas fa-save"></i>
</button>
</div>
`;
container.innerHTML = html;
container.classList.remove('hidden');
// Configurar eventos
setupDetailsPanelEvents(icon);
}
/**
* Configurar eventos del panel de detalles
*/
function setupDetailsPanelEvents(icon) {
// Estilos
document.querySelectorAll('.style-option').forEach(option => {
option.addEventListener('click', () => {
const styleId = option.dataset.style;
selectStyleForIcon(styleId);
});
});
// Tamaño
document.querySelectorAll('.size-btn[data-size]').forEach(btn => {
btn.addEventListener('click', () => {
const size = btn.dataset.size;
updateIconConfig({ size });
updatePreview(icon);
updateCodeSnippets(icon);
});
});
// Color - botones predefinidos
document.querySelectorAll('.color-btn[data-color]').forEach(btn => {
btn.addEventListener('click', () => {
const color = btn.dataset.color;
updateIconConfig({ color });
updatePreview(icon);
updateCodeSnippets(icon);
});
});
// Color - input personalizado
const colorInput = document.getElementById('colorInput');
const customColorBtn = document.getElementById('customColorBtn');
const customColorPicker = document.getElementById('customColorPicker');
colorInput.addEventListener('input', (e) => {
updateIconConfig({ color: e.target.value });
updatePreview(icon);
updateCodeSnippets(icon);
});
colorInput.addEventListener('change', (e) => {
updateIconConfig({ color: e.target.value });
updatePreview(icon);
updateCodeSnippets(icon);
});
customColorBtn.addEventListener('click', () => {
customColorPicker.click();
});
customColorPicker.addEventListener('input', (e) => {
const color = e.target.value;
colorInput.value = color;
updateIconConfig({ color });
updatePreview(icon);
updateCodeSnippets(icon);
});
// Animación
document.querySelectorAll('.animation-btn').forEach(btn => {
btn.addEventListener('click', () => {
const animation = btn.dataset.animation;
updateIconConfig({ animation });
updatePreview(icon);
updateCodeSnippets(icon);
});
});
// Rotación
document.querySelectorAll('[data-rotate]').forEach(btn => {
btn.addEventListener('click', () => {
const rotate = parseInt(btn.dataset.rotate);
updateIconConfig({ rotate });
updatePreview(icon);
updateCodeSnippets(icon);
});
});
// Flip
document.querySelectorAll('[data-flip]').forEach(btn => {
btn.addEventListener('click', () => {
const flip = btn.dataset.flip;
updateIconConfig({ flip });
updatePreview(icon);
updateCodeSnippets(icon);
});
});
// Códigos
document.querySelectorAll('.code-snippet').forEach(snippet => {
snippet.addEventListener('click', () => {
const code = snippet.dataset.code;
copyToClipboard(code);
showNotification('Código copiado al portapapeles');
});
});
// Botones de acción
document.getElementById('insertBtn').addEventListener('click', () => {
const iconCode = generateIconCode(icon, state.currentConfig);
insertIconIntoEditor(iconCode);
});
document.getElementById('copyBtn').addEventListener('click', () => {
const iconCode = generateIconCode(icon, state.currentConfig);
copyToClipboard(iconCode);
showNotification('Código copiado al portapapeles');
});
document.getElementById('savePresetBtn').addEventListener('click', () => {
saveIconConfig(icon.id, state.currentConfig);
showNotification('Configuración guardada');
});
}
/**
* Actualizar estadísticas
*/
function updateStats() {
document.getElementById('iconsCount').textContent =
`${state.filteredIcons.length} íconos`;
const currentCat = state.categories.find(c => c.id === state.currentCategory);
document.getElementById('selectedCategory').textContent =
currentCat ? currentCat.name : 'Todos los íconos';
}
// ================= FUNCIONALIDADES =================
/**
* Actualizar configuración del ícono
*/
function updateIconConfig(newConfig) {
state.currentConfig = {
...state.currentConfig,
...newConfig
};
// Actualizar botones activos
updateActiveButtons();
}
/**
* Actualizar botones activos
*/
function updateActiveButtons() {
const config = state.currentConfig;
// Tamaño
document.querySelectorAll('.size-btn[data-size]').forEach(btn => {
btn.classList.toggle('active', btn.dataset.size === config.size);
});
// Color
document.querySelectorAll('.color-btn[data-color]').forEach(btn => {
btn.classList.toggle('active', btn.dataset.color === config.color);
});
// Animación
document.querySelectorAll('.animation-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.animation === config.animation);
});
// Rotación
document.querySelectorAll('[data-rotate]').forEach(btn => {
btn.classList.toggle('active', parseInt(btn.dataset.rotate) === config.rotate);
});
// Flip
document.querySelectorAll('[data-flip]').forEach(btn => {
btn.classList.toggle('active', btn.dataset.flip === config.flip);
});
// Estilos
document.querySelectorAll('.style-option').forEach(option => {
option.classList.toggle('active', option.dataset.style === config.style);
});
// Actualizar input de color
const colorInput = document.getElementById('colorInput');
if (colorInput) {
colorInput.value = config.color;
}
}
/**
* Actualizar vista previa
*/
function updatePreview(icon) {
const preview = document.getElementById('iconPreview');
if (!preview || !icon) return;
const styleClass = getStyleClass(state.currentConfig.style);
const sizeClass = getSizeClass(state.currentConfig.size);
const animationClass = state.currentConfig.animation !== 'none' ? 'fa-' + state.currentConfig.animation : '';
const rotateStyle = `rotate(${state.currentConfig.rotate}deg)`;
const flipStyle = getFlipTransform(state.currentConfig.flip);
preview.innerHTML = `
<i class="${styleClass} fa-${icon.name} ${sizeClass} ${animationClass}"
style="color: ${state.currentConfig.color === 'inherit' ? 'var(--ls-primary-text-color)' : state.currentConfig.color};
transform: ${rotateStyle} ${flipStyle};">
</i>
`;
}
/**
* Actualizar snippets de código
*/
function updateCodeSnippets(icon) {
if (!icon) return;
const iconCode = generateIconCode(icon, state.currentConfig);
const htmlCode = generateHTMLCode(icon, state.currentConfig);
const cssCode = generateCSSCode(icon, state.currentConfig);
// Actualizar Logseq
const logseqSnippet = document.querySelector('.code-snippet[data-code]');
if (logseqSnippet) {
logseqSnippet.dataset.code = iconCode;
logseqSnippet.textContent = iconCode;
}
// Actualizar HTML
const htmlSnippets = document.querySelectorAll('.code-snippet');
if (htmlSnippets[1]) {
htmlSnippets[1].dataset.code = htmlCode;
htmlSnippets[1].innerHTML = escapeHTML(htmlCode);
}
// Actualizar CSS
if (htmlSnippets[2]) {
htmlSnippets[2].dataset.code = cssCode;
htmlSnippets[2].innerHTML = escapeHTML(cssCode);
}
}
/**
* Generar código para Logseq
*/
function generateIconCode(icon, config) {
const styleClass = getStyleClass(config.style);
let code = `:${styleClass} fa-${icon.name}:`;
// Agregar clases de tamaño
if (config.size !== 'md') {
code = code.replace(':', `:${getSizeClass(config.size)} `);
}
// Agregar animación
if (config.animation !== 'none') {
code = code.replace('fa-', `fa-${config.animation} fa-`);
}
// Agregar estilos inline
const styles = [];
if (config.color !== 'inherit') {
styles.push(`"color: ${config.color}"`);
}
if (config.rotate !== 0) {
styles.push(`"transform: rotate(${config.rotate}deg)"`);
}
if (config.flip !== 'none') {
const flipValue = config.flip === 'horizontal' ? 'scaleX(-1)' :
config.flip === 'vertical' ? 'scaleY(-1)' :
'scale(-1, -1)';
styles.push(`"transform: ${flipValue}"`);
}
if (styles.length > 0) {
code = code.replace(':', `{:style ${styles.join(' ')}} :`);
}
return code;
}
/**
* Generar código HTML
*/
function generateHTMLCode(icon, config) {
const styleClass = getStyleClass(config.style);
const sizeClass = getSizeClass(config.size);
const animationClass = config.animation !== 'none' ? `fa-${config.animation}` : '';
let html = `<i class="${styleClass} fa-${icon.name} ${sizeClass} ${animationClass}"`;
const styles = [];
if (config.color !== 'inherit') {
styles.push(`color: ${config.color}`);
}
if (config.rotate !== 0) {
styles.push(`transform: rotate(${config.rotate}deg)`);
}
if (config.flip !== 'none') {
const flipValue = config.flip === 'horizontal' ? 'scaleX(-1)' :
config.flip === 'vertical' ? 'scaleY(-1)' :
'scale(-1, -1)';
styles.push(`transform: ${flipValue}`);
}
if (styles.length > 0) {
html += ` style="${styles.join('; ')}"`;
}
html += '></i>';
return html;
}
/**
* Generar código CSS
*/
function generateCSSCode(icon, config) {
const styleClass = getStyleClass(config.style);
const css = [];
css.push(`.${styleClass}.fa-${icon.name} {`);
if (config.color !== 'inherit') {
css.push(` color: ${config.color} !important;`);
}
if (config.size !== 'md') {
const sizes = {
'xs': '0.75em',
'sm': '0.875em',
'lg': '1.33em',
'xl': '1.75em',
'2x': '2em',
'3x': '3em',
'4x': '4em'
};
if (sizes[config.size]) {
css.push(` font-size: ${sizes[config.size]} !important;`);
}
}
if (config.rotate !== 0) {
css.push(` transform: rotate(${config.rotate}deg) !important;`);
}
if (config.flip !== 'none') {
const flipValue = config.flip === 'horizontal' ? 'scaleX(-1)' :
config.flip === 'vertical' ? 'scaleY(-1)' :
'scale(-1, -1)';
css.push(` transform: ${flipValue} !important;`);
}
if (config.animation !== 'none') {
css.push(` animation: fa-${config.animation} 2s infinite;`);
}
css.push('}');
return css.join('\n');
}
/**
* Guardar configuración del ícono
*/
function saveIconConfig(iconId, config) {
state.iconConfigs[iconId] = config;
saveState();
}
// ================= UTILIDADES =================
/**
* Obtener clase CSS para un estilo
*/
function getStyleClass(styleId) {
const style = state.styles.find(s => s.id === styleId);
return style ? style.prefix : 'fas';
}
/**
* Obtener clase CSS para tamaño
*/
function getSizeClass(size) {
const sizeMap = {
'xs': 'fa-xs',
'sm': 'fa-sm',
'md': '',
'lg': 'fa-lg',
'xl': 'fa-xl',
'2x': 'fa-2x',
'3x': 'fa-3x',
'4x': 'fa-4x'
};
return sizeMap[size] || '';
}
/**
* Obtener transformación CSS para flip
*/
function getFlipTransform(flip) {
switch (flip) {
case 'horizontal': return 'scaleX(-1)';
case 'vertical': return 'scaleY(-1)';
case 'both': return 'scale(-1, -1)';
default: return '';
}
}
/**
* Escapar HTML
*/
function escapeHTML(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Copiar al portapapeles
*/
function copyToClipboard(text) {
navigator.clipboard.writeText(text).catch(err => {
console.error('Error copiando:', err);
// Fallback
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
});
}
/**
* Mostrar notificación
*/
function showNotification(message, type = 'success') {
const container = document.getElementById('notificationContainer');
if (!container) return;
const notification = document.createElement('div');
notification.className = 'notification';
notification.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-circle'}"></i>
<span>${message}</span>
`;
container.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateY(20px)';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
}
/**
* Mostrar error
*/
function showError(message) {
console.error('❌ Error:', message);
showNotification(message, 'error');
}
// ================= FILTRADO Y SELECCIÓN =================
/**
* Filtrar íconos
*/
function filterIcons() {
state.filteredIcons = state.icons.filter(icon => {
// Categoría
if (state.currentCategory !== 'all') {
if (state.currentCategory === 'favorites') {
if (!state.favorites.has(icon.id)) return false;
} else if (state.currentCategory === 'recent') {
if (!state.recentIcons.includes(icon.id)) return false;
} else {
if (!icon.categories.includes(state.currentCategory)) return false;
}
}
// Estilo
if (state.currentStyle !== 'all') {
if (!icon.styles.includes(state.currentStyle)) return false;
}
// Búsqueda
if (state.currentSearch) {
const searchLower = state.currentSearch.toLowerCase();
const matchesName = icon.name.toLowerCase().includes(searchLower);
const matchesLabel = icon.label.toLowerCase().includes(searchLower);
const matchesSearchTerms = icon.searchTerms.some(term =>
term.toLowerCase().includes(searchLower)
);
const matchesCategories = icon.categories.some(cat =>
cat.toLowerCase().includes(searchLower)
);
if (!(matchesName || matchesLabel || matchesSearchTerms || matchesCategories)) {
return false;
}
}
return true;
});
renderIconsGrid();
}
/**
* Seleccionar categoría
*/
function selectCategory(categoryId) {
state.currentCategory = categoryId;
document.querySelectorAll('.category-item').forEach(item => {
item.classList.remove('active');
if (item.dataset.category === categoryId) {
item.classList.add('active');
}
});
filterIcons();
}
/**
* Seleccionar estilo
*/
function selectStyle(styleId) {
state.currentStyle = styleId;
document.querySelectorAll('.style-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.style === styleId) {
btn.classList.add('active');
}
});
filterIcons();
}
/**
* Aplicar filtro rápido
*/
function applyQuickFilter(filterId) {
switch (filterId) {
case 'pro':
state.filteredIcons = state.icons.filter(icon => icon.pro);
break;
case 'free':
state.filteredIcons = state.icons.filter(icon => !icon.pro);
break;
case 'recent':
state.currentCategory = 'recent';
selectCategory('recent');
return;
case 'favorites':
state.currentCategory = 'favorites';
selectCategory('favorites');
return;
}
renderIconsGrid();
}
/**
* Seleccionar ícono
*/
function selectIcon(iconId, style) {
const icon = state.icons.find(i => i.id === iconId);
if (!icon) return;
state.selectedIcon = icon;
state.selectedStyle = style;
state.currentConfig.style = style;
// Agregar a recientes
addToRecent(iconId);
// Actualizar UI
document.querySelectorAll('.icon-card').forEach(card => {
card.classList.remove('selected');
if (card.dataset.icon === iconId) {
card.classList.add('selected');
}
});
renderDetailsPanel();
}
/**
* Seleccionar estilo para ícono
*/
function selectStyleForIcon(styleId) {
if (!state.selectedIcon) return;
state.selectedStyle = styleId;
state.currentConfig.style = styleId;
updateActiveButtons();
updatePreview(state.selectedIcon);
updateCodeSnippets(state.selectedIcon);
}
/**
* Agregar a recientes
*/
function addToRecent(iconId) {
state.recentIcons = state.recentIcons.filter(id => id !== iconId);
state.recentIcons.unshift(iconId);
if (state.recentIcons.length > CONFIG.maxRecentIcons) {
state.recentIcons.pop();
}
saveState();
}
/**
* Mostrar menú contextual
*/
function showIconContextMenu(iconId, x, y) {
// Implementación simplificada
const icon = state.icons.find(i => i.id === iconId);
if (!icon) return;
const menu = document.createElement('div');
menu.style.cssText = `
position: fixed;
left: ${x}px;
top: ${y}px;
background: var(--ls-primary-background-color);
border: 1px solid var(--ls-border-color);
border-radius: 6px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 1000;
min-width: 180px;
padding: 4px 0;
`;
const isFavorite = state.favorites.has(iconId);
menu.innerHTML = `
<div style="padding: 8px 12px; font-size: 12px; color: var(--ls-secondary-text-color); border-bottom: 1px solid var(--ls-border-color);">
${icon.label}
</div>
<div style="padding: 6px 12px; cursor: pointer; font-size: 12px;"
onclick="window.FontAwesomePlugin?.insertIcon(':fas fa-${icon.name}:')">
<i class="fas fa-plus" style="margin-right: 8px;"></i> Insertar
</div>
<div style="padding: 6px 12px; cursor: pointer; font-size: 12px;"
onclick="toggleFavorite('${iconId}')">
<i class="fas fa-heart" style="margin-right: 8px; color: ${isFavorite ? '#ef4444' : 'var(--ls-secondary-text-color)'};"></i>
${isFavorite ? 'Quitar de favoritos' : 'Agregar a favoritos'}
</div>
`;
document.body.appendChild(menu);
setTimeout(() => {
const closeMenu = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener('click', closeMenu);
}
};
document.addEventListener('click', closeMenu);
});
}
/**
* Alternar favorito
*/
window.toggleFavorite = function(iconId) {
if (state.favorites.has(iconId)) {
state.favorites.delete(iconId);
showNotification('Quitado de favoritos');
} else {
state.favorites.add(iconId);
showNotification('Agregado a favoritos');
}
saveState();
};
/**
* Insertar ícono en editor
*/
function insertIconIntoEditor(code) {
if (typeof logseq !== 'undefined') {
logseq.Editor.insertAtEditingCursor(code);
logseq.hideMainUI();
showNotification('Ícono insertado');
} else {
console.log('Insertar en editor:', code);
copyToClipboard(code);
showNotification('Código copiado (Logseq no detectado)');
}
}
// ================= DATOS DE RESPALDO =================
function getFallbackIcons() {
return [
{
id: "home",
name: "home",
label: "Home",
categories: ["household", "buildings"],
styles: ["solid", "regular", "light"],
searchTerms: ["casa", "hogar", "inicio"],
unicode: "f015",
version: "6.0.0",
free: true,
pro: true
}
];
}
function getFallbackCategories() {
return [
{ id: 'all', name: 'Todos los Íconos', icon: 'fas fa-th', count: 1 },
{ id: 'household', name: 'Hogar', icon: 'fas fa-home', count: 1 }
];
}
function getFallbackStyles() {
return [
{ id: 'solid', name: 'Solid', prefix: 'fas', class: 'fas', available: true },
{ id: 'regular', name: 'Regular', prefix: 'far', class: 'far', available: false },
{ id: 'light', name: 'Light', prefix: 'fal', class: 'fal', available: false }
];
}
</script>
</body>
</html>