Skip to Content
WebJavaScriptWeb APIs18. Clipboard·Notification·Web Share 통합

이번 편의 결과물: 메모 목록 항목에 복사·공유 버튼을 추가하고, 툴바에서 알림 권한을 요청해 자동 저장이 끝날 때마다 시스템 알림을 띄웁니다. · 다루는 개념: navigator.clipboard.writeText, Notification 권한 요청·표시, navigator.share

이 편에서 만드는 파일

notes-app/ ├── index.html (~ 알림 켜기 버튼 추가) └── src/ ├── notes.js (~ buildNoteText 추가) ├── render.js (~ 복사·공유 버튼 추가) └── main.js (~ 클립보드·알림·공유 이벤트 연결)

개념 정리

Clipboard API

navigator.clipboard.writeText(text)는 문자열을 시스템 클립보드에 씁니다. Promise를 반환하는 비동기 API이고, HTTPS나 localhost 같은 보안 컨텍스트(secure context)에서만 동작합니다.

Notification API

Notification은 운영체제 알림 센터에 메시지를 띄웁니다. 사용 전에 권한을 요청해야 합니다.

Notification.permission의미
default아직 물어보지 않음
granted사용자가 허용함
denied사용자가 거부함(다시 요청해도 브라우저가 자동으로 거부)

Notification.requestPermission()Promise<string>을 반환합니다. denied 상태에서는 코드로 다시 권한을 얻을 수 없고, 사용자가 브라우저 설정에서 직접 바꿔야 합니다.

Web Share API

navigator.share(data)는 운영체제의 공유 시트(메일·메신저 목록)를 띄웁니다. 모든 브라우저가 지원하지는 않으므로 navigator.canShare로 먼저 확인하고, 지원하지 않으면 클립보드 복사 같은 대체 동작을 준비합니다.

속성용도
data.title공유 항목 제목
data.text본문 텍스트

실습

1. src/notes.js에 공유용 텍스트 생성 함수 추가

// src/notes.js 맨 아래에 추가 export function buildNoteText(note) { return `${note.title}\n${note.body}`; }

2. src/render.js에 복사·공유 버튼 추가

17편의 createNoteItem은 그대로 두고, 버튼 두 개만 덧붙입니다.

// src/render.js (createNoteItem 함수만 수정) export function createNoteItem(note) { const li = document.createElement('li'); li.className = 'note-item'; li.dataset.id = note.id; const title = document.createElement('strong'); title.textContent = note.title; const tags = document.createElement('span'); tags.className = 'note-tags'; tags.textContent = note.tags.join(', '); const copyBtn = document.createElement('button'); copyBtn.type = 'button'; copyBtn.dataset.action = 'copy'; copyBtn.textContent = '복사'; const shareBtn = document.createElement('button'); shareBtn.type = 'button'; shareBtn.dataset.action = 'share'; shareBtn.textContent = '공유'; li.append(title, tags, copyBtn, shareBtn); if (note.attachment) { li.append(createAttachmentThumb(note.attachment)); } return li; }

3. index.html에 알림 켜기 버튼 추가

<!-- index.html의 #toolbar(10편) 안에 추가 --> <button id="enable-notifications-btn" type="button">알림 켜기</button>

4. src/main.js에서 클립보드·알림·공유 연결

// src/main.js (아래만 추가, 04~17편 초기화는 그대로 유지) import { buildNoteText } from './notes.js'; import { getAllNotes, putNote } from './storage.js'; const noteListEl = document.querySelector('#note-list'); const enableNotifBtn = document.querySelector('#enable-notifications-btn'); enableNotifBtn.addEventListener('click', async () => { const permission = await Notification.requestPermission(); enableNotifBtn.textContent = permission === 'granted' ? '알림 켜짐' : '알림 거부됨'; }); function notifySaved(note) { if (Notification.permission !== 'granted') return; new Notification('메모 저장됨', { body: note.title }); } noteListEl.addEventListener('click', async (event) => { const copyButton = event.target.closest('button[data-action="copy"]'); if (copyButton) { const id = copyButton.closest('.note-item').dataset.id; const notes = await getAllNotes(); const note = notes.find((item) => item.id === id); await navigator.clipboard.writeText(buildNoteText(note)); copyButton.textContent = '복사됨'; setTimeout(() => { copyButton.textContent = '복사'; }, 1500); return; } const shareButton = event.target.closest('button[data-action="share"]'); if (shareButton) { const id = shareButton.closest('.note-item').dataset.id; const notes = await getAllNotes(); const note = notes.find((item) => item.id === id); const shareData = { title: note.title, text: note.body }; if (navigator.canShare && navigator.canShare(shareData)) { await navigator.share(shareData); } else { await navigator.clipboard.writeText(buildNoteText(note)); } } });

14편의 runSave 함수 안, animateSavedBadge() 호출 다음 줄에 notifySaved 호출을 추가합니다.

// src/main.js의 runSave 함수 안 (14편 코드에 한 줄만 추가) async function runSave() { if (!editingId) return; const body = getEditorText(); const notes = await getAllNotes(); const note = notes.find((item) => item.id === editingId); await putNote({ ...note, body, updatedAt: new Date().toISOString() }); saveTimer = null; animateSavedBadge(); notifySaved(note); }

5. 실행

npm run dev

확인

  • “알림 켜기”를 누르면 브라우저 권한 팝업이 뜨고, 허용하면 버튼 글자가 “알림 켜짐”으로 바뀝니다.
  • 메모를 편집하고 자동 저장이 끝나면 시스템 알림 센터에 “메모 저장됨” 알림이 뜹니다(권한을 허용한 경우).
  • 목록 항목의 “복사”를 누르면 버튼 글자가 잠깐 “복사됨”으로 바뀌고, 다른 앱에 붙여넣으면 제목과 내용이 들어갑니다.
  • 모바일 브라우저나 navigator.share를 지원하는 환경에서 “공유”를 누르면 운영체제 공유 시트가 뜹니다.

직접 해보기

  1. 클립보드 복사가 실패했을 때(권한 거부 등) try/catch로 잡아 alert를 띄우는 코드를 추가해봅니다.
  2. navigator.share가 없는 브라우저에서는 “공유” 버튼 자체를 숨기도록 createNoteItem을 수정해봅니다.

정답 보기

// main.js의 copy 처리 부분 수정 try { await navigator.clipboard.writeText(buildNoteText(note)); copyButton.textContent = '복사됨'; } catch (error) { alert('클립보드 복사 권한이 없습니다.'); }
// render.js의 createNoteItem 안, li.append 이전 if (navigator.share) { li.append(shareBtn); }

자주 하는 실수

증상원인고치는 법
navigator.clipboardundefinedHTTP(비보안) 환경에서 실행localhost나 HTTPS에서 테스트
알림 버튼을 여러 번 눌러도 팝업이 다시 안 뜸이전에 denied를 선택함브라우저 사이트 설정에서 알림 권한을 초기화
navigator.share is not a function데스크톱 브라우저 일부는 Web Share 미지원호출 전에 if (navigator.share)로 지원 여부 확인
공유 시트는 떴는데 아무것도 안 보냄사용자가 시트에서 취소함(정상 동작)share()가 던지는 AbortError를 무시하도록 처리

확인 문제

문제 14지선다
navigator.clipboard.writeText가 정상 동작하기 위한 조건은?
문제 24지선다
Notification.permission이 denied 상태일 때 다시 requestPermission을 호출하면?
문제 34지선다
navigator.share를 호출하기 전에 navigator.canShare로 먼저 확인하는 이유는?
문제 44지선다
runSave 함수 끝에서 notifySaved(note)를 호출하는 코드의 동작으로 옳은 것은?

참고 자료

Last updated on