PageSourceSearch

https://inkas.org/assets/design/contact-page.js

js inkas.org collected 2026-10-03 00:51:53 UTC 8,716 bytes, 74 lines download raw bytes

1const topics = [
2  ['general', 'General question', '일반 문의'],
3  ['support', 'Adoptee support', '입양인 지원'],
4  ['programs', 'Programs & visits', '프로그램·방문'],
5  ['participation', 'Volunteering & teaching', '자원봉사·교육'],
6  ['donations', 'Donations & partnerships', '후원·협력'],
7];
8export function renderContact({tr, url, esc, params}) {
9  const selected = topics.some(([id]) => id === params.get('topic')) ? params.get('topic') : 'general';
10  return `<div class="contact-page wrap" data-preserve-text><div class="contact-layout">
11    <header class="contact-heading"><p class="eyebrow">InKAS / ${tr('CONTACT', '문의')}</p><h1>${tr('Contact us.', '문의하기')}</h1><p>${tr('Ask about a program, volunteering, donations or support for Korean adoptees. Tell us how we can help.', '프로그램, 자원봉사, 후원, 해외입양인 지원에 대해 문의해주세요. 필요한 도움을 알려주시면 안내해드리겠습니다.')}</p>
12    <div class="contact-post" aria-hidden="true"><div class="contact-letter"><span>InKAS</span><i></i><i></i><i></i><b>${tr('hello.', '안녕하세요.')}</b></div><div class="contact-envelope-art"></div><span class="contact-postmark">${tr('TO<br>InKAS', '받는 곳<br>InKAS')}</span></div>
13    <div class="contact-direct"><span>${tr('EMAIL', '이메일')}</span><a href="mailto:[email protected]">[email protected] <span aria-hidden="true">↗</span></a><span class="contact-phone-label">${tr('PHONE', '전화')}</span><a href="tel:+82231480258">02-3148-0258 <span aria-hidden="true">↗</span></a><p class="contact-international">${tr('From outside Korea: +82 2-3148-0258', '해외에서 전화: +82 2-3148-0258')}</p></div></header>
14    <form class="contact-paper" id="contact-form">
15      <div class="contact-address"><div><span>${tr('A MESSAGE TO', '받는 곳')}</span><strong>InKAS</strong></div><span class="contact-stamp" aria-hidden="true"><svg viewBox="0 0 32 32"><path d="M6 9h20v15H6zM6 9l10 8L26 9M6 24l7-9m13 9-7-9"/></svg><span>InKAS</span></span></div>
16      <fieldset class="contact-topics"><legend>${tr('What is your message about?', '어떤 문의인가요?')}</legend><div>${topics.map(([id,en,ko]) => `<label><input type="radio" name="topic" value="${id}" ${selected === id ? 'checked' : ''}>
16<span>${tr(en,ko)}</span></label>`).join('')}</div></fieldset>
17      <div class="contact-fields"><label>${tr('Your name', '이름')} <span>${tr('(optional)', '(선택)')}</span><input name="name" autocomplete="name" maxlength="80"></label><label>${tr('Your email', '이메일')}<input name="email" type="email" autocomplete="email" maxlength="254" placeholder="[email protected]" required></label></div>
18      <label class="contact-message-label">${tr('Your message', '문의 내용')}<textarea name="message" rows="6" maxlength="2000" required aria-describedby="contact-message-help contact-availability" placeholder="${esc(tr('How can we help?', '어떤 도움이 필요하신가요?'))}"></textarea></label>
19      <div class="contact-message-meta"><span id="contact-message-help">${tr('Include your preferred language and any relevant dates.', '편한 언어와 관련 일정을 함께 알려주세요.')}</span><span data-contact-count>0 / 2,000</span></div>
20      <p id="contact-availability" class="contact-availability">${tr('Your message will be sent to [email protected]. We can reply to the email address you provide.', '문의는 [email protected]로 전송됩니다. 입력하신 이메일 주소로 답변드릴 수 있습니다.')}</p>
21      <label hidden aria-hidden="true">Website<input name="website" tabindex="-1" autocomplete="off"></label>
22      <div class="contact-send-row"><button class="button" type="submit">${tr('Send message', '문의 보내기')} →</button><button class="contact-copy" type="button" data-contact-copy>${tr('Copy message', '내용 복사')}</button></div>
23      <p class="contact-status" data-contact-status role="status" aria-live="polite" aria-atomic="true"></p>
24      <div class="contact-footnote"><p>${tr('Please leave sensitive adoption records, medical documents and passwords out of this form.', '민감한 입양 기록, 의료 서류, 비밀번호는 이 양식에 입력하지 마세요.')} <a href="${url('privacy')}">${tr('Privacy', '개인정보 안내')}</a></p><a data-contact-email href="mailto:[email protected]">${tr('Open in your email app', '이메일 앱에서 열기')} ↗</a></div>
25    </form></div></div>`;
26}
27
28export function bindContact(root, {tr}) {
29  const form = root.querySelector('#contact-form');
30  if (!form) return;
31  const message = form.elements.message;
32  const status = form.querySelector('[data-contact-status]');
33  const note = () => {
34    const values = new FormData(form);
35    const topic = topics.find(([id]) => id === values.get('topic'));
36    return {subject: `InKAS — ${tr(topic[1], topic[2])}`, body: `${tr('Name', '이름')}: ${values.get('name').trim()}\n${tr('Email', '이메일')}: ${values.get('email').trim()}\n\n${message.value.trim()}`};
37  };
38  const update = () => {
39    form.querySelector('[data-contact-count]').textContent = `${message.value.length.toLocaleString()} / 2,000`;
40    const {subject, body} = note();
41    form.querySelector('[data-contact-email]').href = `mailto:[email protected]?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
42  };
43  form.addEventListener('input', update);
44  form.addEventListener('change', update);
45  let sending=false;
46  form.addEventListener('submit', async event => {
47    event.preventDefault();
48    if(sending||!form.reportValidity())return;
49    if(!message.value.trim()){message.setCustomValidity(tr('Enter a message.','문의 내용을 입력해주세요.'));message.reportValidity();return;}
50    sending=true;const submit=form.querySelector('[type=submit]'),label=submit.textContent;
51    const payload=Object.fromEntries(new FormData(form));
52    submit.disabled=true;form.setAttribute('aria-busy','true');submit.textContent=tr('Sending…','전송 중…');status.dataset.state='';status.textContent='';
53    try{
54      const response=await fetch('/api/contact',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),signal:AbortSignal.timeout(25000)});
55      const result=await response.json();
56      if(!response.ok||!result.ok)throw new Error(response.status===429?tr('Too many messages. Please try again later.','문의가 너무 많습니다. 잠시 후 다시 시도해주세요.'):tr('Your message could not be sent. Your text is still here; try again or email [email protected].','전송하지 못했습니다. 작성한 내용은 그대로 있습니다. 다시 시도하거나 [email protected]로 이메일을 보내주세요.'));
57      status.dataset.state='success';status.textContent=result.preview?tr('Preview test received. No email was sent.','미리보기 테스트가 접수되었습니다. 실제 이메일은 전송되지 않았습니다.'):tr('Your message has been sent to InKAS.','InKAS로 문의를 전송했습니다.');
58      // Preserve any edits made while the request was in flight.
59      if(['name','email','message','topic'].every(key=>form.elements[key].value===payload[key])){form.reset();
59update();}
60    }catch(error){status.dataset.state='error';status.textContent=error.name==='TimeoutError'?tr('Sending timed out. Delivery is unconfirmed; your message is still here.','전송 확인 시간이 초과되었습니다. 전송 여부를 확인할 수 없으며 작성한 내용은 보존됩니다.'):error.name==='TypeError'?tr('Could not connect. Your message is still here. Please try again.','연결하지 못했습니다. 작성한 내용은 보존됩니다. 다시 시도해주세요.'):error.message;}
61    finally{sending=false;submit.disabled=false;submit.textContent=label;form.removeAttribute('aria-busy');}
62  });
63  message.addEventListener('input',()=>message.setCustomValidity(''));
64  form.querySelector('[data-contact-copy]').addEventListener('click', async () => {
65    const {subject, body} = note();
66    try {
67      await navigator.clipboard.writeText(`${subject}\n\n${body}`);
68      status.textContent = tr('Message copied. Paste it into an email to [email protected]. Nothing has been sent yet.', '내용을 복사했습니다. [email protected]로 보낼 이메일에 붙여넣어주세요. 아직 전송되지는 않았습니다.');
69    } catch {
70      status.textContent = tr('Copying is unavailable in this browser. Select and copy your message, or use the email app link.', '이 브라우저에서는 자동 복사를 사용할 수 없습니다. 내용을 직접 선택해 복사하거나 이메일 앱 링크를 이용해주세요.');
71    }
72  });
73  update();
74}

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.