Merge pull request #8 from Gyubin-Han/feature/frontend
Feat: 단축 URL 생성 페이지 구현
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -54,6 +54,7 @@ node_modules/
|
|||||||
# OS specific
|
# OS specific
|
||||||
.DS_Store
|
.DS_Store
|
||||||
# Task files
|
# Task files
|
||||||
|
.taskmaster
|
||||||
tasks.json
|
tasks.json
|
||||||
tasks/
|
tasks/
|
||||||
|
|
||||||
@@ -66,3 +67,5 @@ scripts/
|
|||||||
.taskmasterconfig
|
.taskmasterconfig
|
||||||
.windsurfrules
|
.windsurfrules
|
||||||
compose.yaml
|
compose.yaml
|
||||||
|
|
||||||
|
js/
|
||||||
|
|||||||
209
src/main/resources/templates/index.html
Normal file
209
src/main/resources/templates/index.html
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<script>
|
||||||
|
const VALIDATE_URL="^((http|https):\\/\\/)?([a-z0-9-]{2,}\\.[a-z]{2,}|([0-9]{1,3}\\.){3}[0-9]{1,3})[\\w.\\/가-힣\\-\\ ?=&:]*";
|
||||||
|
|
||||||
|
// DOM 객체(요소) 체이닝 생성 클래스
|
||||||
|
class Element{
|
||||||
|
// 생성자
|
||||||
|
// type(string) : 요소(태그)의 종류 정의
|
||||||
|
constructor(type){
|
||||||
|
this.element=document.createElement(type);
|
||||||
|
this.element.eventList={}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 하위에 DOM 객체(요소)를 추가하는 메소드
|
||||||
|
// child(Object(Element) || DOMObject) : 하위에 추가할 요소
|
||||||
|
append(child){
|
||||||
|
// child가 Object(Element)인 경우, 하위에 있는 element를 가져와 추가하고,
|
||||||
|
// 아닌 경우, child를 그대로 추가
|
||||||
|
this.element.append(child.element || child);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 요소 내 html 코드를 직접 설정하는 메소드
|
||||||
|
html(html){
|
||||||
|
this.element.innerHTML=html;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 요소에 표시할 text 값을 설정하는 메소드
|
||||||
|
text(text){
|
||||||
|
this.element.textContent=text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 요소에 class를 추가하는 메소드
|
||||||
|
addClass(name){
|
||||||
|
this.element.classList.add(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 요소에 추가되어 있는 class를 제거하는 메소드
|
||||||
|
removeClass(name){
|
||||||
|
this.element.classList.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 링크 값을 설정하는 메소드
|
||||||
|
href(link){
|
||||||
|
this.element.href=link;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// id 값을 설정하는 메소드
|
||||||
|
id(value){
|
||||||
|
this.element.id=value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 요소의 속성 값을 설정하는 메소드
|
||||||
|
setAttr(name,value){
|
||||||
|
this.element.setAttribute(name,value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 요소에 설정되어 있는 속성을 제거하는 메소드
|
||||||
|
removeAttr(name){
|
||||||
|
this.element.removeAttribute(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이벤트 리스너를 추가하는 메소드
|
||||||
|
// type(string) : 감지될 이벤트 타입
|
||||||
|
// action(func) : 해당 이벤트가 발생했을 때, 처리될 이벤트 핸들러 함수
|
||||||
|
addEventListener(type,action){
|
||||||
|
this.element.eventList[type]=action;
|
||||||
|
this.element.addEventListener(type,action);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 추가되어 있는 이벤트 리스너를 제거하는 메소드
|
||||||
|
// type(string) : 제거할 이벤트 타입
|
||||||
|
removeEventListener(type){
|
||||||
|
this.element.eventList[type]=null;
|
||||||
|
this.element.removeEventListener(type);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 최종적으로 완성된 Element 객체를 DOM 요소 형태로 반환
|
||||||
|
get(){
|
||||||
|
return this.element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단축 URL의 복사 버튼을 클릭하였을 때 동작되는 이벤트 함수
|
||||||
|
function clickBtnShortUrlCopy(shortId){
|
||||||
|
// 이 단축 URL의 사용자 내부 ID 값을 통하여, 단축 URL 값을 추출
|
||||||
|
// 주소를 복사할 범위 객체 생성
|
||||||
|
const range=document.createRange();
|
||||||
|
// 범위를 드래그할 객체 생성
|
||||||
|
const selection=window.getSelection();
|
||||||
|
// 드래그하기 전, 기존에 드래그 된 것은 제거
|
||||||
|
selection.removeAllRanges();
|
||||||
|
|
||||||
|
// 범위 지정
|
||||||
|
range.selectNodeContents(document.querySelector(".short-item[data-id='"+shortId+"'] .short-item-short-url"));
|
||||||
|
// 범위를 실제 드래그한 범위로 지정
|
||||||
|
selection.addRange(range);
|
||||||
|
// 사용자 클라이언트의 클립보드에 복사
|
||||||
|
document.execCommand("copy");
|
||||||
|
// 복사 후, 기존의 드래그 범위를 제거
|
||||||
|
selection.removeAllRanges();
|
||||||
|
|
||||||
|
// 사용자에게 성공적으로 복사되었음을 알림
|
||||||
|
alert("성공적으로 복사되었습니다!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단축 URL 생성에 성공한 경우 동작하는 메소드
|
||||||
|
// 단축 URL의 정보를 사용자 화면에 출력
|
||||||
|
function createShortResultElement(data){
|
||||||
|
// 사용자의 접속 기본 정보를 추출
|
||||||
|
const userProtocol=window.location.protocol;
|
||||||
|
const userHost=window.location.host;
|
||||||
|
const shortUrl=userProtocol+"//"+userHost+"/"+data.shortUrl;
|
||||||
|
|
||||||
|
// 사용자 화면에 출력될 요소 생성 및 하위 요소 생성, 설정
|
||||||
|
var newRootElement=new Element("td");
|
||||||
|
newRootElement.text("단축되었습니다!");
|
||||||
|
newRootElement.append(
|
||||||
|
new Element("ul")
|
||||||
|
.append(new Element("li").text("원본 URL : "+data.originalUrl).addClass("short-item-original-url"))
|
||||||
|
.append(new Element("li").html("단축 URL : "+data.shortUrl+"<br>\n")
|
||||||
|
.append(new Element("span").html(shortUrl).addClass("short-item-short-url"))
|
||||||
|
.append(new Element("button").text("복사하기").addEventListener("click",function() { clickBtnShortUrlCopy(data.shortUrl); }))
|
||||||
|
)
|
||||||
|
.append(new Element("li").text("유효기간 : "+data.expiredAt))
|
||||||
|
)
|
||||||
|
newRootElement.setAttr("data-id",data.shortUrl);
|
||||||
|
newRootElement.addClass("short-item")
|
||||||
|
|
||||||
|
// 위에서 만들어진 요소를 실제 화면에 배치
|
||||||
|
document.querySelector("#short-list").append(new Element("tr").append(newRootElement).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단축 버튼을 클릭했을 때 동작되는 이벤트 함수
|
||||||
|
function clickBtnGenShort(){
|
||||||
|
// 이벤트(단축 버튼을 클릭)가 발생했을 당시 입력한 원본 URL 값을 가져옴.
|
||||||
|
var inputUrl=document.body.querySelector("input[type='text'][name='input_url']").value;
|
||||||
|
|
||||||
|
// 입력한 원본 URL이 올바른 형태의 URL인지 검증
|
||||||
|
if(!inputUrl.match(VALIDATE_URL)){
|
||||||
|
alert("잘못된 URL 값 입니다. 올바른 URL 값을 입력해주세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단축 URL 생성 요청 및 처리
|
||||||
|
var queryUrl="/short?url="+inputUrl;
|
||||||
|
fetch(queryUrl,{
|
||||||
|
method: "POST"
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if(!response.ok){
|
||||||
|
alert("통신 중에 오류가 발생했습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
// 응답된 데이터의 상태 코드가 success가 아니라면 알림창 출력
|
||||||
|
if(!data.status=="success"){
|
||||||
|
alert("URL 단축에 실패하였습니다. 다시 시도해주세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단축 URL 생성 성공
|
||||||
|
console.log("단축 성공 : "+data.shortUrl);
|
||||||
|
|
||||||
|
// 결과를 표시할 새 요소 생성
|
||||||
|
createShortResultElement(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("에러 발생 : ",error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
#short-list{
|
||||||
|
width:100%;
|
||||||
|
margin:10px 0px;
|
||||||
|
padding:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.short-item td{
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
.short-item td ul{
|
||||||
|
margin:3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<h3>HanGyub의 URL Shortener(단축기)</h3>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
정보 입력<br>
|
||||||
|
<input type="text" name="input_url" placeholder="단축할 원본 URL" size="50">
|
||||||
|
<button id="btn-gen-short">단축!</button>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<table border="1" id="short-list">
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelector("#btn-gen-short").addEventListener("click",function() { clickBtnGenShort(); });
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user