SNS에 하이라이트 공유 기능 추가하기
안녕하세요? LINER 팀에서 브라우저 익스텐션 개발을 맡고 있는 마크입니다!
확장 프로그램이라고도 불리는 ‘브라우저 익스텐션’은 웹 브라우저의 기존 동작을 변경하거나 완전히 새로운 기능을 추가할 수 있는 무궁무진한 웹 브라우저용 프로그램입니다. LINER 팀의 대표적인 프로덕트도 브라우저 익스텐션이에요.
👉 브라우저 익스텐션에 대해 좀 더 알고 싶다면?
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions
입사하고 한 달 정도 지날 무렵, 처음으로 브라우저 익스텐션 개발을 접했는데요. <script>
태그를 통해 Javascript를 삽입해 자기 자신의 DOM을 선택하고 조작하는 보통 웹 개발과 다르게, 브라우저 익스텐션은 Javascript로 다른 사이트의 DOM을 선택하고 조작할 수 있다는 점에서 제게 매력적으로 다가왔습니다.
여태까지 기억나는 BE 개발은 많지만, 오늘은 그 중 제가 삽질(?)을 가장 많이 했던 Layer on Top of SNS 기능을 개발한 이야기를 해 보겠습니다. Layer on Top of SNS는 Facebook, LinkedIn, Twitter와 같은 소셜 네트워크에서 LINER로 하이라이트 한 내용을 공유하도록 하는 기능이에요.
브라우저 내 윈도우 간 어떻게 소통할까요?
시작하기 전에 해야 할 일이 무엇이 있을 지 큰 단위로 정리해 보았어요.
- SNS에 ‘하이라이트 공유’ 버튼 끼워넣기
- 공유 가능한 ‘내 하이라이트’ 목록을 팝업으로 띄우기
- 팝업에서 하이라이트 선택 시, 이를 SNS의 텍스트 에디터로 옮기기
1번은 루크가 어느 정도 미리 구현하셨고, 2번은 기존 홈페이지의 모바일 뷰에 isSharedHighlight이라는 쿼리스트링이 붙을 시, 공유 가능한 ‘내 하이라이트’ 목록이 뜨도록 코드를 수정하여 쉽게 구현할 수 있었습니다.
문제는 3번이었는데요. 팝업에 있는 ‘내 하이라이트’를 다른 윈도우에 있는 SNS의 텍스트 에디터에 옮길 수 있는 방법이 도저히 생각나지 않았습니다. 고민과 삽질을 반복하던 중, 저는 CEO이자 BE 개발자이신 루크에게 페어 프로그래밍을 요청드렸고, 루크는 감사하게도 흔쾌히 수락해주셨습니다😂
방법은 의외로 어렵지 않았습니다! 바로 Browser Extension 전용 Javascript API 중 하나인 browser.tabs.sendMessage()
를 사용하는 것이었어요.
우선 루크가 browser.tabs.sendMessage()
를 이용해 미리 구현한 messageToNative()
함수를 이용해, 팝업에서 선택한 하이라이트를 background
라는 공간으로 보냅니다. 이후 background
에서 다시 messageTo()
라는 함수를 이용해 SNS가 있는 페이지로 하이라이트를 다시 보내면, 결과적으로 팝업에서 SNS가 있는 윈도우로 메시지를 보내준 것과 같다고 볼 수 있습니다. 참고로 background
는 브라우저의 모든 창이 공유할 수 있는 정보를 담는 공간이라고 생각하시면 됩니다.
👉 Browser Extension 전용 Javascript API에 대해 더 알고 싶다면?
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API
루크와의 페어 프로그래밍은 단순한 지식 전달 뿐만 아니라, 한 줄 한 줄 코드를 작성하면서 왜 이렇게 짰는지 사고를 공유할 수 있어서 기억에 더 남고 의미 있는 시간이었습니다. 저는 문제에 봉착했을 때 혼자서 많이 고민하는 타입이라 효율이 떨어지는 일이 종종 있었는데, 페어 프로그래밍 덕분에 잘 해결할 수 있었고, 좋은 동료의 소중함도 느끼게 되었습니다.
(참고로 LINER 팀은 매 달마다 꼭 페어 프로그래밍을 수행해야 하는 동료를 선정해 적극 장려하고 있어요. 페어 프로그래밍 문화를 잘 정착해주신 셀리나 감사합니다👍)
또 다른 난관, contenteditable과 selection으로 해결하다!
하이라이트 정보를 SNS가 있는 페이지에서 변수로 저장하는 것은 성공했지만, SNS의 텍스트 에디터에 옮기는 건 또 다른 난관이었어요. SNS의 텍스트 에디터는 단순한 <textarea>
나 <input>
이 아닌 contenteditable attribute를 가진 <div>
였거든요. contenteditable
이란 HTML Global attributes 중 하나로 true일 경우, 해당 요소를 유저가 수정할 수 있게 됩니다. 실제로 현재 제가 작성하고 있는 wordpress의 텍스트 에디터도 contenteditable attribute를 가지고 있어요.
👉 contenteditable에 대해 좀 더 알고 싶다면?
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content
저는 기존 유저가 작성하던 텍스트를 공유할 하이라이트 텍스트에 이어 붙이고, 텍스트 에디터에 있는 내용을 이어 붙인 텍스트로 대체해 보았어요. 그 결과 텍스트는 바뀌었지만, 글자를 하나라도 입력하는 순간 다시 공유하기 전 내용으로 돌아갔어요.
또 다른 시도로 텍스트 에디터 내부에 있는 <p>
태그에 있는 텍스트와 공유할 하이라이트 텍스트를 합친 후, 기존 <p>
태그를 제거하고 합친 텍스트가 들어간 새로운 <p>
태그로 대체해 보았는데, 마찬가지로 키보드 입력이 들어오면 공유하기 전 내용으로 돌아갔어요. 마치 에디터에 입력된 텍스트가 내부 변수로 저장되어, 임의로 내부 텍스트를 바꾸더라도 결국 저장된 내용이 돌아오는 느낌이었어요.
제가 한창 골머리를 앓고 있을 때, 그걸 본 데이브가 실마리를 주셨어요! (감사합니다 데이브😭) 바로 selection
객체와, contenteditable 속성이 true일 때 사용할 수 있는 document.execCommand()
메서드를 이용하는 방법이었어요. selection 객체는 웹 페이지 내에서 특정 영역을 선택한 것을 나타내고, document.execCommand()
메서드는 선택한 영역의 텍스트를 복사하거나 대체하는 등의 조작을 가능하게 해요.
👉 document.execCommand()에 대해 더 알고 싶다면?
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(linkedInTextArea);
selection.removeAllRanges();
selection.addRange(range);
위 코드는 링크드인의 텍스트 에디터 내 전체 내용을 선택합니다. 이 때, 유저가 드래그 한 것 처럼 선택되어 파란색으로 변하는 영역이 selection이고, 그 영역의 범위를 range라고 생각하시면 됩니다. 이후 아래 코드로 선택한 영역에 제가 원하는 텍스트를 깔끔하게 삽입합니다.
document.execCommand('insertHTML', false, linkedInTextAreaAlreadyWritten + contentBeforeUrl + '\n' + window.linerShareSnsUrl + '\n');
페이스북에서 하이라이트 공유하기
링크드인은 위 방법으로 쉽게 구현했지만 페이스북은 다음과 같은 문제점들이 발견되었습니다.
- 빈 에디터에 하이라이트를 공유했을 때, 링크가 텍스트 밑에 있으면 에디터가 강제 종료됨
- 전체 영역을 선택한 후, 하이라이트를 공유하면 키보드 입력 시 에디터가 강제 종료됨
- 뒤에 개행이 있을 때 하이라이트를 공유하면 하이라이트가 두 번 복사됨
그 중 가장 어려웠지만 재미있었던 3번 문제를 직접 해결한 과정을 공유하겠습니다!
저는 개행을 지워보기도 하고, 개행을 제외한 부분을 selection으로 잡아보기도 하고, 마지막 줄만 selection으로 잡아보기도 했습니다. 수 많은 실험을 통해서 알게된 점은 페이스북 에디터에서는 줄에 문자가 하나라도 있으면 괜찮지만, 단순한 개행을 강제로 제거하거나 내용을 대체하면 문제가 발생한다는 것이었죠. 특히 개행을 제거했을 때는 에디터가 아예 종료되어버려 그 방법은 아예 불가능했고, 그나마 마지막 개행만 selection으로 잡아서 하이라이트를 공유했을 때, 하이라이트가 두 번 복사되기는 하지만 에러가 발생하지 않았습니다.
그러던 도중, 텍스트 에디터 하단에 있는 ‘사람 태그’, ‘위치 추가’ 등의 버튼을 눌러 해당 메뉴 화면으로 이동했다가 다시 에디터로 돌아오면 두 번 복사되었던 하이라이트 중 하나가 사라져 정상적으로 공유가 이루어지는 것을 알게 되었습니다. 그래서 맨 밑 개행을 selection으로 잡아 하이라이트를 삽입함과 동시에, ‘사람 태그’ 버튼의 클릭 이벤트를 트리거하여 화면을 이동시켰습니다.
화면의 전환이 보이면 유저의 경험이 나빠지기에 opacity를 0으로 만들었습니다. 이후 setTimeout()
으로 0.5초 후, 이동한 화면의 왼쪽 상단에 있는 ‘뒤로 가기’ 버튼의 클릭 이벤트를 트리거하여 다시 에디터 화면으로 돌아간 뒤, opacity를 1로 만들어 유저의 입장에서는 마치 로딩 후 하이라이트가 공유되는 것처럼 보이게 만들었습니다. 아래는 그 결과물입니다…!😮💨
마치며
사실 이 작업에 알고리즘이나 논리적인 사고는 크게 필요하지 않았습니다. 다만, 아무도 시도해보지 않은 문제에 도전하고 이를 어떻게든 해결해 나가는 게 제게는 큰 기쁨이었고, “내가 진짜 스타트업에서 일하고 있구나!”라는 생각이 많이 들게 한 작업이었습니다. 또한 유저의 경험에 대해 고민하고 창의적으로 생각해보는 과정도 즐거웠고, 제가 만든 서비스가 많은 유저들에게 직접적으로 영향을 미칠 수 있다는 게 너무 짜릿했습니다.
LINER에 합류하고 약 3개월이 지난 지금, 저는 제 자신이 매일 매일 성장하고 있다는 게 느껴집니다. 아직 많이 부족한 주니어지만, 좋은 동료들과 함께 계속 도전하고 고민하다보면 언젠가는 뛰어난 개발자가 될 수 있을 거라고 생각합니다!
[라이너 팀에서 동료를 찾고 있어요!]
‘라이너’는 전 세계 300만 유저와 함께하는 하이라이팅 기반 정보 탐색 서비스입니다.
픽사 창업자, 트위터 창업자, 넷플릭스 부사장까지 유료 구독하는 진정한 글로벌 프로덕트를 함께 만들어갈 인재분들을 찾고 있습니다.
작년에 7명의 팀원으로 50억원이 넘는 투자유치를 마쳤기 때문에(지금은 24명) 매우 쾌적한 환경에서! 직접적인 임팩트를! 내실 수 있습니다. “구글의 핵심 멤버”들은 15~30번째로 구글에 합류한 사람들이 가장 많습니다. 지금, 라이너와 25번째 팀원으로 함께 할 관심이 있으신 분, 혹은 주변에 떠오르는 적합한 인재가 있다면, 아래 페이지를 확인 해주세요!