// Hyvor Talk Integration for Obsidian Publish // Version: 2.0.1 - Paywall & Membership System (Fixed SPA navigation + login detection) // Documentation: https://github.com/your-repo/obsidian-hyvor-talk (function() { 'use strict'; // ============================================ // CONFIGURATION // ============================================ const CONFIG = { websiteId: '11990', pageIdStrategy: 'url', loadingMode: 'default', containerSelector: '.published-container .markdown-preview-view', insertPosition: 'end', customSelector: null, excludePaths: [], includePaths: [], showHeader: true, customSettings: {}, sso: { enabled: false, }, debug: true, // ============================================ // PAYWALL CONFIGURATION // ============================================ paywall: { enabled: true, // Pages accessibles SANS abonnement (wildcards supportés) publicPaths: [ '/', '/index', '/accueil', '/mentions-legales', '/a-propos', '/tarifs', '/contact' ], // Textes de l'écran paywall title: 'Contenu réservé aux membres', subtitle: 'Rejoignez notre communauté professionnelle pour accéder à ce contenu.', loginText: 'Déjà membre ?', loginButton: 'Se connecter', subscribeButton: 'Découvrir les offres', // Style blurContent: true, blurAmount: '8px' } }; // ============================================ // UTILITY FUNCTIONS // ============================================ function log(...args) { if (CONFIG.debug) { console.log('[Hyvor Talk]', ...args); } } function error(...args) { console.error('[Hyvor Talk]', ...args); } function matchPath(path, pattern) { if (pattern === path) return true; if (pattern.endsWith('/*')) { const prefix = pattern.slice(0, -2); return path.startsWith(prefix); } return false; } function normalizePath(path) { try { path = decodeURIComponent(path); } catch (e) {} return path.replace(/\+/g, ' ').replace(/\/+$/, '') || '/'; } function isPublicPage() { if (!CONFIG.paywall.enabled) return true; const currentPath = normalizePath(window.location.pathname); for (const pattern of CONFIG.paywall.publicPaths) { if (matchPath(currentPath, normalizePath(pattern))) { log('Page publique:', currentPath); return true; } } log('Page protégée:', currentPath); return false; } function shouldShowComments() { const currentPath = normalizePath(window.location.pathname); if (CONFIG.excludePaths.length > 0) { for (const pattern of CONFIG.excludePaths) { if (matchPath(currentPath, normalizePath(pattern))) { log('Page excluded by pattern:', pattern); return false; } } } if (CONFIG.includePaths.length > 0) { let matched = false; for (const pattern of CONFIG.includePaths) { if (matchPath(currentPath, normalizePath(pattern))) { matched = true; break; } } if (!matched) { log('Page not in include paths'); return false; } } return true; } function getPageId() { if (CONFIG.pageIdStrategy === 'url') { const canonical = document.querySelector('link[rel="canonical"]'); return canonical ? canonical.href : window.location.href; } else if (CONFIG.pageIdStrategy === 'custom') { const metaPageId = document.querySelector('meta[name="hyvor-page-id"]'); if (metaPageId) { return metaPageId.content; } return window.location.pathname; } return window.location.href; } // ============================================ // CSS STYLES // ============================================ function injectStyles() { if (document.getElementById('hyvor-talk-custom-css')) return; const style = document.createElement('style'); style.id = 'hyvor-talk-custom-css'; style.textContent = ` /* ============================================ LAYOUT FIX - Minimal theme graph view ============================================ */ /* Ensure site-body is a proper flex row */ .published-container .site-body { display: flex !important; align-items: flex-start !important; flex-wrap: nowrap !important; } /* Main content area must not overflow into graph column */ .published-container .site-body-center-column { flex: 1 1 auto !important; min-width: 0 !important; overflow-x: hidden !important; } /* Right column (graph view) stays in its lane */ .published-container .site-body-right-column { flex: 0 0 auto !important; position: sticky !important; top: 0 !important; align-self: flex-start !important; max-height: 100vh !important; overflow-y: auto !important; } /* Wrapper respects Minimal/Sanctum theme width constraint and centers */ .published-container .markdown-preview-view .hyvor-talk-wrapper { width: 100% !important; max-width: var(--line-width, var(--file-line-width, 40rem)) !important; margin: 3rem auto 0 auto !important; padding: 2rem 0 0 0 !important; box-sizing: border-box !important; } /* Basic constraints for Hyvor Talk comments */ hyvor-talk-comments { display: block !important; min-height: auto !important; height: auto !important; width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; } /* ============================================ PAYWALL STYLES ============================================ */ /* Blur effect on protected content */ .hyvor-paywall-blur { filter: blur(${CONFIG.paywall.blurAmount}) !important; pointer-events: none !important; user-select: none !important; } /* Paywall overlay container */ .hyvor-paywall-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.95); display: flex; align-items: center; justify-content: center; z-index: 9999; backdrop-filter: blur(4px); } /* Dark mode support */ .theme-dark .hyvor-paywall-overlay, [data-theme="dark"] .hyvor-paywall-overlay { background: rgba(30, 30, 30, 0.95); } /* Paywall card */ .hyvor-paywall-card { background: var(--background-primary, #ffffff); border-radius: 12px; padding: 3rem 2.5rem; max-width: 420px; width: 90%; text-align: center; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); border: 1px solid var(--background-modifier-border, #e0e0e0); } .theme-dark .hyvor-paywall-card, [data-theme="dark"] .hyvor-paywall-card { background: var(--background-primary, #1e1e1e); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4); } /* Paywall icon */ .hyvor-paywall-icon { font-size: 3rem; margin-bottom: 1.5rem; } /* Paywall title */ .hyvor-paywall-title { font-size: 1.5rem; font-weight: 600; color: var(--text-normal, #333); margin: 0 0 0.75rem 0; } /* Paywall subtitle */ .hyvor-paywall-subtitle { font-size: 1rem; color: var(--text-muted, #666); margin: 0 0 2rem 0; line-height: 1.5; } /* Paywall buttons container */ .hyvor-paywall-buttons { display: flex; flex-direction: column; gap: 0.75rem; } /* Primary button (subscribe) */ .hyvor-paywall-btn-primary { background: var(--interactive-accent, #7c3aed); color: white; border: none; padding: 0.875rem 1.5rem; font-size: 1rem; font-weight: 500; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; } .hyvor-paywall-btn-primary:hover { opacity: 0.9; transform: translateY(-1px); } /* Secondary button (login) */ .hyvor-paywall-btn-secondary { background: transparent; color: var(--text-muted, #666); border: 1px solid var(--background-modifier-border, #e0e0e0); padding: 0.75rem 1.5rem; font-size: 0.9rem; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; } .hyvor-paywall-btn-secondary:hover { background: var(--background-secondary, #f5f5f5); } /* Login text */ .hyvor-paywall-login-text { font-size: 0.85rem; color: var(--text-muted, #888); margin-top: 0.5rem; } /* Memberships widget positioning */ hyvor-talk-memberships { position: fixed; bottom: 0; right: 0; z-index: 10000; } `; document.head.appendChild(style); } // ============================================ // PAYWALL FUNCTIONS // ============================================ function createPaywallOverlay() { const overlay = document.createElement('div'); overlay.className = 'hyvor-paywall-overlay'; overlay.id = 'hyvor-paywall-overlay'; overlay.innerHTML = ` <div class="hyvor-paywall-card"> <div class="hyvor-paywall-icon">🔒</div> <h2 class="hyvor-paywall-title">${CONFIG.paywall.title}</h2> <p class="hyvor-paywall-subtitle">${CONFIG.paywall.subtitle}</p> <div class="hyvor-paywall-buttons"> <button class="hyvor-paywall-btn-primary" id="hyvor-paywall-subscribe"> ${CONFIG.paywall.subscribeButton} </button> <p class="hyvor-paywall-login-text">${CONFIG.paywall.loginText}</p> <button class="hyvor-paywall-btn-secondary" id="hyvor-paywall-login"> ${CONFIG.paywall.loginButton} </button> </div> </div> `; return overlay; } function showPaywall() { log('Showing paywall...'); // Add blur to content if enabled if (CONFIG.paywall.blurContent) { const content = document.querySelector('.markdown-preview-view'); if (content) { content.classList.add('hyvor-paywall-blur'); } } // Create and show overlay if (!document.getElementById('hyvor-paywall-overlay')) { const overlay = createPaywallOverlay(); document.body.appendChild(overlay); // Button handlers const subscribeBtn = document.getElementById('hyvor-paywall-subscribe'); const loginBtn = document.getElementById('hyvor-paywall-login'); subscribeBtn.addEventListener('click', () => { log('Opening membership modal...'); const memberships = document.querySelector('hyvor-talk-memberships'); if (memberships && memberships.api) { memberships.api.modal.open(); } }); loginBtn.addEventListener('click', () => { log('Opening login modal...'); const memberships = document.querySelector('hyvor-talk-memberships'); if (memberships && memberships.api) { memberships.api.modal.open(); } }); } } function hidePaywall() { log('Hiding paywall - user is a member'); // Remove blur const content = document.querySelector('.markdown-preview-view'); if (content) { content.classList.remove('hyvor-paywall-blur'); } // Remove overlay const overlay = document.getElementById('hyvor-paywall-overlay'); if (overlay) { overlay.remove(); } } function checkMembershipStatus(membershipsElement) { const user = membershipsElement.api.auth.user(); if (!user) { log('User not logged in'); return { loggedIn: false, isMember: false }; } log('User logged in:', user.name); if (user.subscription && user.subscription.status === 'active') { log('User has active subscription:', user.subscription.plan.name); return { loggedIn: true, isMember: true, user, subscription: user.subscription }; } log('User logged in but no active subscription'); return { loggedIn: true, isMember: false, user }; } // ============================================ // SCRIPT LOADERS // ============================================ function loadMembershipsScript() { return new Promise((resolve, reject) => { if (document.querySelector('script[src*="memberships.js"]')) { log('Memberships script already loaded'); resolve(); return; } const script = document.createElement('script'); script.src = 'https://talk.hyvor.com/embed/memberships.js'; script.type = 'module'; script.async = true; script.onload = () => { log('Memberships script loaded'); resolve(); }; script.onerror = () => { error('Failed to load Memberships script'); reject(new Error('Failed to load Memberships script')); }; document.head.appendChild(script); }); } function loadHyvorScript() { return new Promise((resolve, reject) => { if (document.querySelector('script[src*="talk.hyvor.com/embed/embed.js"]')) { log('Hyvor script already loaded'); resolve(); return; } const script = document.createElement('script'); script.src = 'https://talk.hyvor.com/embed/embed.js'; script.type = 'module'; script.async = true; script.onload = () => { log('Hyvor script loaded successfully'); resolve(); }; script.onerror = () => { error('Failed to load Hyvor script'); reject(new Error('Failed to load Hyvor Talk script')); }; document.head.appendChild(script); }); } function createMembershipsElement() { if (document.querySelector('hyvor-talk-memberships')) { return document.querySelector('hyvor-talk-memberships'); } const memberships = document.createElement('hyvor-talk-memberships'); memberships.setAttribute('website-id', CONFIG.websiteId); // French translations memberships.setAttribute('t-button-subscribe', 'S\'abonner'); memberships.setAttribute('t-modal-title', 'Rejoindre la communauté'); memberships.setAttribute('t-modal-title-members', 'Vous êtes membre !'); memberships.setAttribute('t-login-to-subscribe', 'Se connecter pour s\'abonner'); memberships.setAttribute('t-manage-subscription', 'Gérer mon abonnement'); memberships.setAttribute('t-logout', 'Déconnexion'); document.body.appendChild(memberships); return memberships; } function createCommentsElement() { const comments = document.createElement('hyvor-talk-comments'); comments.setAttribute('website-id', CONFIG.websiteId); comments.setAttribute('page-id', getPageId()); if (CONFIG.loadingMode) { comments.setAttribute('loading', CONFIG.loadingMode); } if (CONFIG.customSettings && Object.keys(CONFIG.customSettings).length > 0) { try { comments.setAttribute('settings', JSON.stringify(CONFIG.customSettings)); } catch (e) { error('Failed to apply custom settings:', e); } } if (CONFIG.sso && CONFIG.sso.enabled) { if (CONFIG.sso.userData && CONFIG.sso.hash) { comments.setAttribute('sso-user', CONFIG.sso.userData); comments.setAttribute('sso-hash', CONFIG.sso.hash); } } return comments; } function insertComments() { if (document.querySelector('.hyvor-talk-wrapper')) { log('Comments already inserted, skipping...'); return false; } let container = document.querySelector(CONFIG.containerSelector); if (!container) { error('Container not found:', CONFIG.containerSelector); return false; } log('Using stable container:', CONFIG.containerSelector); const wrapper = document.createElement('div'); wrapper.className = 'hyvor-talk-wrapper'; wrapper.style.borderTop = '1px solid var(--background-modifier-border, #e0e0e0)'; wrapper.style.minHeight = 'auto'; wrapper.style.maxHeight = 'none'; if (CONFIG.showHeader) { const header = document.createElement('h2'); header.textContent = 'Commentaires'; header.style.marginBottom = '1.5rem'; wrapper.appendChild(header); } const comments = createCommentsElement(); wrapper.appendChild(comments); if (CONFIG.insertPosition === 'custom' && CONFIG.customSelector) { const customTarget = document.querySelector(CONFIG.customSelector); if (customTarget) { customTarget.appendChild(wrapper); } else { container.appendChild(wrapper); } } else if (CONFIG.insertPosition === 'start') { container.insertBefore(wrapper, container.firstChild); } else { container.appendChild(wrapper); } log('Comments inserted successfully'); return true; } // ============================================ // INITIALIZATION // ============================================ function handlePageAccess(memberships) { const status = checkMembershipStatus(memberships); // Check if this is a public page if (isPublicPage()) { log('Public page - showing content'); hidePaywall(); if (shouldShowComments()) { insertComments(); } return; } // Protected page - check membership if (status.isMember) { log('Member access granted'); hidePaywall(); if (shouldShowComments()) { insertComments(); } } else { log('Access denied - showing paywall'); showPaywall(); } } // Polling to detect login state changes (for existing members logging in) let loginCheckInterval = null; let lastLoginState = null; function startLoginPolling(memberships) { if (loginCheckInterval) return; loginCheckInterval = setInterval(() => { const user = memberships.api ? memberships.api.auth.user() : null; const currentState = user ? `${user.id}-${user.subscription?.status || 'none'}` : 'logged-out'; if (lastLoginState !== null && lastLoginState !== currentState) { log('Login state changed:', lastLoginState, '->', currentState); handlePageAccess(memberships); } lastLoginState = currentState; }, 1000); } function stopLoginPolling() { if (loginCheckInterval) { clearInterval(loginCheckInterval); loginCheckInterval = null; } lastLoginState = null; } async function init() { log('Initializing Hyvor Talk integration v2.0...'); // Validate configuration if (!CONFIG.websiteId || CONFIG.websiteId === 'YOUR_WEBSITE_ID') { error('Website ID not configured. Please set your Hyvor Talk Website ID.'); return; } // Inject styles injectStyles(); try { // Load scripts await Promise.all([ loadHyvorScript(), loadMembershipsScript() ]); // Create or get existing memberships element const memberships = createMembershipsElement(); // Check if API is already available (SPA navigation case) if (memberships.api) { log('Memberships API already available (SPA navigation)'); handlePageAccess(memberships); startLoginPolling(memberships); } else { // First load - wait for loaded event memberships.addEventListener('loaded', () => { log('Memberships component loaded'); handlePageAccess(memberships); startLoginPolling(memberships); }); } // Listen for subscription changes (new subscriptions) memberships.addEventListener('subscription:success', (e) => { log('Subscription successful!', e.detail); hidePaywall(); if (shouldShowComments()) { insertComments(); } }); log('Hyvor Talk initialized successfully'); } catch (err) { error('Initialization failed:', err); } } // ============================================ // STARTUP // ============================================ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // Handle SPA navigation let lastUrl = location.href; let isInitializing = false; new MutationObserver(() => { const url = location.href; if (url !== lastUrl && !isInitializing) { lastUrl = url; isInitializing = true; log('Navigation detected, reinitializing...'); // Stop polling to prevent memory leaks stopLoginPolling(); // Clean up const wrapper = document.querySelector('.hyvor-talk-wrapper'); if (wrapper) wrapper.remove(); const overlay = document.getElementById('hyvor-paywall-overlay'); if (overlay) overlay.remove(); const content = document.querySelector('.markdown-preview-view'); if (content) content.classList.remove('hyvor-paywall-blur'); // Reinitialize setTimeout(() => { init(); isInitializing = false; }, 200); } }).observe(document.body || document, { subtree: true, childList: true }); })();