1/* ============================================================================= 2 main.js · å¯å¨ãhash è·¯ç±ãè¯è¨åæ¢ãä¾§æ ãé³ç¬¦ç²å 3 ----------------------------------------------------------------------------- 4 ç¨ hash è·¯ç±è䏿¯ History APIï¼GitHub Pages æ æ³é ç½® rewriteï¼ 5 History è·¯ç±å¨ç´æ¥è®¿é®åè·¯å¾æ¶ä¼ 404ã 6 ============================================================================= */ 7 8import { t, getLang, setLang, onLangChange } from './i18n.js'; 9import { applyI18n, initNotes, toast } from './ui.js'; 10import { initPlayer } from './player.js'; 11import { renderHome, renderListen, renderCreate, renderLibrary, markCurrent, 12 applyHandoff } from './pages.js'; 13import { prefs } from './store.js'; 14import { openApiKeyDialog, onKeyChange } from './apikey.js'; 15import { canGenerate, needsUserKey } from './config.js'; 16 17const $ = id => document.getElementById(id); 18 19const ROUTES = { 20 home: { view: 'view-home', render: renderHome, once: false }, 21 listen: { view: 'view-listen', render: renderListen, once: true }, 22 create: { view: 'view-create', render: renderCreate, once: true }, 23 library: { view: 'view-library', render: renderLibrary, once: false }, 24}; 25const rendered = new Set(); 26let current = null; 27 28function routeName() { 29 const h = (location.hash || '#/').replace(/^#\/?/, '').split('?')[0]; 30 return ROUTES[h] ? h : 'home'; 31} 32 33function navigate() { 34 const name = routeName(); 35 const r = ROUTES[name]; 36 37 for (const [k, v] of Object.entries(ROUTES)) { 38 $(v.view).classList.toggle('on', k === name); 39 } 40 document.querySelectorAll('.nav-item').forEach(a => { 41 if (a.dataset.route === name) a.setAttribute('aria-current', 'page'); 42 else a.removeAttribute('aria-current'); 43 }); 44 45 if (!r.once || !rendered.has(name)) { 46 r.render(); 47 rendered.add(name); 48 } else { 49 markCurrent(); 50 // åä½é¡µæ¯ once:trueï¼ä¿çä¼è¯ä»»å¡ä¸å·²ä¸ä¼ é³é¢ï¼ï¼ä½ä»é¦é¡µå¸¦è¿æ¥ç 51 // åææ¯æ¬¡é½è¦çæï¼æä»¥åç¬è°ä¸æ¬¡ã 52 if (name === 'create') applyHandoff(); 53 } 54 55 document.title = name === 'home' 56 ? t('app.title') 57 : `${t('nav.' + name)} · StepAudio-3-Music`; 58 59 // å页åå°é¡¶é¨ï¼ä½ä¸è¦æææ£å¨ææ¾çé³é¢ 60 if (current !== name) $('main').scrollTo({ top: 0, behavior: 'instant' }); 61 current = name; 62} 63 64/* ââ ä¾§æ æ¶èµ· ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 65function applyNav() { 66 const mini = prefs.navMini; 67 $('app').dataset.nav = mini ? 'mini' : 'full'; 68 $('navToggleIcon').setAttribute('href', mini ? '#i-panel-open' : '#i-panel-close'); 69 const btn = $('navToggle'); 70 btn.dataset.tipI18n = mini ? 'nav.expand' : 'nav.collapse'; 71 btn.querySelector('.nav-label').textContent = t(mini ? 'nav.expand' : 'nav.collapse'); 72 btn.dataset.tip = t(mini ? 'nav.expand' : 'nav.collapse'); 73} 74 75/* ââ API Key ç¶æç¹ âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 76 ä¾§æ é£ä¸ªå°åç¹ï¼æ²¡é Key æ¶äº®ç¥çè²ï¼è®©è®¿å®¢ä¸ç¼çå°è¿å·®ä¸æ¥ã 77 proxy 模å¼ä¸é¢åº¦ç±å¹³å°ä¾ç»ï¼ä¸éè¦è®¿å®¢é Keyï¼å°±å«æ¾ç¤ºäºã */ 78function applyKeyDot() { 79 const dot = $('navKeyDot'); 80 if (!dot) return; 81 const need = needsUserKey() && !canGenerate(); 82 dot.hidden = !need; 83 dot.classList.toggle('warn', need); 84} 85 86/* ââ è¯è¨ ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 87function applyLang() { 88 const lang = getLang(); 89 document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en'; 90 document.querySelectorAll('.lang-switch button').forEach(b => { 91 b.setAttribute('aria-pressed', String(b.dataset.lang === lang)); 92 }); 93 applyI18n(document); 94 applyNav(); 95 applyKeyDot(); 96} 97 98/* ââ å¯å¨ ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 99function boot() { 100 applyLang(); 101 initPlayer(); 102 initNotes(); 103 104 $('navToggle').onclick = () => { prefs.navMini = !prefs.navMini; applyNav(); }; 105 $('navApiKey').onclick = openApiKeyDialog; 106 onKeyChange(applyKeyDot); 107 applyKeyDot(); 108 document.querySelectorAll('.lang-switch button').forEach(b => { 109 b.onclick = () => setLang(b.dataset.lang); 110 }); 111 112 // å ä½é¾æ¥ï¼çå®å°åå¾ å®ï¼ç¹äºç»ä¸ªæç¡®åé¦è䏿¯éé»è·³ # 113 document.querySelectorAll('[data-placeholder-link]').forEach(a => { 114 a.onclick = e => { e.preventDefault(); toast(getLang() === 'zh' ? '龿¥å¾ è¡¥å ' : 'Link coming soon'); }; 115 }); 116 117 onLangChange(() => { 118 applyLang(); 119 rendered.clear(); // è¯è¨åäºï¼é¡µé¢å 容è¦é建 120 navigate(); 121 }); 122 123 addEventListener('hashchange', navigate); 124 navigate(); 125 126 addEventListener('offline', () => toast(t('err.offline'))); 127} 128 129if (document.readyState === 'loading') { 130 document.addEventListener('DOMContentLoaded', boot, { once: true }); 131} else { 132 boot(); 133}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.