cookcli 0.28.0

A command-line interface for managing and working with Cooklang recipes
Documentation
{% extends "base.html" %}

{% block title %}Preferences - Cook{% endblock %}

{% block content %}
<div>
    <h1 class="text-2xl font-bold mb-6">{{ tr.t("pref-title") }}</h1>

    <div class="space-y-6">
        <!-- Language Selector -->
        <div class="bg-gradient-to-r from-purple-50 to-pink-50 p-6 rounded-2xl border-2 border-purple-200">
            <h2 class="text-lg font-semibold mb-4 text-purple-900">{{ tr.t("pref-language") }}</h2>
            <div class="flex flex-wrap gap-3">
                <button onclick="setLanguage('en-US')"
                        class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "en-US" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
                    🇺🇸 English
                </button>
                <button onclick="setLanguage('de-DE')"
                        class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "de-DE" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
                    🇩🇪 Deutsch
                </button>
                <button onclick="setLanguage('nl-NL')"
                        class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "nl-NL" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
                    🇳🇱 Nederlands
                </button>
                <button onclick="setLanguage('fr-FR')"
                        class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "fr-FR" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
                    🇫🇷 Français
                </button>
                <button onclick="setLanguage('es-ES')"
                        class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "es-ES" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
                    🇪🇸 Español
                </button>
                <button onclick="setLanguage('eu-ES')"
                        class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "eu-ES" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
                    Euskara
                </button>
                <button onclick="setLanguage('sv-SE')"
                        class="language-btn px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 {% if tr.lang_string() == "sv-SE" %}bg-gradient-to-r from-orange-500 to-orange-600 text-white border-orange-600 shadow-lg scale-105{% else %}bg-white text-gray-700 border-gray-300 hover:border-orange-400 hover:bg-orange-50 hover:scale-105{% endif %}">
                    🇸🇪 Svenska
                </button>
            </div>
            <p class="text-sm text-purple-700 mt-3">Your language preference will be saved in a cookie.</p>
        </div>

        <!-- CookCloud Sync -->
        {% if sync_enabled %}
        <div class="bg-gray-50 p-4 rounded">
            <h2 class="text-lg font-semibold mb-3">CookCloud Sync</h2>
            <div id="sync-section">
                {% if sync_logged_in %}
                <div class="flex items-center justify-between">
                    <div>
                        <p class="text-sm">
                            Signed in as <span class="font-medium">{{ sync_email.as_deref().unwrap_or("Unknown") }}</span>
                        </p>
                        <p class="text-xs text-gray-600 mt-1" id="sync-status-text">{% if sync_syncing %}Syncing recipes...{% else %}Sync idle{% endif %}</p>
                    </div>
                    <button onclick="syncLogout()"
                            class="px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 bg-white text-red-600 border-red-300 hover:bg-red-50 hover:border-red-400">
                        Logout
                    </button>
                </div>
                {% else %}
                <div class="flex items-center justify-between">
                    <p class="text-sm text-gray-600" id="sync-login-message">Sync your recipes across devices with CookCloud.</p>
                    <button id="sync-login-btn" onclick="syncLogin()"
                            class="px-4 py-2 rounded-lg font-medium transition-all duration-200 border-2 bg-orange-700 text-white border-orange-800 hover:bg-orange-800">
                        Login to CookCloud
                    </button>
                </div>
                {% endif %}
            </div>
        </div>
        {% endif %}

        <div class="bg-gray-50 p-4 rounded">
            <h2 class="text-lg font-semibold mb-3">Configuration Files</h2>
            <div class="space-y-2 text-sm">
                <div>
                    <span class="font-medium">{{ tr.t("pref-aisle-path") }}:</span>
                    <span class="text-gray-600 ml-2">{{ aisle_path }}</span>
                </div>
                <div>
                    <span class="font-medium">{{ tr.t("pref-pantry-path") }}:</span>
                    <span class="text-gray-600 ml-2">{{ pantry_path }}</span>
                </div>
            </div>
        </div>

        <div class="bg-gray-50 p-4 rounded">
            <h2 class="text-lg font-semibold mb-3">Recipe Directory</h2>
            <div class="text-sm">
                <span class="font-medium">{{ tr.t("pref-base-path") }}:</span>
                <span class="text-gray-600 ml-2">{{ base_path }}</span>
            </div>
        </div>

        <div class="bg-gray-50 p-4 rounded">
            <h2 class="text-lg font-semibold mb-3">About</h2>
            <div class="space-y-2 text-sm">
                <div>
                    <span class="font-medium">{{ tr.t("pref-version") }}:</span>
                    <span class="text-gray-600 ml-2">{{ version }}</span>
                </div>
                <div>
                    <span class="font-medium">Cooklang CLI:</span>
                    <span class="text-gray-600 ml-2">A command-line tool for managing Cooklang recipes</span>
                </div>
            </div>
        </div>

        <div class="bg-gray-50 p-4 rounded">
            <h2 class="text-lg font-semibold mb-3">Documentation & Resources</h2>
            <div class="space-y-2 text-sm">
                <div>
                    <a href="https://cooklang.org/cli/" target="_blank" rel="noopener noreferrer"
                       class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
                        📚 CLI Documentation
                    </a>
                    <span class="text-gray-600 ml-2">- Learn about all CLI commands and features</span>
                </div>
                <div>
                    <a href="https://cooklang.org/docs/spec/" target="_blank" rel="noopener noreferrer"
                       class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
                        📖 Cooklang Specification
                    </a>
                    <span class="text-gray-600 ml-2">- Recipe markup language syntax guide</span>
                </div>
                <div>
                    <a href="https://cooklang.org/docs/getting-started/" target="_blank" rel="noopener noreferrer"
                       class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
                        🚀 Getting Started
                    </a>
                    <span class="text-gray-600 ml-2">- Introduction to Cooklang basics</span>
                </div>
                <div>
                    <a href="https://github.com/cooklang/CookCLI" target="_blank" rel="noopener noreferrer"
                       class="text-orange-700 hover:text-orange-800 hover:underline font-medium">
                        🔧 GitHub Repository
                    </a>
                    <span class="text-gray-600 ml-2">- Source code and issue tracker</span>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

{% block scripts %}
<script>
    function setLanguage(lang) {
        // Set cookie for 1 year
        const maxAge = 365 * 24 * 60 * 60;
        document.cookie = `lang=${lang}; path={{ prefix }}/; max-age=${maxAge}; SameSite=Lax`;

        // Reload page to apply new language
        window.location.reload();
    }

    {% if sync_enabled %}
    async function syncLogin() {
        const btn = document.getElementById('sync-login-btn');
        const msg = document.getElementById('sync-login-message');
        try {
            btn.disabled = true;
            btn.textContent = 'Opening browser...';
            btn.classList.add('opacity-50', 'cursor-not-allowed');
            msg.textContent = 'Complete login in your browser.';

            const resp = await fetch('{{ prefix }}/api/sync/login', { method: 'POST' });
            if (!resp.ok) {
                const err = await resp.json();
                btn.disabled = false;
                btn.textContent = 'Login to CookCloud';
                btn.classList.remove('opacity-50', 'cursor-not-allowed');
                msg.textContent = err.error || 'Login failed. Please try again.';
                return;
            }

            btn.textContent = 'Waiting for login...';

            // Poll for login completion
            const pollInterval = setInterval(async () => {
                const status = await fetch('{{ prefix }}/api/sync/status').then(r => r.json());
                if (status.logged_in) {
                    clearInterval(pollInterval);
                    window.removeEventListener('beforeunload', cleanupPolling);
                    window.location.reload();
                }
            }, 2000);
            // Stop polling after 5 minutes and show timeout message
            const timeoutId = setTimeout(() => {
                clearInterval(pollInterval);
                window.removeEventListener('beforeunload', cleanupPolling);
                btn.disabled = false;
                btn.textContent = 'Login to CookCloud';
                btn.classList.remove('opacity-50', 'cursor-not-allowed');
                msg.textContent = 'Login timed out. Please try again.';
            }, 300000);
            // Clean up timers if user navigates away
            function cleanupPolling() {
                clearInterval(pollInterval);
                clearTimeout(timeoutId);
            }
            window.addEventListener('beforeunload', cleanupPolling);
        } catch (e) {
            btn.disabled = false;
            btn.textContent = 'Login to CookCloud';
            btn.classList.remove('opacity-50', 'cursor-not-allowed');
            msg.textContent = 'Failed to start login: ' + e.message;
        }
    }

    async function syncLogout() {
        try {
            await fetch('{{ prefix }}/api/sync/logout', { method: 'POST' });
            window.location.reload();
        } catch (e) {
            alert('Logout failed: ' + e.message);
        }
    }
    {% endif %}
</script>
{% endblock %}