1$('#sql-input').on('keydown', function(event) { 2 if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { 3 event.preventDefault(); 4 formatSQL(event.shiftKey); 5 } 6 }); 7 8document.getElementById('sql-input').focus(); 9 10toastr.options = {"positionClass": "toast-bottom-right"}; 11 12// Show merch banner occasionally after formatting 13let formatCount = 0; 14function showMerchBanner() { 15 formatCount++; 16 // Show banner every 5th format, but not immediately 17 if (formatCount % 5 === 0 && formatCount > 1) { 18 // Alternate between merch and donation 19 const showDonation = Math.random() < 0.5; 20 21 const banner = showDonation ? ` 22 <div id="merch-success-banner" class="notification is-light has-text-centered mt-3" style="background: linear-gradient(135deg, #f5f5f5 0%, #fff 100%); border-left: 4px solid #f80; animation: slideDown 0.3s ease;"> 23 <button class="delete" onclick="$('#merch-success-banner').fadeOut()"></button> 24 <span class="material-icons-outlined" style="color: #f80; vertical-align: text-bottom; margin-right: 5px;">favorite</span> 25 <strong>SQL looks great!</strong> Love this tool? <a href="https://merch.sqlformat.org/pages/donate" target="_blank" style="color: #f80; text-decoration: underline;"><strong>Support with a donation</strong></a> ð 26 </div> 27 ` : ` 28 <div id="merch-success-banner" class="notification is-light has-text-centered mt-3" style="background: linear-gradient(135deg, #f5f5f5 0%, #fff 100%); border-left: 4px solid #f80; animation: slideDown 0.3s ease;"> 29 <button class="delete" onclick="$('#merch-success-banner').fadeOut()"></button> 30 <span class="material-icons-outlined" style="color: #f80; vertical-align: text-bottom; margin-right: 5px;">local_cafe</span> 31 <strong>SQL looks great!</strong> Like this tool? Check out our <a href="https://merch.sqlformat.org" target="_blank" style="color: #f80; text-decoration: underline;"><strong>"I Speak SQL" merch</strong></a> â 32 </div> 33 `; 34 35 $(banner).prependTo('#result-list').hide().fadeIn(500); 36 37 // Auto-hide after 8 seconds 38 setTimeout(() => { 39 $('#merch-success-banner').fadeOut(1000); 40 }, 8000); 41 } 42} 43 44document.addEventListener('DOMContentLoaded', (event) => { 45$('#format-btn').on('click', (event) => { 46 event.preventDefault(); 47 formatSQL(); 48}); 49 50$('#btn-clear-results').on('click', (event) => { 51 $('#result-list').empty(); 52 $('#btn-clear-results').attr('disabled', ''); 53}); 54 55document.getElementById('sqlfile').addEventListener('change', function(event) { 56 const file = event.target.files[0]; 57 if (file) { 58 const reader = new FileReader(); 59 reader.onload = function(e) { 60 const content = e.target.result; 61 document.getElementById('sql-input').textContent = content; 62 formatSQL(); 63 }; 64 reader.readAsText(file); 65 } 66}); 67}); 68 69function formatSQL(copyToClipboard) { 70 const sqlInput = document.getElementById('sql-input').value; 71 const removeComments = $('#opt-comments').prop('checked'); 72 const compact = $('#opt-compact').prop('checked'); 73 let indent = $('#opt-indent').val(); 74 indent = indent == '' ? null : indent; 75 let kwCase = $('#opt-keywords').val(); 76 kwCase = kwCase != 'none' ? kwCase : null; 77 let idCase = $('#opt-identifiers').val(); 78 idCase = idCase != 'none' ? idCase : null; 79 const outputFormat = $('#opt-output').val(); 80 pyodideWorker.postMessage({ type: 'format', 81 sql: sqlInput, remove_comments: removeComments, compact: compact, 82 indent: indent, keyword_case: kwCase, 83 identifier_case: idCase, output_format: outputFormat, 84 copyToClipboard: copyToClipboard }); 85 dataLayer.push({'event': 'sql_formatted'}); 86} 87 88function addResult(sql) { 89 let div = $('<div>').addClass('box').addClass('flash-bg'); 90 let actionDiv = $('<div>').addClass('has-text-right').addClass('is-size-7').appendTo(div); 91 $('<a onClick="copyToClipboardLink(this);">Copy to clipboard</a> | <a onClick="scrollToEditor();">Top</a>').appendTo(actionDiv);
92 let code = $('<pre>').addClass('language-sql').addClass('flash-bg').appendTo(div); 93 $('<textarea>').addClass('is-hidden').val(sql).appendTo(div); 94 code.html(sql); 95 div.prependTo('#result-list'); 96 hljs.highlightBlock(code[0]); 97 div[0].scrollIntoView({behavior: "smooth"}); 98 $('#btn-clear-results').removeAttr('disabled'); 99} 100 101 102function copyToClipboardLink(elem) { 103 const content = $(elem).parent().parent().find('textarea').val(); 104 copyToClipboard(content); 105} 106 107function copyToClipboard(content) { 108 if (navigator.clipboard) { 109 navigator.clipboard.writeText(content).then(function() { 110 toastr.success('Copied to clipboard'); 111 }, function(err) { 112 toastr.error("Failed to copy to clipboard"); 113 }); 114 } else { 115 toastr.error("Sorry, clipbpoard access is not supported in your browser") 116 } 117} 118 119function scrollToEditor() { 120 $('body')[0].scrollIntoView({behavior: "smooth"}); 121 $('#sql-input').focus() 122} 123 124const pyodideWorker = new Worker(WORKER_URL); 125pyodideWorker.onmessage = function(event) { 126 if (event.data.type === 'version') { 127 document.getElementById("format-btn").classList.remove("is-loading"); 128 document.getElementById("info-loading").textContent = event.data.version + ', Formatter engine loaded.'; 129 setTimeout(function() { 130 document.getElementById("info-loading").textContent = event.data.version; 131 }, 3000); 132 } else if (event.data.type === 'error') { 133 document.getElementById("info-loading").textContent = event.data.message; 134 document.getElementById("info-loading").classList.add("has-text-danger"); 135 } else if (event.data.type === 'formatted') { 136 addResult(event.data.formattedSQL); 137 if (event.data.copyToClipboard) { 138 copyToClipboard(event.data.formattedSQL); 139 } 140 // Show merch banner occasionally 141 showMerchBanner(); 142 } 143};
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.