feat: voice note recorder in form
This commit is contained in:
parent
ab6dd05b6e
commit
3e64ff4857
1 changed files with 84 additions and 1 deletions
|
|
@ -109,6 +109,70 @@ pub fn render_form(config: &Config) -> String {
|
|||
String::new()
|
||||
};
|
||||
|
||||
let voice_note_section = if config.enable_voice_notes {
|
||||
format!(
|
||||
r##"<span class="guestbook-voice-wrap"><span class="guestbook-voice-inline"><a href="#" class="guestbook-voice-record">add a voice note</a> <span class="guestbook-voice-timer"></span></span><span class="guestbook-voice-playback"></span></span><input type="hidden" name="voice_note"><script>(function(){{
|
||||
var maxDur={max_dur};
|
||||
var inl=document.querySelector('.guestbook-voice-inline'),
|
||||
pb=document.querySelector('.guestbook-voice-playback'),
|
||||
hid=document.querySelector('[name=voice_note]'),
|
||||
rec=null,chunks=[],iv=null,st=0;
|
||||
function fmt(s){{var m=Math.floor(s/60),sec=s%60;return m+':'+(sec<10?'0':'')+sec}}
|
||||
function setInit(){{
|
||||
if(rec&&rec.state==='recording'){{rec.stop();rec.stream.getTracks().forEach(function(t){{t.stop()}})}}
|
||||
rec=null;chunks=[];clearInterval(iv);iv=null;pb.innerHTML='';hid.value='';
|
||||
inl.innerHTML='';
|
||||
var a=document.createElement('a');a.href='#';a.textContent='add a voice note';
|
||||
a.addEventListener('click',function(e){{e.preventDefault();startRec()}});
|
||||
inl.appendChild(a);
|
||||
}}
|
||||
function setRec(){{
|
||||
inl.innerHTML='';
|
||||
var a=document.createElement('a');a.href='#';a.className='guestbook-voice-record recording';
|
||||
a.textContent='stop recording';
|
||||
a.addEventListener('click',function(e){{e.preventDefault();rec.stop();rec.stream.getTracks().forEach(function(t){{t.stop()}})}});
|
||||
inl.appendChild(a);inl.appendChild(document.createTextNode(' '));
|
||||
var t=document.createElement('span');t.className='guestbook-voice-timer';inl.appendChild(t);
|
||||
st=Date.now();t.textContent=fmt(0)+' / '+fmt(maxDur);
|
||||
iv=setInterval(function(){{
|
||||
var el=Math.floor((Date.now()-st)/1000);t.textContent=fmt(el)+' / '+fmt(maxDur);
|
||||
if(el>=maxDur){{rec.stop();rec.stream.getTracks().forEach(function(t){{t.stop()}})}}
|
||||
}},250);
|
||||
}}
|
||||
function setResult(){{
|
||||
clearInterval(iv);iv=null;
|
||||
var blob=new Blob(chunks,{{type:'audio/webm;codecs=opus'}});
|
||||
inl.innerHTML='';
|
||||
var re=document.createElement('a');re.href='#';re.textContent='re-record';
|
||||
re.addEventListener('click',function(e){{e.preventDefault();setInit();startRec()}});
|
||||
var disc=document.createElement('a');disc.href='#';disc.textContent='discard';
|
||||
disc.addEventListener('click',function(e){{e.preventDefault();setInit()}});
|
||||
inl.appendChild(re);inl.appendChild(document.createTextNode(' | '));inl.appendChild(disc);
|
||||
var url=URL.createObjectURL(blob);
|
||||
var au=document.createElement('audio');au.controls=true;au.preload='metadata';au.src=url;
|
||||
pb.innerHTML='';pb.appendChild(au);
|
||||
var rd=new FileReader();rd.onload=function(){{hid.value=rd.result}};rd.readAsDataURL(blob);
|
||||
}}
|
||||
function startRec(){{
|
||||
chunks=[];hid.value='';pb.innerHTML='';
|
||||
navigator.mediaDevices.getUserMedia({{audio:true}}).then(function(stream){{
|
||||
rec=new MediaRecorder(stream,{{mimeType:'audio/webm;codecs=opus'}});
|
||||
rec.ondataavailable=function(e){{if(e.data.size>0)chunks.push(e.data)}};
|
||||
rec.onstop=function(){{setResult()}};
|
||||
rec.start();setRec();
|
||||
}}).catch(function(){{
|
||||
inl.querySelector('a').textContent='add a voice note';
|
||||
inl.appendChild(document.createTextNode(' (mic denied)'));
|
||||
}});
|
||||
}}
|
||||
inl.querySelector('a').addEventListener('click',function(e){{e.preventDefault();startRec()}});
|
||||
}})();</script>"##,
|
||||
max_dur = config.voice_note_max_duration,
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"<span class="guestbook-prompt">{prompt}</span>
|
||||
<form class="guestbook-form" method="post" action="/submit" accept-charset="UTF-8">
|
||||
|
|
@ -118,7 +182,7 @@ pub fn render_form(config: &Config) -> String {
|
|||
<label class="guestbook-label">{label_message}</label>
|
||||
<textarea class="guestbook-textarea" name="message" style="width:{tw}px;height:{th}px" required></textarea>
|
||||
{captcha_section}
|
||||
{drawing_section}<input name="url" style="display:none" tabindex="-1" autocomplete="off"><button class="guestbook-button" type="submit">{button}</button>
|
||||
{drawing_section}{voice_note_section}<input name="url" style="display:none" tabindex="-1" autocomplete="off"><button class="guestbook-button" type="submit">{button}</button>
|
||||
</form>"#,
|
||||
prompt = config.form_prompt,
|
||||
label_name = config.label_name,
|
||||
|
|
@ -128,6 +192,7 @@ pub fn render_form(config: &Config) -> String {
|
|||
th = config.textarea_height,
|
||||
captcha_section = captcha_section,
|
||||
drawing_section = drawing_section,
|
||||
voice_note_section = voice_note_section,
|
||||
button = config.button_text,
|
||||
)
|
||||
}
|
||||
|
|
@ -527,4 +592,22 @@ mod tests {
|
|||
assert!(html.contains("back"));
|
||||
assert!(html.contains("<style>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_form_shows_voice_note_when_enabled() {
|
||||
let mut config = test_config();
|
||||
config.enable_voice_notes = true;
|
||||
let form = render_form(&config);
|
||||
assert!(form.contains("add a voice note"));
|
||||
assert!(form.contains("guestbook-voice-record"));
|
||||
assert!(form.contains("name=\"voice_note\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_form_hides_voice_note_when_disabled() {
|
||||
let config = test_config();
|
||||
let form = render_form(&config);
|
||||
assert!(!form.contains("add a voice note"));
|
||||
assert!(!form.contains("name=\"voice_note\""));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue