add github
This commit is contained in:
		
							
								
								
									
										373
									
								
								my-blog/templates/post-complex.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								my-blog/templates/post-complex.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,373 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ post.title }} - {{ config.title }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="article-container">
 | 
			
		||||
    <article class="article-content">
 | 
			
		||||
        <header class="article-header">
 | 
			
		||||
            <h1 class="article-title">{{ post.title }}</h1>
 | 
			
		||||
            <div class="article-meta">
 | 
			
		||||
                <time class="article-date">{{ post.date }}</time>
 | 
			
		||||
                {% if post.language %}
 | 
			
		||||
                <span class="article-lang">{{ post.language }}</span>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="article-actions">
 | 
			
		||||
                {% if post.markdown_url %}
 | 
			
		||||
                <a href="{{ post.markdown_url }}" class="action-btn markdown-btn" title="View Markdown">
 | 
			
		||||
                    📝 Markdown
 | 
			
		||||
                </a>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                {% if post.translation_url %}
 | 
			
		||||
                <a href="{{ post.translation_url }}" class="action-btn translation-btn" title="View Translation">
 | 
			
		||||
                    🌐 {% if post.language == 'ja' %}English{% else %}日本語{% endif %}
 | 
			
		||||
                </a>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </header>
 | 
			
		||||
        
 | 
			
		||||
        <div class="article-body">
 | 
			
		||||
            {{ post.content | safe }}
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <!-- Comment Section -->
 | 
			
		||||
        <section class="comment-section">
 | 
			
		||||
            <div class="comment-container">
 | 
			
		||||
                <h3>Comments</h3>
 | 
			
		||||
                
 | 
			
		||||
                <!-- ATProto Auth Widget Container -->
 | 
			
		||||
                <div id="atproto-auth-widget" class="comment-auth"></div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="commentForm" class="comment-form" style="display: none;">
 | 
			
		||||
                    <textarea id="commentText" placeholder="Share your thoughts..." rows="4"></textarea>
 | 
			
		||||
                    <button onclick="submitComment()" class="submit-btn">Post Comment</button>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="commentsList" class="comments-list">
 | 
			
		||||
                    <!-- Comments will be loaded here -->
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </section>
 | 
			
		||||
    </article>
 | 
			
		||||
    
 | 
			
		||||
    <aside class="article-sidebar">
 | 
			
		||||
        <nav class="toc">
 | 
			
		||||
            <h3>Contents</h3>
 | 
			
		||||
            <div id="toc-content">
 | 
			
		||||
                <!-- TOC will be generated by JavaScript -->
 | 
			
		||||
            </div>
 | 
			
		||||
        </nav>
 | 
			
		||||
    </aside>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block sidebar %}
 | 
			
		||||
<!-- Include ATProto Libraries via script tags (more reliable than dynamic imports) -->
 | 
			
		||||
<script src="https://cdn.jsdelivr.net/npm/@atproto/oauth-client-browser@latest/dist/index.js"></script>
 | 
			
		||||
<script src="https://cdn.jsdelivr.net/npm/@atproto/api@latest/dist/index.js"></script>
 | 
			
		||||
 | 
			
		||||
<!-- Fallback: Try multiple CDNs -->
 | 
			
		||||
<script>
 | 
			
		||||
console.log('Checking ATProto library availability...');
 | 
			
		||||
 | 
			
		||||
// Check if libraries loaded successfully
 | 
			
		||||
if (typeof ATProto === 'undefined' && typeof window.ATProto === 'undefined') {
 | 
			
		||||
    console.log('Primary CDN failed, trying fallback...');
 | 
			
		||||
    
 | 
			
		||||
    // Create fallback script elements
 | 
			
		||||
    const fallbackScripts = [
 | 
			
		||||
        'https://unpkg.com/@atproto/oauth-client-browser@latest/dist/index.js',
 | 
			
		||||
        'https://esm.sh/@atproto/oauth-client-browser',
 | 
			
		||||
        'https://cdn.skypack.dev/@atproto/oauth-client-browser'
 | 
			
		||||
    ];
 | 
			
		||||
    
 | 
			
		||||
    // Load fallback scripts sequentially
 | 
			
		||||
    let scriptIndex = 0;
 | 
			
		||||
    function loadNextScript() {
 | 
			
		||||
        if (scriptIndex < fallbackScripts.length) {
 | 
			
		||||
            const script = document.createElement('script');
 | 
			
		||||
            script.src = fallbackScripts[scriptIndex];
 | 
			
		||||
            script.onload = () => {
 | 
			
		||||
                console.log(`Loaded from fallback CDN: ${fallbackScripts[scriptIndex]}`);
 | 
			
		||||
                window.atprotoLibrariesReady = true;
 | 
			
		||||
            };
 | 
			
		||||
            script.onerror = () => {
 | 
			
		||||
                console.log(`Failed to load from: ${fallbackScripts[scriptIndex]}`);
 | 
			
		||||
                scriptIndex++;
 | 
			
		||||
                loadNextScript();
 | 
			
		||||
            };
 | 
			
		||||
            document.head.appendChild(script);
 | 
			
		||||
        } else {
 | 
			
		||||
            console.error('All CDN fallbacks failed');
 | 
			
		||||
            window.atprotoLibrariesReady = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    loadNextScript();
 | 
			
		||||
} else {
 | 
			
		||||
    console.log('✅ ATProto libraries loaded from primary CDN');
 | 
			
		||||
    window.atprotoLibrariesReady = true;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<!-- Simple ATProto Widget (no external dependency) -->
 | 
			
		||||
<link rel="stylesheet" href="/atproto-auth-widget/dist/atproto-auth.min.css">
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
// Initialize auth widget
 | 
			
		||||
let authWidget = null;
 | 
			
		||||
 | 
			
		||||
document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
    generateTableOfContents();
 | 
			
		||||
    initializeAuthWidget();
 | 
			
		||||
    loadComments();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function generateTableOfContents() {
 | 
			
		||||
    const tocContainer = document.getElementById('toc-content');
 | 
			
		||||
    const headings = document.querySelectorAll('.article-body h1, .article-body h2, .article-body h3, .article-body h4, .article-body h5, .article-body h6');
 | 
			
		||||
    
 | 
			
		||||
    if (headings.length === 0) {
 | 
			
		||||
        tocContainer.innerHTML = '<p class="no-toc">No headings found</p>';
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const tocList = document.createElement('ul');
 | 
			
		||||
    tocList.className = 'toc-list';
 | 
			
		||||
    
 | 
			
		||||
    headings.forEach((heading, index) => {
 | 
			
		||||
        const id = `heading-${index}`;
 | 
			
		||||
        heading.id = id;
 | 
			
		||||
        
 | 
			
		||||
        const listItem = document.createElement('li');
 | 
			
		||||
        listItem.className = `toc-item toc-${heading.tagName.toLowerCase()}`;
 | 
			
		||||
        
 | 
			
		||||
        const link = document.createElement('a');
 | 
			
		||||
        link.href = `#${id}`;
 | 
			
		||||
        link.textContent = heading.textContent;
 | 
			
		||||
        link.className = 'toc-link';
 | 
			
		||||
        
 | 
			
		||||
        // Smooth scroll behavior
 | 
			
		||||
        link.addEventListener('click', function(e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            heading.scrollIntoView({ behavior: 'smooth' });
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        listItem.appendChild(link);
 | 
			
		||||
        tocList.appendChild(listItem);
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    tocContainer.appendChild(tocList);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Initialize ATProto Auth Widget
 | 
			
		||||
async function initializeAuthWidget() {
 | 
			
		||||
    try {
 | 
			
		||||
        // Check WebCrypto API availability
 | 
			
		||||
        console.log('WebCrypto check:', {
 | 
			
		||||
            available: !!window.crypto && !!window.crypto.subtle,
 | 
			
		||||
            secureContext: window.isSecureContext,
 | 
			
		||||
            protocol: window.location.protocol,
 | 
			
		||||
            hostname: window.location.hostname
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        if (!window.crypto || !window.crypto.subtle) {
 | 
			
		||||
            throw new Error('WebCrypto API is not available. This requires HTTPS or localhost.');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!window.isSecureContext) {
 | 
			
		||||
            console.warn('Not in secure context - WebCrypto may not work properly');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Simplified approach: Show manual OAuth form
 | 
			
		||||
        console.log('Using simplified OAuth approach...');
 | 
			
		||||
        showSimpleOAuthForm();
 | 
			
		||||
            // Fallback to widget initialization
 | 
			
		||||
            authWidget = await window.initATProtoWidget('#atproto-auth-widget', {
 | 
			
		||||
                clientId: clientId,
 | 
			
		||||
            onLogin: (session) => {
 | 
			
		||||
                console.log('User logged in:', session.handle);
 | 
			
		||||
                document.getElementById('commentForm').style.display = 'block';
 | 
			
		||||
            },
 | 
			
		||||
            onLogout: () => {
 | 
			
		||||
                console.log('User logged out');
 | 
			
		||||
                document.getElementById('commentForm').style.display = 'none';
 | 
			
		||||
            },
 | 
			
		||||
            onError: (error) => {
 | 
			
		||||
                console.error('ATProto Auth Error:', error);
 | 
			
		||||
                // Show user-friendly error message
 | 
			
		||||
                const authContainer = document.getElementById('atproto-auth-widget');
 | 
			
		||||
                if (authContainer) {
 | 
			
		||||
                    let errorMessage = 'Authentication service is temporarily unavailable.';
 | 
			
		||||
                    let suggestion = 'Please try refreshing the page.';
 | 
			
		||||
                    
 | 
			
		||||
                    if (error.message && error.message.includes('WebCrypto')) {
 | 
			
		||||
                        errorMessage = 'This feature requires a secure HTTPS connection.';
 | 
			
		||||
                        suggestion = 'Please ensure you are accessing via https://log.syui.ai';
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    authContainer.innerHTML = `
 | 
			
		||||
                        <div class="atproto-auth__fallback">
 | 
			
		||||
                            <p>${errorMessage}</p>
 | 
			
		||||
                            <p>${suggestion}</p>
 | 
			
		||||
                            <details style="margin-top: 10px; font-size: 0.8em; color: #666;">
 | 
			
		||||
                                <summary>Technical details</summary>
 | 
			
		||||
                                <pre>${error.message || 'Unknown error'}</pre>
 | 
			
		||||
                            </details>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    `;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            theme: 'default'
 | 
			
		||||
            });
 | 
			
		||||
        } else if (typeof window.ATProtoAuthWidget === 'function') {
 | 
			
		||||
            // Fallback to direct widget initialization
 | 
			
		||||
            authWidget = new window.ATProtoAuthWidget({
 | 
			
		||||
                containerSelector: '#atproto-auth-widget',
 | 
			
		||||
                clientId: clientId,
 | 
			
		||||
                onLogin: (session) => {
 | 
			
		||||
                    console.log('User logged in:', session.handle);
 | 
			
		||||
                    document.getElementById('commentForm').style.display = 'block';
 | 
			
		||||
                },
 | 
			
		||||
                onLogout: () => {
 | 
			
		||||
                    console.log('User logged out');
 | 
			
		||||
                    document.getElementById('commentForm').style.display = 'none';
 | 
			
		||||
                },
 | 
			
		||||
                onError: (error) => {
 | 
			
		||||
                    console.error('ATProto Auth Error:', error);
 | 
			
		||||
                    const authContainer = document.getElementById('atproto-auth-widget');
 | 
			
		||||
                    if (authContainer) {
 | 
			
		||||
                        authContainer.innerHTML = `
 | 
			
		||||
                            <div class="atproto-auth__fallback">
 | 
			
		||||
                                <p>Authentication service is temporarily unavailable.</p>
 | 
			
		||||
                                <p>Please try refreshing the page.</p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        `;
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                theme: 'default'
 | 
			
		||||
            });
 | 
			
		||||
            await authWidget.init();
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error('ATProto widget not available');
 | 
			
		||||
        }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error('Failed to initialize auth widget:', error);
 | 
			
		||||
        // Show fallback UI
 | 
			
		||||
        const authContainer = document.getElementById('atproto-auth-widget');
 | 
			
		||||
        if (authContainer) {
 | 
			
		||||
            authContainer.innerHTML = `
 | 
			
		||||
                <div class="atproto-auth__fallback">
 | 
			
		||||
                    <p>Authentication widget failed to load.</p>
 | 
			
		||||
                    <p>Please check your internet connection and refresh the page.</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            `;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function submitComment() {
 | 
			
		||||
    const commentText = document.getElementById('commentText').value.trim();
 | 
			
		||||
    if (!commentText || !authWidget.isLoggedIn()) {
 | 
			
		||||
        alert('Please login and enter a comment');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
        const postSlug = '{{ post.slug }}';
 | 
			
		||||
        const postUrl = window.location.href;
 | 
			
		||||
        const createdAt = new Date().toISOString();
 | 
			
		||||
        
 | 
			
		||||
        // Create comment record using the auth widget
 | 
			
		||||
        const response = await authWidget.createRecord('ai.log.comment', {
 | 
			
		||||
            $type: 'ai.log.comment',
 | 
			
		||||
            text: commentText,
 | 
			
		||||
            post_slug: postSlug,
 | 
			
		||||
            post_url: postUrl,
 | 
			
		||||
            createdAt: createdAt
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        console.log('Comment posted:', response);
 | 
			
		||||
        document.getElementById('commentText').value = '';
 | 
			
		||||
        loadComments();
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error('Comment submission failed:', error);
 | 
			
		||||
        alert('Failed to post comment: ' + error.message);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showAuthenticatedState(session) {
 | 
			
		||||
    const authContainer = document.getElementById('atproto-auth-widget');
 | 
			
		||||
    const agent = new window.ATProtoAgent(session);
 | 
			
		||||
    
 | 
			
		||||
    authContainer.innerHTML = `
 | 
			
		||||
        <div class="atproto-auth__authenticated">
 | 
			
		||||
            <p>✅ Authenticated as: <strong>${session.did}</strong></p>
 | 
			
		||||
            <button id="logout-btn" class="atproto-auth__button">Logout</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    `;
 | 
			
		||||
    
 | 
			
		||||
    document.getElementById('logout-btn').onclick = async () => {
 | 
			
		||||
        await session.signOut();
 | 
			
		||||
        window.location.reload();
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Show comment form
 | 
			
		||||
    document.getElementById('commentForm').style.display = 'block';
 | 
			
		||||
    window.currentSession = session;
 | 
			
		||||
    window.currentAgent = agent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showLoginForm(oauthClient) {
 | 
			
		||||
    const authContainer = document.getElementById('atproto-auth-widget');
 | 
			
		||||
    
 | 
			
		||||
    authContainer.innerHTML = `
 | 
			
		||||
        <div class="atproto-auth__login">
 | 
			
		||||
            <h4>Login with ATProto</h4>
 | 
			
		||||
            <input type="text" id="handle-input" placeholder="user.bsky.social" />
 | 
			
		||||
            <button id="login-btn" class="atproto-auth__button">Connect</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    `;
 | 
			
		||||
    
 | 
			
		||||
    document.getElementById('login-btn').onclick = async () => {
 | 
			
		||||
        const handle = document.getElementById('handle-input').value.trim();
 | 
			
		||||
        if (!handle) {
 | 
			
		||||
            alert('Please enter your handle');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        try {
 | 
			
		||||
            const url = await oauthClient.authorize(handle);
 | 
			
		||||
            window.open(url, '_self', 'noopener');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error('OAuth authorization failed:', error);
 | 
			
		||||
            alert('Authentication failed: ' + error.message);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Enter key support
 | 
			
		||||
    document.getElementById('handle-input').onkeypress = (e) => {
 | 
			
		||||
        if (e.key === 'Enter') {
 | 
			
		||||
            document.getElementById('login-btn').click();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function loadComments() {
 | 
			
		||||
    try {
 | 
			
		||||
        const commentsList = document.getElementById('commentsList');
 | 
			
		||||
        commentsList.innerHTML = '<p class="loading">Loading comments from ATProto network...</p>';
 | 
			
		||||
        
 | 
			
		||||
        // In a real implementation, you would query an aggregation service
 | 
			
		||||
        // For demo, show empty state
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            commentsList.innerHTML = '<p class="no-comments">Comments will appear here when posted via ATProto.</p>';
 | 
			
		||||
        }, 1000);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error('Failed to load comments:', error);
 | 
			
		||||
        document.getElementById('commentsList').innerHTML = '<p class="error">Failed to load comments</p>';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user