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()
|
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!(
|
format!(
|
||||||
r#"<span class="guestbook-prompt">{prompt}</span>
|
r#"<span class="guestbook-prompt">{prompt}</span>
|
||||||
<form class="guestbook-form" method="post" action="/submit" accept-charset="UTF-8">
|
<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>
|
<label class="guestbook-label">{label_message}</label>
|
||||||
<textarea class="guestbook-textarea" name="message" style="width:{tw}px;height:{th}px" required></textarea>
|
<textarea class="guestbook-textarea" name="message" style="width:{tw}px;height:{th}px" required></textarea>
|
||||||
{captcha_section}
|
{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>"#,
|
</form>"#,
|
||||||
prompt = config.form_prompt,
|
prompt = config.form_prompt,
|
||||||
label_name = config.label_name,
|
label_name = config.label_name,
|
||||||
|
|
@ -128,6 +192,7 @@ pub fn render_form(config: &Config) -> String {
|
||||||
th = config.textarea_height,
|
th = config.textarea_height,
|
||||||
captcha_section = captcha_section,
|
captcha_section = captcha_section,
|
||||||
drawing_section = drawing_section,
|
drawing_section = drawing_section,
|
||||||
|
voice_note_section = voice_note_section,
|
||||||
button = config.button_text,
|
button = config.button_text,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -527,4 +592,22 @@ mod tests {
|
||||||
assert!(html.contains("back"));
|
assert!(html.contains("back"));
|
||||||
assert!(html.contains("<style>"));
|
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