feat: CtLayout01 커스텀 콘텐츠 기능 개선
- fnCustomVideo 유튜브 팝업 함수 추가 - script/link exclude 속성 지원 - 전역 함수 등록 로직 통합 (registerGlobalFunctions) - CSS Selector injection 보안 취약점 수정 - 사용되지 않는 변수/props 제거 - DOMPurify exclude, defer 속성 허용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,13 @@ export const isExternalUrl = (url: string): boolean => {
|
||||
return url.startsWith('http://') || url.startsWith('https://')
|
||||
}
|
||||
|
||||
/**
|
||||
* 경로에 쿼리스트링을 추가하는 함수
|
||||
*/
|
||||
export const addQueryString = (path: string, queryString: string): string => {
|
||||
return queryString ? `${path}?${queryString}` : path
|
||||
}
|
||||
|
||||
/**
|
||||
* 게임 도메인을 추출하는 함수
|
||||
* 서버와 클라이언트 환경에서 모두 동작
|
||||
@@ -95,55 +102,83 @@ export const getPathAfterLanguage = (url: string): string => {
|
||||
}
|
||||
|
||||
/**
|
||||
* intro page_url을 기반으로 리다이렉트 경로를 생성하는 공통 함수
|
||||
* @param introPageUrl - intro.page_url 값
|
||||
* @param langCode - 현재 언어 코드
|
||||
* @param currentUrl - 현재 접근한 URL (쿼리스트링 포함, 옵션)
|
||||
* @returns 최종 리다이렉트 경로 (없으면 기본값 /home)
|
||||
* URL을 파싱하여 최종 경로를 반환하는 함수
|
||||
* 경로가 '/' 또는 ''이고 인트로 URL이 있으면 인트로로 리다이렉트,
|
||||
* 그렇지 않으면 언어 코드를 추가한 URL을 반환
|
||||
* SSR과 CSR 모두에서 작동
|
||||
* @param url - URL 문자열
|
||||
* @param finalLocale - 최종 언어 코드
|
||||
* @param langCodes - 지원하는 언어 코드 배열
|
||||
* @param ISO_LANGUAGE_CODES - ISO 언어 코드 배열
|
||||
* @param introPageUrl - 인트로 페이지 URL (선택)
|
||||
* @returns 최종 URL 문자열
|
||||
*/
|
||||
export const getIntroRedirectPath = (
|
||||
introPageUrl: string | undefined | null,
|
||||
langCode: string,
|
||||
currentUrl?: string
|
||||
export const parseUrl = (
|
||||
url: string,
|
||||
finalLocale: string,
|
||||
langCodes: string[],
|
||||
ISO_LANGUAGE_CODES: string[],
|
||||
introPageUrl?: string
|
||||
): string => {
|
||||
// 기본값: /langCode/home
|
||||
let defaultPath = `/${langCode}/home`
|
||||
const [pathPart, queryString = ''] = url.split('?')
|
||||
const pathSegments = pathPart.split('/').filter(Boolean)
|
||||
const currentLocale = pathSegments[0]
|
||||
const isKnownLocale =
|
||||
langCodes.includes(currentLocale) ||
|
||||
ISO_LANGUAGE_CODES.includes(currentLocale)
|
||||
const isEmptyPath =
|
||||
pathSegments.length === 0 || (pathSegments.length === 1 && isKnownLocale)
|
||||
|
||||
if (introPageUrl && introPageUrl.trim() !== '') {
|
||||
if (isExternalUrl(introPageUrl)) {
|
||||
// 외부 URL인 경우 그대로 사용
|
||||
defaultPath = introPageUrl
|
||||
} else {
|
||||
// 내부 경로인 경우 언어 코드 패턴 확인
|
||||
const normalizedIntroUrl = introPageUrl.split('?')[0] // 쿼리스트링 제외
|
||||
const languagePattern = /^\/[a-z]{2}(-[a-z]{2})?(\/|$)/
|
||||
const hasLanguageCode = languagePattern.test(normalizedIntroUrl)
|
||||
// 경로가 '/' 또는 ''인 경우 인트로 URL 처리
|
||||
if (isEmptyPath) {
|
||||
const hasIntroUrl = introPageUrl?.trim()
|
||||
if (hasIntroUrl) {
|
||||
// 외부 URL인 경우 그대로 반환
|
||||
if (isExternalUrl(introPageUrl)) {
|
||||
return introPageUrl
|
||||
}
|
||||
// 내부 경로인 경우 언어 코드 추가
|
||||
// 인트로 URL이 이미 언어 코드로 시작하는지 확인
|
||||
const introPathSegments = introPageUrl.split('/').filter(Boolean)
|
||||
const introFirstSegment = introPathSegments[0]
|
||||
const introHasLocale =
|
||||
introFirstSegment &&
|
||||
(langCodes.includes(introFirstSegment) ||
|
||||
ISO_LANGUAGE_CODES.includes(introFirstSegment))
|
||||
|
||||
if (hasLanguageCode) {
|
||||
// 이미 언어 코드가 있으면 그대로 사용
|
||||
defaultPath = introPageUrl
|
||||
let introPath: string
|
||||
if (introHasLocale) {
|
||||
// 이미 언어 코드가 있으면 언어 코드만 교체
|
||||
introPath = '/' + [finalLocale, ...introPathSegments.slice(1)].join('/')
|
||||
} else {
|
||||
// 언어 코드가 없으면 추가
|
||||
const pathWithSlash = normalizedIntroUrl.startsWith('/')
|
||||
? normalizedIntroUrl
|
||||
: `/${normalizedIntroUrl}`
|
||||
defaultPath = `/${langCode}${pathWithSlash}`
|
||||
|
||||
// 쿼리스트링이 있으면 다시 추가
|
||||
if (introPageUrl.includes('?')) {
|
||||
defaultPath += '?' + introPageUrl.split('?')[1]
|
||||
}
|
||||
introPath = `/${finalLocale}${introPageUrl}`
|
||||
}
|
||||
return addQueryString(introPath, queryString)
|
||||
}
|
||||
// 인트로 URL이 없으면 기본 홈 경로 반환
|
||||
return addQueryString(`/${finalLocale}/home`, queryString)
|
||||
}
|
||||
|
||||
// intro에 쿼리스트링이 없고, 현재 URL에 쿼리스트링이 있으면 추가
|
||||
if (!defaultPath.includes('?') && currentUrl && currentUrl.includes('?')) {
|
||||
const queryString = currentUrl.split('?')[1]
|
||||
if (queryString) {
|
||||
defaultPath += '?' + queryString
|
||||
// 리다이렉트 경로 생성
|
||||
const remainingPath = pathSegments.slice(1)
|
||||
const hasRemainingPath = remainingPath.length > 0
|
||||
let newPath: string
|
||||
|
||||
if (isKnownLocale) {
|
||||
// 현재 언어 코드와 최종 언어가 같으면 리다이렉트 불필요
|
||||
if (currentLocale === finalLocale) return url
|
||||
|
||||
// 유효한 언어 코드가 있지만 다른 언어인 경우
|
||||
if (hasRemainingPath) {
|
||||
// 경로가 있으면 언어 코드만 교체 (/vi/story -> /finalLocale/story)
|
||||
newPath = '/' + [finalLocale, ...remainingPath].join('/')
|
||||
}
|
||||
} else {
|
||||
// 언어 코드가 없거나 유효하지 않은 경우: 언어 코드를 앞에 추가
|
||||
const pathWithoutSlash = pathPart === '/' ? '' : pathPart
|
||||
newPath = `/${finalLocale}${pathWithoutSlash}`
|
||||
}
|
||||
|
||||
return defaultPath
|
||||
return addQueryString(newPath, queryString)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user