<!DOCTYPE html>
<!-----------------------------------------------------------------------------
     Copyright 2014-2025 Jesper Larsson

     This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>

     Klipspringer is free software: you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the Free
     Software Foundation, either version 3 of the License, or (at your option)
     any later version.

     Klipspringer is distributed in the hope that it will be useful, but
     WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
     for more details.

     You should have received a copy of the GNU General Public License along
     with Klipspringer. If not, see <https://www.gnu.org/licenses/>.
   --------------------------------------------------------------------------->

<html>
    <head>
        <meta charset="UTF-8">
        <style>$(klipstyle.css:file)</style>
        <title>$(path)</title>
        <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
        $(klipicons.html:file)
    </head>

    <body>
        <div class="recording-menus">
            Download format:
            <select id="format-select" class="recording-menu">
                <option value="raw">Raw PCM</option>
                <option value="wav">WAV PCM</option>
                <option value="flac" id="flac-option">FLAC</option>
                <option value="ogg" id="vorbis-option">Ogg Vorbis (.9 quality)</option>
                <option value="mp3" id="mp3-option">MP3 (320&nbsp;kbit/s)</option>
            </select>
            <button class="recording-menu" id="clear-cache">Clear cache</button>
        </div>
        <table id="recs"></table>

        <dialog id="trash-alert">
            <div id="trash-alert-explain" class="dialog-message"></div>
            <div>
                <input type="button" class="click-choose dialog-button" id="trash-alert-hide" value="OK" />
            </div>
        </dialog>

        <dialog id="dlod-alert">
            <div class="dialog-message">Download begins when <span id="dlod-alert-format"></span> file is ready. The file is cached for faster access.</div>
            <div>
                <input type="button" class="click-choose dialog-button" id="dlod-alert-hide" value="OK" />
            </div>
        </dialog>

        <script>
         'use strict';
         $(sendcmd.js:file)

         if (!$(flac_supported)) { document.getElementById("flac-option").remove() }
         if (!$(vorbis_supported)) { document.getElementById("vorbis-option").remove() }
         if (!$(lame_supported)) { document.getElementById("mp3-option").remove() }

         const touchdevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
         const pressevent = touchdevice ? "click" : "mousedown";
         const normalpress = touchdevice ? (ev) => true : (ev) => ev.buttons === 1;

         // Button animation.
         const blinkButton = (elt) => {
             elt.classList.add("blinking");
             clearTimeout(elt.tout);
             elt.tout = setTimeout(() => {
                 elt.classList.remove("blinking");
             }, 150);
         }

         // Modify href in download link before action, and show message.
         const downloadNote = (ev) => {
             const a = ev.currentTarget;
             blinkButton(a.firstElementChild.firstElementChild);
             const format = formatSelectElt.value;
             if (format.toLowerCase() !== 'raw') {
                 dlodAlertFormatElt.innerHTML = format;
                 dlodAlertDialogElt.showModal();
             }
             const h = a.href;
             const m = /&format=([a-z0-9]*)/d.exec(h);
             if (m) {
                 const [i, j] = m.indices[1];
                 a.href = h.slice(0, i)+format+h.slice(j);
             } else {
                 a.href = h+"&format="+format;
             }
         }

         // Blink play button before action.
         const blinkPlay = (ev) => {
             const a = ev.currentTarget;
             blinkButton(a.firstElementChild.firstElementChild);
         }

         let dontask = false;
         const recs = $(recs);
         const formatSelectElt = document.getElementById("format-select");
         formatSelectElt.value = $(default_download_format);
         const recsElt = document.getElementById("recs");
         const playElt = document.createElement("a");
         playElt.innerHTML = $(play_svg);
         const dlodElt = document.createElement("a");
         dlodElt.innerHTML = $(download_svg);
         const remvElt = document.createElement("a");
         remvElt.innerHTML = $(remove_svg);

         document.getElementById("clear-cache").addEventListener(pressevent, (ev) => {
             if (normalpress(ev)) {
                 sendUrl("/recording?cmd=clearcache");
             }
         });

         const confirmDialogElt = document.createElement("dialog");
         confirmDialogElt.innerHTML = '<div class="dialog-message"></div>'+
                                      '<div class="dialog-content">'+
                                      '    <label>'+
                                      '        Don&#x2019;t ask again'+
                                      '        <input type="checkbox" />'+
                                      '    </label>                '+
                                      '</div>            '+
                                      '<div>'+
                                      '    <input type="button" class="click-choose dialog-button" value="OK" />'+
                                      '    <input type="button" class="click-choose dialog-button" value="Cancel" />'+
                                      '</div>';

         const trashAlertDialogElt = document.getElementById("trash-alert");
         const trashAlertExplainElt = document.getElementById("trash-alert-explain");
         document.getElementById("trash-alert-hide").addEventListener(pressevent, (ev) => {
             if (normalpress(ev)) { trashAlertDialogElt.close() }
         });

         const dlodAlertDialogElt = document.getElementById("dlod-alert");
         const dlodAlertFormatElt = document.getElementById("dlod-alert-format");
         document.getElementById("dlod-alert-hide").addEventListener(pressevent, (ev) => {
             if (normalpress(ev)) { dlodAlertDialogElt.close() }
         });

         const trash = (tr, file) => sendUrl("/recording?cmd=unlink&file="+file).then(ans => {
             if (ans.ok) { tr.remove() }
             else {
                 trashAlertExplainElt.innerHTML = "Error deleting recording"+(ans.err ? ": "+ans.err : "");
                 trashAlertDialogElt.showModal();
             }
         });

         for (const r of recs) {
             const tr = document.createElement("tr");
             recsElt.appendChild(tr);

             let td = document.createElement("td");
             tr.appendChild(td);

             const play = playElt.cloneNode(true);
             td.appendChild(play);
             play.setAttribute("href", "/choice?source=$(source)&path="+encodeURIComponent(r.file));
             play.addEventListener("click", blinkPlay);

             const dlod = dlodElt.cloneNode(true);
             td.appendChild(dlod);
             dlod.setAttribute("href", "/recording?cmd=download&file="+encodeURIComponent(r.file));
             dlod.setAttribute("download", "");
             dlod.addEventListener("click", downloadNote);

             const remv = remvElt.cloneNode(true);
             td.appendChild(remv);
             const dia = confirmDialogElt.cloneNode(true);
             remv.appendChild(dia);
             dia.children[0].innerHTML = "Deleting recording from "+r.date;
             dia.children[2].children[0].addEventListener(pressevent, (ev) => {
                 if (normalpress(ev)) {
                     dontask = dontask || dia.children[1].children[0].children[0].checked;
                     dia.close();
                     trash(tr, r.file);
                 }
             });
             dia.children[2].children[1].addEventListener(pressevent, (ev) => {
                 if (normalpress(ev)) { dia.close(); }
             });
             remv.addEventListener("click", (ev) => {
                 blinkButton(ev.currentTarget.firstElementChild.firstElementChild);
                 if (dontask) { trash(tr, r.file) }
                 else { dia.showModal() }
             });

             td = document.createElement("td");
             tr.appendChild(td);
             td.innerHTML = r.date;

             td = document.createElement("td");
             tr.appendChild(td);
             let t = Math.round(r.size / (r.rate*Math.ceil((r.bits+r.pad)/8) * r.channels) * 1000);
             if (t >= 3600000) {                // has hours
                 const h = Math.trunc(t/3600000);
                 t %= 3600000;
                 const m = Math.trunc(t/60000);
                 t %= 60000;
                 const s = t/1000;
                 td.innerHTML = "("+h+":"+(m < 10 ? "0" : "")+m+":"+(s < 10 ? "0" : "")+s+")";
             } else if (t > 60000) {
                 const m = Math.trunc(t/60000);
                 t %= 60000;
                 const s = t/1000;
                 td.innerHTML = "("+m+":"+(s < 10 ? "0" : "")+s+")";
             } else {
                 const s = t/1000;
                 td.innerHTML = "("+s+")";
             }
             td = document.createElement("td");
             tr.appendChild(td);
             td.innerHTML = r.bits+"-bit "+(r.rate >= 1000 ? r.rate/1000+"&thinsp;kHz" : r.rate+"&thinsp;Hz")+(r.channels === 2 ? " stereo" : (r.channels === 1 ? " mono" : r.channels+" channels"));
         }
        </script>
    </body>
</html>

Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home