\n \n
\n \n \n
\n \n \n \n
\n\n \n \n \n
\n \n \n \n '}setupEventListeners(){super.setupEventListeners();const e=this.element.querySelector("#import-btn"),t=this.element.querySelector("#thesession-username"),n=this.element.querySelector("#import-limit"),s=this.element.querySelector("#thesession-tune-id"),a=this.element.querySelector("#import-status");e.addEventListener("click",()=>this.handleImport());const i=e=>{"Enter"!==e.key||this.isLoading||this.handleImport()};s.addEventListener("keypress",i),t.addEventListener("keypress",i),n.addEventListener("keypress",i);const r=()=>{a.textContent="",a.className="import-status"};t.addEventListener("input",r),s.addEventListener("input",r)}showStatus(e,t="info"){const n=this.element.querySelector("#import-status");n.textContent=e,n.className=`import-status import-status--${t}`}setLoading(e){this.isLoading=e;this.element.querySelector(".modal__overlay").style.cursor=e?"wait":"pointer"}static delay(e){return new Promise(t=>setTimeout(t,e))}async handleImport(){const e=this.element.querySelector("#import-btn");this.setLoading(!0),e.disabled=!0;const t=this.element.querySelector("#thesession-tune-id").value?.trim(),s=this.element.querySelector("#thesession-username").value?.trim(),a=parseInt(document.getElementById("import-limit").value)||10;if(s||t){this.setLoading(!0);try{let e,i;if(s){if(this.showStatus("Fetching member information…","info"),e=await this.getMemberIdByUsername(s),!e)throw new Error(`Member '${s}' not found`);this.showStatus(`Found member ${s}.`,"info")}if(i=t?[t]:await this.getMemberTunebook(e,this.tunesData.length+a),0===i.length)throw new Error("No tunes found");this.showStatus(`Found ${i.length} tunes. Fetching ABC settings…`,"info");const r=[],o=[];for(let t=0;te.name&&d.name&&(e.name.trim().toLowerCase()===d.name.trim().toLowerCase()||d.aliases?.find(t=>t?.trim().toLowerCase()===e.name.trim().toLowerCase())))){o.push(d.name);continue}const c=n(d);if(h.emit("tuneImported",c),r.push(c.name),r.length>=a)break;await m.delay(200)}if(r.length>0){let e=`Successfully imported ${r.length} tunes.`;o.length>0&&(e+=` Skipped ${o.length} tunes already in list.`),this.showStatus(e,"success")}let d=`Successfully imported ${r.length} tunes.`;o.length>0&&(d+=` Skipped ${o.length} tunes already in list.`),this.showStatus(d,"success")}catch(e){console.error("Import error:",e),this.showStatus(e.message||"import error","error")}finally{this.setLoading(!1),e.disabled=!1}}else this.showStatus("Please enter a username and/or a tune ID","error")}async getMemberIdByUsername(e){const t=`https://thesession.org/members/search?q=${encodeURIComponent(e)}&format=json`,n=await fetch(t);if(!n.ok)throw new Error(`Failed to search for member: ${n.status}`);const s=await n.json(),a=s.members?.find(t=>t.name.toLowerCase()===e.toLowerCase());return a?.id||null}async getMemberTunebook(e,t=500){const n=[];let s=1;const a=Math.min([20,t]);for(;n.length{const n=a.comments.find(e=>e.date===t.date);let s=n?"\n"+n.content.replace(/ /gm,"\n").split("\n").map(e=>function(e,t=40,n=20,s="N:"){const a=e.split(/\s+/),i=[];let r="";for(let e=0;et;)r.length>0&&(i.push(r),r=""),i.push(n.slice(0,t)),n=n.slice(t);r.length&&r.length+1+n.length>t?(i.push(r),r=n):r=r.length?r+" "+n:n}return r.length>0&&i.push(r),i.map(e=>s+e).join("\n")}(e,80)).join("\n")+"\nN:---":"";return`X:1\nT:${a.name+c}\nR:${a.type}\nL:1/8\nM:${o+s}\nN:Imported from https://thesession.org/tunes/${e}#setting${t.id}${t.member?.name?`\nN:Setting entered in thesession by user ${t.member.name}`:""} on ${t.date.substr(0,10)}\nK:${t.key}\n${t.abc.replace(/!(\w+)!/gm,"__$1__").replace(/\!/gm,"\n").replace(/__(\w+)__/gm,"!$1!")}`};return{name:a.name,nameIsFromAbc:!0,abc:r?r.map(l):l(d),scores:[{url:`https://thesession.org/tunes/${e}#setting${d.id}`,name:"thesession.org"}]}}selectBestSetting(e,t=null){if(!e||0===e.length)return null;if(t){const n=e.filter(e=>e.member&&e.member.id===t);if(n.length>0)return n}return e[0]}}const u="tunesData";let A,p,g={column:null,direction:"asc"};function f(){try{localStorage.setItem(u,JSON.stringify(tunesData)),console.log("Saved to local storage")}catch(e){console.error("Failed to save to local storage:",e)}}function B(){confirm("This will reset all tunes to the original data. Continue?")&&(localStorage.removeItem(u),location.reload())}function y(){localStorage.getItem(u)&&!confirm("You may lose some data. This cannot be undone. Continue?")||localStorage.removeItem(u),window.tunesData=[],window.filteredData=[],T(),f()}function G(){window.tunesData.forEach(e=>{e.nameIsFromAbc&&(delete e.name,delete e.nameIsFromAbc),e.keyIsFromAbc&&(delete e.key,delete e.keyIsFromAbc),e.rhythmIsFromAbc&&(delete e.rhythm,delete e.rhythmIsFromAbc),e.references=e.references?.filter(e=>!e.fromAbc),0===e.references?.length&&delete e.references,0===e.scores?.length&&delete e.scores,e.abc||delete e.abc});const e=function(e,t=2){const n=new WeakSet;return function e(s,a){if("object"==typeof s&&null!==s){if(n.has(s))return"[Circular]";if(n.add(s),Array.isArray(s)){const n=s.map(t=>e(t,a+1));return`[\n${" ".repeat((a+1)*t)}${n.join(`,\n${" ".repeat((a+1)*t)}`)}\n${" ".repeat(a*t)}]`}{const n=Object.entries(s).map(([t,n])=>`${/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?t:JSON.stringify(t)}: ${e(n,a+1)}`);return`{\n${" ".repeat((a+1)*t)}${n.join(`,\n${" ".repeat((a+1)*t)}`)}\n${" ".repeat(a*t)}}`}}return"string"==typeof s?s.includes("\n")||s.includes("\r")?"`"+s.replace(/`/g,"\\`")+"`":JSON.stringify(s):String(s)}(e,0)}(tunesData,2);navigator.clipboard.writeText(e).then(()=>{const e=document.getElementById("copyTunesBtn"),t=e.textContent;e.textContent="✓ Copied!",setTimeout(()=>{e.textContent=t},2e3)},e=>{console.error("Failed to copy:",e),alert("Failed to copy to clipboard")})}function D(){const e={name:"New Tune",key:"",rhythm:"",abc:null,references:[],scores:[],incipit:null};window.tunesData.push(e),window.filteredData.push(e),f(),T();const t=window.filteredData.length-1;p.openWithTune(e,t)}function b(e){const t=window.filteredData[e];if(!confirm(`Delete tune "${t.name}"? This cannot be undone.`))return;const n=window.tunesData.findIndex(e=>e===t);-1!==n&&window.tunesData.splice(n,1),f(),C(),N()}function F(e,t){const n=document.querySelector(`.notes-truncated[data-tune-index="${e}"][data-ref-index="${t}"]`),s=document.querySelector(`.notes-full[data-tune-index="${e}"][data-ref-index="${t}"]`);n&&s&&(n.style.display="none",s.style.display="block")}function E(e,t){const n=document.querySelector(`.notes-truncated[data-tune-index="${e}"][data-ref-index="${t}"]`),s=document.querySelector(`.notes-full[data-tune-index="${e}"][data-ref-index="${t}"]`);n&&s&&(n.style.display="block",s.style.display="none")}function v(){window.tunesData.sort((e,t)=>e.rhythm===t.rhythm?e.name===t.name?0:e.namee.rhythm).filter(e=>e))].sort(),t=[...new Set(tunesData.map(e=>e.key).filter(e=>e))].sort(),n=document.getElementById("rhythmFilter"),s=document.getElementById("keyFilter");n.innerHTML='',e.forEach(e=>{n.innerHTML+=``}),s.innerHTML='',t.forEach(e=>{s.innerHTML+=``})}function k(e){e.abc&&(new c).openWithTune(e)}function T(){const e=document.getElementById("tunesTableBody");0!==window.filteredData.length?(e.innerHTML="",window.filteredData.forEach((t,n)=>{const a=document.createElement("tr");let i="";t.references?.forEach((e,t)=>{let s="";if(e.notes){const a=e.notes.replace(/\n/g,"
").replace(/\[([^\]]+)\]\(([^)]+)\)/g,'$1').replace(/https?:\/\/[^\s<>"']+/g,e=>{try{const{hostname:t,pathname:n,search:s}=new URL(e);return`${t+n+s}`}catch{return e}}),i=e.notes.split("\n");if(i.length>5){const e=i.slice(0,5).join("\n").replace(/\n/g,"
").replace(/\[([^\]]+)\]\(([^)]+)\)/g,'$1');s=`\n \n ${e}\n
\n
\n \n ${a}\n
\n
\n `}else s=`${a}
`}i+=`\n \n ${e.artists?`
${e.artists}
`:""}\n ${e.url?`
`:""}\n ${s}\n
\n `});const r=!!t.abc,o=r?"tune-name has-abc":"tune-name";let d=`incipit${n}`,c=``;a.innerHTML=`\n ${c}\n | \n ${t.key} | \n ${t.rhythm} | \n ${i} | \n \n ${t.scores&&t.scores.length>0?`${t.scores[0].name}`:""}\n | \n `;const l=a.querySelector(".tune-name");r&&l.addEventListener("click",()=>{k(t)}),a.querySelector(".btn-edit").addEventListener("click",()=>{p.openWithTune(window.filteredData[n],n)}),e.appendChild(a),t.incipit&&s.renderAbc(d,t.incipit,{scale:.8,staffwidth:330,paddingtop:1,paddingbottom:1,paddingright:1,paddingleft:1})}),document.getElementById("spCount").innerText=`${filteredData.length}/${tunesData.length}`):e.innerHTML='No tunes found matching your criteria. |
'}function N(){const e=document.getElementById("searchInput").value.toLowerCase(),t=document.getElementById("rhythmFilter").value,n=document.getElementById("keyFilter").value;window.filteredData=window.tunesData.filter(s=>{const a=""===e||s.name.toLowerCase().includes(e)||s.rhythm.toLowerCase().includes(e)||s.key.toLowerCase().includes(e)||s.references.some(t=>t.artists?.toLowerCase().includes(e)||t.notes?.toLowerCase().includes(e)),i=""===t||s.rhythm===t,r=""===n||s.key===n;return a&&i&&r}),T()}document.addEventListener("DOMContentLoaded",function(){(function(){window.addNewTune=D,window.applyFilters=N,window.clearStorage=B,window.collapseNotes=E,window.copyTunesToClipboard=G,window.deleteTune=b,window.emptyTunes=y,window.expandNotes=F,window.populateFilters=C,window.saveTunesToStorage=f,window.sortWithDefaultSort=v,h.on("tuneImported",e=>{tunesData.push(e),f()}),h.on("refreshTable",()=>{v(),C(),N()});let t={saveTunesToStorage:f,populateFilters:C,applyFilters:N,renderTable:T,sortWithDefaultSort:v};A=new o(t),p=new l(t),window.tunesData=[],window.filteredData=[];const s=function(){try{const e=localStorage.getItem(u);if(e){const t=JSON.parse(e);if(Array.isArray(t))return t}}catch(e){console.error("Failed to load from local storage:",e)}return null}();s?(console.log("Loading from local storage"),window.tunesData=s):(console.log("Loading from tunesDataRaw and processing"),window.tunesData=e.tunes.filter(e=>void 0!==e).map(n).sort((e,t)=>e.rhythm===t.rhythm?e.name===t.name?0:e.namet.groups?.toLowerCase().includes(e.toLowerCase())))}if(i.has("q")){let e=i.get("q");e&&(document.getElementById("searchInput").value=e,N(),a=!0)}if(i.has("n")){let e=i.get("n");e&&(r=e,window.filteredData=window.tunesData.filter(e=>e.name?.toLowerCase().includes(r.toLowerCase())),T()),C()}var r;a||N(),1===window.filteredData.length&&window.filteredData[0].abc&&k(window.filteredData[0])})(),document.getElementById("searchInput").addEventListener("input",N),document.getElementById("rhythmFilter").addEventListener("change",N),document.getElementById("keyFilter").addEventListener("change",N),document.querySelectorAll("th.sortable").forEach(e=>{e.addEventListener("click",function(){!function(e){g.column===e?g.direction="asc"===g.direction?"desc":"asc":(g.column=e,g.direction="asc"),window.filteredData.sort((t,n)=>{let s=t[e],a=n[e];return"string"==typeof s&&(s=s.toLowerCase(),a=a.toLowerCase()),sa?"asc"===g.direction?1:-1:0}),document.querySelectorAll("th").forEach(e=>{e.classList.remove("sort-asc","sort-desc")});const t=document.querySelector(`th[data-column="${e}"]`);t?.classList?.add("asc"===g.direction?"sort-asc":"sort-desc"),T()}(this.dataset.column)})}),document.getElementById("addTunesBtn").addEventListener("click",e=>{e.preventDefault(),A.openAddTunes()}),document.getElementById("loadJsonBtn").addEventListener("click",e=>{e.preventDefault(),A.openLoadJson()});const t=document.getElementById("editMenuBtn"),s=t.parentElement;t.addEventListener("click",e=>{e.stopPropagation(),s.classList.toggle("active")}),document.addEventListener("click",e=>{s.contains(e.target)||s.classList.remove("active")}),document.getElementById("addNewTuneBtn")?.addEventListener("click",e=>{e.preventDefault(),s.classList.remove("active"),D()}),document.getElementById("copyTunesBtn")?.addEventListener("click",e=>{e.preventDefault(),s.classList.remove("active"),G()}),document.getElementById("clearStorageBtn")?.addEventListener("click",e=>{e.preventDefault(),s.classList.remove("active"),B()}),document.getElementById("emptyTunesBtn")?.addEventListener("click",e=>{e.preventDefault(),s.classList.remove("active"),y()}),document.getElementById("spLastUpdated").innerHTML=e.lastUpdate,document.getElementById("thesession-import-btn").addEventListener("click",w)})})();