낱낱이 살펴보는 form

💛 nalsae·2022년 7월 21일
1

🧡 내보정 HTML

목록 보기
3/3
post-thumbnail

👊 form 부수기 대작전

 form 관련 요소는 웹 페이지가 서버와 서로 상호작용할 수 있다는 점에서 사용 의의가 있다. HTML에서는 검색어를 입력할 수 있는 상자나, 값을 선택할 수 있는 라디오 버튼 등 다양한 방식으로 사용을 지원하고 있다. 그리고 실제로 많은 곳에서 form 관련 요소들을 유용하게 사용하고 있는 만큼 잘 알고 있을 필요가 있을 것이다. 그러나 스스로 그 구체적인 사용 방법에 대해서는 스스로 좀 무지하다고 느꼈기에 form 관련 요소에 대해 전체적으로 살펴보는 시간을 가져보고자 한다.


🤔 서버와 상호작용을 한다고?

 앞서 form 요소가 웹 문서와 서버의 상호작용을 담당한다고 했다. 그렇다면 이를 어떤 과정으로 수행하고 있는 것일까?

💡 form 요소의 동작 과정

(1) 사용자가 웹 페이지를 방문
(2) 웹 페이지의 form 내용 입력
(3) form의 데이터를 브라우저가 서버로 보냄
(4) 웹 서버가 form 데이터를 처리하기 위해 PHP, JSP 등의 웹 프로그램으로 넘김
(5) 웹 프로그램이 form 데이터를 처리함
(6) 웹 프로그램이 처리 결과에 따라 새로운 HTML 문서를 웹 서버에 보냄
(7) 웹 서버가 받은 HTML 문서를 다시 브라우저에게 보냄
(8) 브라우저가 받은 HTML 문서를 렌더링하여 사용자에게 보여줌

 복잡해보이지만 결국 웹 서버가 브라우저와 웹 프로그램 사이에서 가교 역할을 함으로써, 사용자는 입력한 form 데이터를 바탕으로 변경된 HTML 문서를 볼 수 있는 것이다.


🔍 form을 파헤쳐보자!

 그렇다면 지금부터는 구체적으로 form 관련 요소에 어떤 것들이 있고, 어떻게 사용해야 하는지에 대해 살펴보고자 한다.


🔸 form

 먼저 form은 사용자가 데이터를 입력할 수 있는 form 서식의 영역을 지정할 때 사용하는 태그이다. form 요소에 사용할 수 있는 속성은 다음과 같다.

💡 action

: 필수 속성이며, 값으로는 상호작용하기 위한 웹 서버의 스크립트 파일 주소를 지정

💡 method

: 웹 서버와 데이터를 주고 받을 방식을 설정
: 값으로 GET이나 POST 중에 선택할 수 있음

💡 accept-cherset

: 데이터 전송에 사용할 문자 인코딩 값을 설정할 수 있는 속성

💡 autocomplete

: 자동 완성 기능의 활성화 여부를 선택할 수 있는 속성
: on 또는 off 값 중에 선택할 수 있음

💡 enctype

: form 전송 데이터의 MIME 타입

💡 name

: 서버가 form을 식별하기 위한 이름을 값으로 지정하는 속성

💡 target

: _blank 값을 설정하면 action에서 지정한 스크립트 파일을 다른 창에서 열도록 지정

💡 novalidate

: 데이터를 전송할 때 입력하는 데이터 값의 검증을 수행하지 않도록 지정하는 속성

 위의 속성을 모두 사용하여 예제 코드를 만들어보면 다음과 같이 사용할 수 있겠다.

<form action="data.php" method="GET" name="userData"
     accept-charset="UTF-8" autocomplete="on" 
     target="_blank" novalidate="novalidate">
</form>

❓ GET은 뭐고 POST는 뭐지?

 앞서 form 요소의 속성에 대해 살펴볼 때 생소한 개념이 있었다. 바로 method 속성의 값인 GETPOST가 그것이다. method웹 서버와 데이터를 주고 받는 방식을 정의하는 속성이라고 했다. 그렇다면 구체적으로 GETPOST는 방식에 있어 어떤 차이가 있는 것일까?

 GETPOST의 차이를 아주 간단하게 설명하려면 값의 이름을 보면 된다. 직역해보면 GET은 무엇인가를 획득한다는 의미이고, POST는 무엇인가를 붙이고 게시한다는 의미이다. 즉 보편적으로 GET데이터를 읽을 때, POST데이터를 수정할 때 사용하는 방식이라고 볼 수 있다.

 그러나 단순히 GETPOST를 데이터를 읽는 방식, 수정하는 방식으로 이해하기 보다는 구체적인 특징을 파악하고 있는 것이 사용에 도움이 되리라 생각한다. MDN에 명시되어 있는 GETPOST 방식의 차이를 통해 이를 살펴보고자 한다.

💡 GET 요청의 특징

: 불필요한 요청의 제한을 위해 요청이 캐시에 저장될 수 있음
: 데이터 요청시 내용이 노출되므로 민감한 데이터를 다룰 때 사용해서는 안 됨
: 브라우저 기록에 저장됨
: 북마크에 추가할 수 있음
: 데이터 길이에 대한 제한이 있음
: 데이터를 수정할 수 없음
: idempotent함

💡 POST 요청의 특징

: 요청이 캐시에 저장되지 않음
: 브라우저 기록에 저장되지 않음
: 북마크에 추가할 수 없음
: 데이터 길이에 대한 제한이 없음
: 요청했을 때 리소스를 생성하면 201 HTTP 응답 코드(Created)를 반환
: idempotent하지 않음

💡 GETPOST 요약 비교

GETPOST
캐시OX
브라우저 기록OX
북마크 추가OX
데이터 길이 제한OX
HTTP 응답 코드200(OK)201(Created)
사용 상황리소스 요청리소스 생성
전달 방식쿼리 스트링HTTP Body
idempotentOX

 위와 같이 각각의 특징을 구체적으로 살펴보면 새롭게 보이는 것들이 있다. 특징적인 지점을 짚고 넘어가자면, 먼저 GET으로 데이터를 요청하는 경우 내용이 노출된다는 점이다. 정확히 어떤 방식으로 내용이 노출되는 것일까?

 GET으로 데이터 요청을 하는 경우 URL 끝에 파라미터로 포함되어 전송되는데, 이 부분을 쿼리 스트링(QureyString)이라고 일컫는다. 예를 들면 다음과 같다.

ex) www.example.com/datas?name1=value1&name2=value2

 위의 예시 url을 살펴보면 / 뒤에 붙은 파라미터를 통해 어떤 폴더에서 어떤 값을 받아오는지 전부 노출된다. 이처럼 보안에 취약하기 때문에 민감한 데이터라면 POST 방식을 사용해야 하는 것이다.

 POST 방식은 데이터를 송신할 때 GET과 달리 HTTP 메시지의 Body에 담아서 전송한다. 그러므로 값이 노출되지도 않고, 데이터의 길이 제한이 없기 때문에 대용량 데이터를 전송할 수 있는 것이다. 그러나 POST 요청도 사용자의 눈에 가시적으로 보이지 않을 뿐이지, 개발자 도구를 사용하면 요청 내용을 확인할 수 있기 때문에 민감한 데이터를 따로 암호화하는 과정이 필요할 것이다.

 앞서 살펴본 GETPOST의 특징 중 정말 생소한 단어가 하나 있다. 바로 'idempotent'라는 개념이다. 직역하면 '멱등성'이라는 뜻인데, 큰일이다. 직역을 해봐도 무슨 의미인지 전혀 유추할 수가 없다. 이해를 돕기 위해 일단 사전적 정의를 살펴보면 다음과 같다.

💡 idempotent란?

: 멱등성, 수학이나 전산학에서 연산의 한 성질을 나타내는 것으로, 연산을 여러번 적용하더라도 결과가 달라지지 않는 성질

 즉 쉽게 말하면 연산을 여러번 수행하더라도 동일하게 결과를 도출한다는 것이다. 이를 바탕으로 GETPOST의 특징을 살펴보면, 먼저 GET은 멱등성이 있다. 이 말인 즉슨 GET 요청을 보낼 때 항상 동일한 결과를 반환해야 한다는 것이다. 잘 생각해보면 당연하다. GET은 주로 데이터를 조회할 때 사용한다. 데이터를 조회할 때마다 동일한 데이터가 달라져서는 안 될 것이다. 따라서 GET 요청으로 조회하는 데이터는 멱등성, 즉 항상 동일한 결과 값을 가져야 한다는 것이다. 반면 POST의 경우 주로 데이터를 수정할 때 사용한다. 만약 데이터를 수정하는 요청을 전송했는데도 데이터의 결과가 바뀌지 않는다면, 그것은 잘못된 요청이다. 그러므로 POST 요청으로 수정하는 데이터는 멱등성을 가지지 않는, 즉 가변적인 결과 값을 가져야 하는 것이다.


🔸 fieldset, legend

 다시 돌아와서, form 관련 요소의 종류를 더 알아보겠다. fieldsetlegend는 여러 개의 form 요소를 좀 더 구조적으로 만들기 위해 사용한다.

 구체적으로 살펴보면, fieldset 요소를 사용할 때 주위에 자동으로 아웃라인이 생성되어 그 안에 있는 콘텐츠가 form과 관련되었다는 사실을 쉽게 식별할 수 있다. legend 요소는 fieldset에 포함된 서식에 대한 설명을 제목 형식으로 알려주는 역할을 한다. 다만 legend 요소를 사용할 때의 주의점이 있는데, 이는 다음과 같다.

💡 legend 태그 사용시 주의점

: fieldset 요소의 자식 요소로 한 번만 사용할 수 있음
: 크로스 브라우징 관점에서 브라우저마다 다르게 적용되므로 스타일링시 어려움이 있을 수 있음

<form action="user_data.php" method="get">
  <fieldset>
    <legend>유저 데이터 리스트</legend>
  </fieldset>
</form>

 fieldsetlegend의 구체적인 사용법을 예제 코드로 확인해보면 위와 같다.

 또 하나 주목할 점으로 HTML5에서는 fieldset 요소에 form 속성이 추가되었다. fieldset 요소의 form 속성 값을 form 요소의 id 값과 연결시키면 form 외부에 fieldset이 있는 구조여도 서로 연결시킬 수 있다. 예제 코드로 확인해보면 다음과 같다.


🔸 label

 labelbutton, input, keygen, meter, output, progress, select, textarea와 같은 form 관련 요소에 대한 연관 관계와 설명을 추가함으로써 접근성을 향상시키기는 역할을 한다. 크기가 작은 라디오 버튼이나 체크 박스 같은 경우, label로 연관 관계를 명시해주면 label만 선택해도 연관된 form 요소가 선택되기 때문에 접근하기가 쉬워진다.

 이처럼 웹 접근성 측면에서 label은 아주 중요한 역할을 한다. form 요소만 사용하면 정확히 무엇을 입력해야 하는지 알 수 없기 때문에 반드시 label로 그에 대한 설명을 명시하는 것이 중요하다.

 그렇다면 구체적으로 form 관련 요소와의 연관 관계를 어떻게 명시할 수 있을까? labelfor 속성을 사용해야 한다. for 속성 값과 연결하려는 form 관련 요소의 id 값을 동일하게 설정하면 명시적으로 연관 관계를 표현할 수 있다. 바로 다음과 같은 방식으로 말이다.

 앞서 label을 사용해 연관 관계를 명시적으로 나타내는 방법을 살펴보았는데, 암시적으로 연관 관계를 나타내는 방법도 존재한다. label 요소의 하위 요소로 form 관련 요소를 작성하면 된다. 예제 코드로 확인해보겠다.

 그렇다면 명시적 레이블링과 암시적 레이블링, 둘 중 어떤 방법을 사용하는 것이 바람직할가? 결론부터 말하자면 명시적 레이블링을 사용하는 것이 더 좋다. 그 이유는 크게 두 가지가 있는데, 먼저 오래된 장치에서 암시적 레이블링 방식을 인식하지 못하는 경우가 있기도 하다. 두 번째로는 스타일링의 유연성이다. 아무래도 하위 요소로 상속된 상황은 개별적으로 마크업한 상황보다 스타일링시 고려해야 하는 부분이 늘어날 수밖에 없을 것이다.


🔸 input

 다음으로 form 요소의 핵심, input에 대해 살펴보고자 한다. 말 그대로 input은 사용자로부터 데이터를 입력받기 위한 목적으로 사용된다. HTML5에서는 기존보다 input에 사용할 수 있는 type 속성의 값이 늘어났기 때문에, 다양한 상황에서 적절한 값을 사용할 수 있게 되었다. 또한 HTML5에서는 type 속성 외에도 autocomplete, placeholder 등 편의를 제공할 수 있는 속성들이 다수 추가되었다.

 input 요소는 설정한 type 속성의 값에 따라 서식의 종류가 결정된다는 특징이 있기 때문에, 각각의 특징을 잘 살펴볼 필요가 있을 것이다. 그러므로 type 속성의 값을 하나씩 살펴보고자 한다.

😮 정말 다양한 type

💡 text

: 기본 값, 한 줄 글 상자를 나타낼 때 사용

💡 password

: 비밀번호처럼 민감한 데이터 입력시 사용
: text와 기본적으로 동일하지만, 화면에 입력 결과가 출력되지 않음

💡 search

: text와 기본적으로 동일하지만, 줄바꿈을 포함한 문자열 값이 입력되면 줄바꿈 제거
: 브라우저에 따라 user agent style이 다르게 적용되므로 크로스 브라우징 이슈 발생할 수 있음

💡 tel

: text와 동일하지만 입력 값이 전화번호라는 것을 의미함
: 모바일에서 tel에 값을 입력하려고 하면 자동으로 키보드의 숫자 입력 모드가 트리거되어 사용성이 향상됨

💡 url

: text와 동일하지만 입력 값이 URL이라는 것을 의미함
: url의 값으로 줄바꿈을 포함한 문자열이 들어온다면 브라우저가 이를 자동으로 제거
: 값으로 상대 경로는 입력할 수 없음, 절대 경로 URL만 입력 가능
: 부적절한 URL을 입력했다면 form 전송시 에러 메시지를 출력함
: 모바일에서는 입력시 키보드의 영문 및 숫자 입력 모드가 트리거됨

💡 email

: text와 동일하지만 입력 값이 이메일이라는 것을 의미함
: 입력 값으로 이메일 주소만 가능
: 부적절한 이메일 주소를 입력했다면 form 전송시 에러 메시지 출력
: 모바일에서는 입력시 키보드의 영문 및 숫자 입력 모드가 트리거됨

💡 date

: 날짜를 입력할 때 사용
: 보편적으로 브라우저에서 날짜를 입력할 수 있는 서식을 자동 제공하지만, 제공하지 않는 브라우저도 있음
: minmax 속성을 추가 사용하여 입력 날짜의 범위 지정 가능
: step 속성을 추가 사용하여 입력 최소 단위 지정 가능

💡 month, week, time

: 서식은 date 타입과 동일하지만 각각 월, 주, 시간을 입력할 때 사용한다는 차이점이 있음
: 함께 사용할 수 있는 속성도 date와 동일

💡 datetime, datetime-local

: 날짜와 시간을 함께 입력할 때 사용
: 입력시 타임 존을 UTC로 설정하기 때문에 한국 시간과 비교하면 9시간 늦은 시간이 됨
: date와 마찬가지로 min, max, step 사용 가능
: datetime-local은 타임 존이 없는 날짜와 시간을 입력할 때 사용한다는 차이점 외에 datetime과 동일

💡 number

: 숫자를 입력할 때 사용
: minmax 함께 사용하여 최소값, 최대값 지정 가능
: step 속성으로 입력 단위 지정 가능

💡 range

: 사용 목적은 number와 동일
: 다만 range슬라이드 형태의 UI로 렌더링된다는 차이점
: min, max, step 함께 사용 가능

💡 color

: 색상을 입력할 때 사용
: 모든 웹 브라우저가 지원하지는 않지만, 지원하는 웹 브라우저에서 Color Picker 형태로 렌더링됨

💡 radio

: 라디오 버튼 서식을 제공할 때 사용
: checked 속성 지정하면 미리 선택된 상태로 렌더링됨

💡 file

: 로컬 컴퓨터의 파일을 업로드할 수 있는 서식을 제공할 때 사용
: accept 속성을 함께 사용하여 서버에서 수신할 파일의 형식을 지정할 수 있음
: multiple 속성을 함께 사용하여 여러 개의 파일을 업로드할 수도 있음

💡 hidden

: 설정하면 브라우저에 렌더링되지 않음
: 자바스크립트를 이용해야 내용을 변경할 수 있음
: 사용자가 보면 안 되는 데이터를 함께 전송할 때 유용하게 사용
: name 속성 값으로 charset 값을 지정하면 form의 문자 인코딩 값이 서버로 전달됨

💡 submit

: 전송 버튼을 나타내는 속성
: 데이터를 서버로 전송하는 역할
: value와 함께 사용하여 버튼에 표시될 문자열 입력 가능

💡 reset

: 말 그대로 리셋, form 요소의 데이터를 모두 초기화하는 역할
: value와 함께 사용하여 버튼에 표시될 문자열 입력 가능

💡 button

: 단순한 버튼으로 자바스크립트 등을 실행하는 역할
: value와 함께 사용하여 버튼에 표시될 문자열 입력 가능

💡 image

: submit과 동일하게 전송의 역할
: 다른 점이 있다면 버튼에 이미지 지정할 수 있음
: src 속성으로 경로 지정, alt 속성으로 대체 텍스트 지정

😲 여기서 끝이 아니다, 기타 속성!

 앞서 살펴봤듯 HTML5로 넘어와서 inputtype 속성에 사용할 수 있는 값이 정말 다양해졌다. 이번에는 type과 함께 input 사용에 편의성을 더해주는 속성 몇 가지를 살펴보고자 한다.

💡 autocomplete

: 자동 완성 기능 사용 여부, 값으로 on 또는 off
: on으로 지정하면 이전에 입력한 데이터를 브라우저가 후보 값으로 제공

💡 list

: autocomplete처럼 후보 값을 제공하는 기능은 동일
: 개발자가 직접 후보 값을 제공한다는 점에서 차이가 있음
: 후보 값은 datalist 요소로 마크업하고, datalistidlist의 값을 연결시켜야 함

💡 readonly

: 읽기 전용으로 지정하기 위해 사용
: 사용자가 input값을 변경할 수 없음

💡 size

: input의 크기를 지정하는 속성
: 브라우저마다 크기가 달라질 수 있기 때문에 CSS로 설정하는 것이 바람직함

💡 required

: 해당 서식이 필수적인지 아닌지 지정하는 속성
: 설정한 경우 입력 값이 없는 상태로 서버에 데이터를 전송하면 에러 메시지가 출력됨

💡 placeholder

: 사용자에게 입력 값에 대한 힌트 제공
: 사용자가 새로운 값을 입력하면 placeholder는 사라짐

💡 pattern

: 입력 값의 정규 표현식을 지정할 수 있음
: title 속성으로 정규 표현식에 대한 설명 제공하는 것이 좋음

💡 multiple

: 2개 이상의 값을 지정할 때 사용
: type의 값이 email, file인 경우 사용 가능

💡 maxlength

: 입력할 수 있는 최대 글자 수를 지정할 때 사용

💡 autofocus

: 설정하면 웹 페이지가 렌더링되자마자 바로 포커스가 서식으로 이동

💡 step

: 서식에 지정할 수 있는 값의 단위, type 값에 따라 달라짐

💡 max, min

: max는 입력의 최대값, min은 입력의 최소값을 설정할 수 있음

💡 disabled

: 서식을 사용하지 못하도록 지정할 때 사용

💡 name

: 서식의 이름을 의미, 서버로 데이터를 전송할 때 그 데이터의 이름을 가리키기 위한 목적


🔸 button

 button 태그는 말 그대로 버튼 서식이다. input과 동일하게 type 속성을 지정할 수 있는데, 그 종류로는 submit, reset, button이 있다. 각각의 속성은 inputtype과 기능이 동일하다.

 여기서 잠깐 드는 의문점이 하나 있다. labelinput을 1:1 관계로 작성하여 명시적 레이블링을 해주는 것이 웹 접근성 측면에서 좋다고 했는데, 그렇다면 buttoninput 요소와 마찬가지로 레이블링을 해야 할까? 잠깐 생각해봤을 때는 둘 다 form 요소의 하위 범주에 해당하므로 button 역시 레이블링이 필수적일 것 같지만, 그렇지 않다. type 속성과 value 속성의 값을 통해 button은 자체적으로 어떤 콘텐츠인지 파악할 수 있기 때문에 굳이 레이블링을 해줄 필요가 없는 것이다.


🔸 select, option, optgroup

 select 태그는 사용자가 항목을 선택할 수 있는 서식이다. size 속성으로 표시할 항목의 수를 결정할 수 있고, multiple 속성으로 다중 선택 허용 여부를 결정할 수 있다.

 각각의 항목은 select하위 요소로서option 태그로 마크업할 수 있고, optgroup 태그는 option그룹화할 때 사용한다. optgroup에는 label 속성을 사용하여 그룹의 역할에 대한 정보를 제공할 수 있다.


🔸 datalist

 HTML5에 새로 추가된 태그로, 사용자에게 input 요소 입력 값의 후보 목록을 그룹화할 때 사용한다. datalistinput을 연결시키려면 각각 idlist 속성의 값을 동일하게 설정해야 한다. 각 후보 값은 앞서 살펴본 option 태그의 value 속성을 통해 제공할 수 있다.


🔸 textarea

 여러 줄의 텍스트를 입력 받을 때 textarea 태그를 사용한다. input과는 줄 바꿈 여부에서 차이가 있다. input은 한 줄 글상자로 줄 바꿈이 되지 않는다는 특징이 있지만, textarea 사용시 줄 바꿈 여부를 지정할 수 있다. wrap 속성을 사용하여 줄 바꿈 여부를 수동으로 지정할 수도 있다. 기본 값인 soft는 줄 바꿈을 설정하지 않을 때, hard는 줄 바꿈을 설정할 때 사용한다.

 텍스트 상자의 크기는 cols, rows 속성으로 지정할 수 있다. 그러나 브라우저 설정이나 폰트 크기에 영향을 받으므로 정확한 크기는 CSS의 width, height 값으로 조절하는 것이 바람직하다.


🔸 keygen

 keygen은 서식을 서버로 전송할 때 키를 생성하고, 따라서 전자 인증에 주로 사용한다. 생성하는 키에는 비밀 키와 공개 키가 있다. 이때 비밀 키는 웹 브라우저에 저장되고 공개 키는 서버에 전송된다. HTML5에서 keygen 요소의 type으로는 rsa 방식만 지원한다.


🔸 output

 output계산 결과, input 요소의 출력 값을 나타내는 역할을 한다. 요소 사용시 formfieldset으로 감싸서 영역을 구분해주어야 정상적으로 동작한다.


🔸 progress

 HTML5에 추가된 progress요소의 진행 상황을 나타낼 때 사용한다. value 속성의 값을 0 ~ 1 사이로 설정하여 진행 정도를 나타낼 수 있다.


🔸 meter

 progress와 혼동할 수 있지만, meter는 진행 상황이 아니라 한정된 범위의 값을 나타낼 때 사용한다. max로 최대값, min으로 최소값을 설정할 수 있고, value로 설정 값을 지정할 수 있다.

 추가적으로 low, high, optimum 속성을 사용할 수 있다. 먼저 lowmin 값보다 커야 하며, 범위 내에서 낮은 값으로 간주될 값을 명시하는 역할이다. 따로 설정하지 않으면 min과 동일하게 설정된다. highmax 값보다 작아야 하며, 범위 내에서 높은 값으로 간주될 값을 명시하는 역할이다. 마지막으로 optimum범위 내의 최적값으로 간주될 값을 명시하는 역할이다. 따로 설정하지 않으면 maxmin의 중간 값으로 설정된다.


😇 끝이 없는 form

 지금까지 살펴본 form 관련 요소는 그 종류가 정말 어마어마하다. 공식 문서를 천천히 다 읽어보면 하위 요소나 속성에 대한 정보가 꼬리에 꼬리를 물고 쏟아지는 것을 알 수 있다.

 내용이 워낙 많기 때문에 이를 다 외워서 사용할 수는 없겠지만, 적어도 한 번 찾아보고 코드로 짜본 상태와 그렇지 않은 상태는 천지 차이라고 생각한다. 이왕 공부하는 거 확실하게 해보자고 끝까지 이번 글에서 form 요소를 집중 공략해보았는데, 덕분에 확실히 나중에 form을 좀 더 익숙하게 사용할 수 있을 것 같다.

🙏 출처

https://developer.mozilla.org/ko/docs/Web/HTML/Element/form
https://www.w3schools.com/tags/ref_httpmethods.asp
https://seulbinim.github.io/WSA/form.html#button-%EC%9A%94%EC%86%8C

profile
𝙸'𝚖 𝚊 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚝𝚛𝚢𝚒𝚗𝚐 𝚝𝚘 𝚜𝚝𝚞𝚍𝚢 𝚊𝚕𝚠𝚊𝚢𝚜. 🤔

0개의 댓글