@coldsurf/shared-utils
Advanced tools
+1
-1
@@ -1,1 +0,1 @@ | ||
| var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`zod`)),l=s(require(`jwt-decode`)),u=s(require(`date-fns`)),d=s(require(`slugify`)),f=s(require(`date-fns-tz`)),p=s(require(`date-fns/locale`)),m=`1632802589`,h=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${m}`,g=`COLDSURF`,_=`com.fstvllife.android`,v=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,y=`COLDSURF`,b={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},x=`https://coldsurf.io`,S=c.z.union([c.z.literal(`google`),c.z.literal(`apple`),c.z.literal(`email`)]);function C(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const w=e=>{try{let t=(0,l.jwtDecode)(e);return t}catch(e){return console.error(`Error decoding JWT:`,e),null}};function T(e){return{"@context":`https://schema.org`,"@type":`MusicEvent`,url:e.url,name:e.name,startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:[{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}}],image:e.images,description:e.description,offers:e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:e.url,validFrom:e.validFrom,name:e.name})),organizer:{"@type":`Organization`,name:e.venue.name}}}var E=class{baseData;constructor({baseData:e}){this.baseData=e}generateMetadata(e){let{icons:t,metadataBase:n,appLinks:r,keywords:i,twitter:a,openGraph:o}=this.baseData,s=[{url:`https://coldsurf.io/icons/favicon.ico`}],c={...e,icons:t,metadataBase:n,appLinks:r,keywords:[...e.keywords??[],...i],twitter:a,openGraph:{siteName:o?.siteName,images:Array.isArray(o?.images)&&o.images.length>0?o.images:s,title:o?.title,description:o?.description,...e.openGraph},locale:`ko`};return c}generateLdJson(e){switch(e.type){case`WebSite`:return{"@context":`https://schema.org`,"@type":`WebSite`,url:e.url,name:e.name};case`MusicEvent`:return T(e);case`Brand`:return{"@context":`https://schema.org`,"@type":`Brand`,name:e.name,image:e.image,logo:e.logo,url:e.url,sameAs:e.sameAs};case`Place`:return{"@context":`https://schema.org`,"@type":`Place`,address:e.address,event:e.events.map(e=>T(e)),geo:{"@type":`GeoCoordinates`,latitude:e.latitude,longitude:e.longitude},name:e.name,url:e.url,description:e.description};case`PerformingGroup`:return{"@context":`https://schema.org`,"@type":`PerformingGroup`,image:e.image,name:e.name,url:e.url,event:e.events.map(e=>T(e))};case`WebPageAbout`:return{"@context":`https://schema.org`,name:e.name,"@type":`AboutPage`,url:e.url,image:e.image,sameAs:e.sameAs,description:e.description};default:return{}}}};function D(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function O(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function k(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const A=e=>(0,d.default)(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function j(e,t){let n=I(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const M=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function N(e){return M.reduce((e,[t,n])=>e.replace(t,n),e)}function P(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function F(e){let t=Number((0,u.format)(e,`d`)),n=(0,u.format)(e,`MMM`).toLowerCase(),r=P(t);return`${t}${r}-${n}`}const I=e=>{let t=(0,d.default)(N(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},L=e=>{let t=(0,d.default)(N(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},R=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${F(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=(0,d.default)(N(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function z({title:e,date:t,venueName:n,area:r},i){let a=R({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const B=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},V={getEventCategoryUIName:B},H=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},U={getLocationCityUIName:H},W=`Asia/Seoul`;function G(e,t=new Date){return!(0,u.isSameYear)(e,t)}function K(e,t){let n=(0,f.toZonedTime)(e,W),r=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>G(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return(0,u.format)(n,e,{locale:p.enUS})}let i=(()=>G(n)?r===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:r===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return(0,u.format)(n,i,{locale:p.ko})}function q({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=(0,u.parse)(e,`yyyyMMdd`,new Date),n=(0,u.addDays)(t,1),r=(0,f.fromZonedTime)(t,W),i=(0,f.fromZonedTime)(n,W);return[r,i]}function J(e){let t=e?.yyyymmdd?(0,u.parse)(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,n=(0,u.getDay)(t),r=(5-n+7)%7,i=(0,u.startOfDay)((0,u.addDays)(t,r)),a=(0,u.addDays)(i,1),o=(0,u.addDays)(i,2),s=(0,u.addDays)(i,3);return[{label:`FRIDAY`,utcStart:(0,f.fromZonedTime)(i,W),utcEnd:(0,f.fromZonedTime)(a,W)},{label:`SATURDAY`,utcStart:(0,f.fromZonedTime)(a,W),utcEnd:(0,f.fromZonedTime)(o,W)},{label:`SUNDAY`,utcStart:(0,f.fromZonedTime)(o,W),utcEnd:(0,f.fromZonedTime)(s,W)}]}const Y={parseEventDate:K,toUTCDayRangeFromYYYYMMDD:q,getWeekendUTCStartDates:J};function X(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function Z(e){try{let t=new URL(e);return t.pathname=X(t.pathname),t.toString()}catch{return X(e)}}function Q(e){return e.includes(`%`)}function $(e){return/%25(25)+/i.test(e)}exports.APP_STORE_ID=m,exports.APP_STORE_URL=h,exports.COLDSURF_WEB_URL=x,exports.NextMetadataGenerator=E,exports.PLAYSTORE_APP_NAME=y,exports.PLAYSTORE_PACKAGE=_,exports.PLAYSTORE_URL=v,exports.SERVICE_NAME=g,exports.SNS_LINKS=b,exports.createConcertSlug=R,exports.createSlug=I,exports.createSlugHashtag=L,exports.dateUtils=Y,exports.decodeJwt=w,exports.eventCategoryUtils=V,exports.fullyDecodePathname=Z,exports.fullyDecodeURI=X,exports.generateConcertSlug=z,exports.generateSlug=j,exports.generateUUID=O,exports.getRandomInt=D,exports.getSafeSlug=A,exports.isDoubleEncoded=$,exports.isEncoded=Q,exports.locationCityUtils=U,exports.loginProviderSchema=S,exports.pickFile=C,exports.tryParse=k; | ||
| var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`zod`)),l=s(require(`jwt-decode`)),u=s(require(`date-fns`)),d=s(require(`slugify`)),f=s(require(`date-fns-tz`)),p=s(require(`date-fns/locale`)),m=`1632802589`,h=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${m}`,ee=`COLDSURF`,te=`com.fstvllife.android`,g=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,_=`COLDSURF`,v={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},y=`https://coldsurf.io`,b={ko:`ko_KR`,en:`en_US`},x=`https://schema.org`;function ne(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function S(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function C(e,t){return t.startsWith(`http`)?t:S(e,t)}function re(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function w(e,t){return C(e,t)}function T(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?S(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function E(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function D(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function O(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function k(e,t){let n=e.name,r=e.image?C(t,e.image):void 0,i=e.url?w(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),s=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,c=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:s,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:s,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:c,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:c,image:r,url:i,sameAs:a,datePublished:o}}}function A(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:k(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function j(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:w(t,e.path)}))}}function M(e,t){return{"@type":`MusicEvent`,name:e.name,url:w(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>C(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:C(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function N(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let i=t.baseUrl.replace(/\/$/,``),a=`${i}/#org`,o=`${i}/#editor`,s=`${i}/#website`,c=[];for(let l of r)switch(l.type){case`WebSite`:c.push(D(t,n,s,a));break;case`Article`:{let r=O(e,t,n,a,o);r&&c.push(r);break}case`Review`:c.push(A(l.itemReviewed,l.rating,e,t,n,i,a,o));break;case`CreativeWork`:c.push(k(l.item,i));break;case`EventPage`:c.push(M(l.event,i));break;case`Breadcrumb`:c.push(j(l.items,i));break}if(c.length===0)return[];let l=[],u=T(t,a);u&&l.push(u);let d=E(t,o);d&&l.push(d);let f={"@context":x,"@graph":[...l,...c]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(f)}]}function P(e,t){let n=e.lang??`ko`,r=RegExp(`\\b${ne(t.name)}\\b`).test(e.title),i=r?e.title:`${e.title} | ${t.name}`,a=w(t.baseUrl,e.path),o=e.alternates??[],s={...b,...t.locales},c=[{rel:`canonical`,href:a}];if(o.length>0){c.push({rel:`alternate`,hreflang:n,href:a});for(let e of o)c.push({rel:`alternate`,hreflang:e.lang,href:w(t.baseUrl,e.path)});let r=o.find(e=>e.lang===`ko`)?.path??e.path;c.push({rel:`alternate`,hreflang:`x-default`,href:w(t.baseUrl,r)})}let l=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:i},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:a},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:s[n]}],u=[...new Set([...e.keywords??[],...t.keywords??[]])];u.length>0&&l.push({name:`keywords`,content:u.join(`, `)});let d=e.image??t.defaultImage,f=d?re(C(t.baseUrl,d.path),t.assetVersion,d.path.startsWith(`http`)):void 0;d&&f&&(l.push({property:`og:image`,content:f}),d.type&&l.push({property:`og:image:type`,content:d.type}),d.width&&l.push({property:`og:image:width`,content:String(d.width)}),d.height&&l.push({property:`og:image:height`,content:String(d.height)}),d.alt&&l.push({property:`og:image:alt`,content:d.alt}),l.push({name:`twitter:image`,content:f}),d.alt&&l.push({name:`twitter:image:alt`,content:d.alt})),t.logoPath&&l.push({property:`og:logo`,content:S(t.baseUrl,t.logoPath)});let p=!!d&&!!d.width&&!!d.height&&d.width>=d.height*1.5,m=p?`summary_large_image`:`summary`;l.push({name:`twitter:card`,content:m},{name:`twitter:title`,content:i},{name:`twitter:description`,content:e.description});for(let e of o)e.lang!==n&&l.push({property:`og:locale:alternate`,content:s[e.lang]});if(e.article){l.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&l.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&l.push({property:`article:author`,content:e.article.author}),e.article.section&&l.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])l.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(l.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),l.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&l.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(l.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&l.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&l.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let h=N(e,t,{url:a,fullTitle:i,imageUrl:f,lang:n});return{title:i,htmlLang:n,meta:l,link:c,script:h}}function F(e){return{buildTags:t=>P(t,e)}}const I=c.z.union([c.z.literal(`google`),c.z.literal(`apple`),c.z.literal(`email`)]);function L(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const R=e=>{try{let t=(0,l.jwtDecode)(e);return t}catch(e){return console.error(`Error decoding JWT:`,e),null}};function z(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function B(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function V(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const H=e=>(0,d.default)(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function U(e,t){let n=q(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const ie=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function W(e){return ie.reduce((e,[t,n])=>e.replace(t,n),e)}function G(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function K(e){let t=Number((0,u.format)(e,`d`)),n=(0,u.format)(e,`MMM`).toLowerCase(),r=G(t);return`${t}${r}-${n}`}const q=e=>{let t=(0,d.default)(W(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},J=e=>{let t=(0,d.default)(W(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},Y=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${K(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=(0,d.default)(W(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function ae({title:e,date:t,venueName:n,area:r},i){let a=Y({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const oe=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},se={getEventCategoryUIName:oe},ce=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},le={getLocationCityUIName:ce},X=`Asia/Seoul`;function Z(e,t=new Date){return!(0,u.isSameYear)(e,t)}function ue(e,t){let n=(0,f.toZonedTime)(e,X),r=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>Z(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return(0,u.format)(n,e,{locale:p.enUS})}let i=(()=>Z(n)?r===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:r===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return(0,u.format)(n,i,{locale:p.ko})}function Q({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=(0,u.parse)(e,`yyyyMMdd`,new Date),n=(0,u.addDays)(t,1),r=(0,f.fromZonedTime)(t,X),i=(0,f.fromZonedTime)(n,X);return[r,i]}function de(e){let t=e?.yyyymmdd?(0,u.parse)(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,n=(0,u.getDay)(t),r=(5-n+7)%7,i=(0,u.startOfDay)((0,u.addDays)(t,r)),a=(0,u.addDays)(i,1),o=(0,u.addDays)(i,2),s=(0,u.addDays)(i,3);return[{label:`FRIDAY`,utcStart:(0,f.fromZonedTime)(i,X),utcEnd:(0,f.fromZonedTime)(a,X)},{label:`SATURDAY`,utcStart:(0,f.fromZonedTime)(a,X),utcEnd:(0,f.fromZonedTime)(o,X)},{label:`SUNDAY`,utcStart:(0,f.fromZonedTime)(o,X),utcEnd:(0,f.fromZonedTime)(s,X)}]}const fe={parseEventDate:ue,toUTCDayRangeFromYYYYMMDD:Q,getWeekendUTCStartDates:de};function $(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function pe(e){try{let t=new URL(e);return t.pathname=$(t.pathname),t.toString()}catch{return $(e)}}function me(e){return e.includes(`%`)}function he(e){return/%25(25)+/i.test(e)}exports.APP_STORE_ID=m,exports.APP_STORE_URL=h,exports.COLDSURF_WEB_URL=y,exports.PLAYSTORE_APP_NAME=_,exports.PLAYSTORE_PACKAGE=te,exports.PLAYSTORE_URL=g,exports.SERVICE_NAME=ee,exports.SNS_LINKS=v,exports.buildSeoTags=P,exports.createConcertSlug=Y,exports.createSeo=F,exports.createSlug=q,exports.createSlugHashtag=J,exports.dateUtils=fe,exports.decodeJwt=R,exports.eventCategoryUtils=se,exports.fullyDecodePathname=pe,exports.fullyDecodeURI=$,exports.generateConcertSlug=ae,exports.generateSlug=U,exports.generateUUID=B,exports.getRandomInt=z,exports.getSafeSlug=H,exports.isDoubleEncoded=he,exports.isEncoded=me,exports.locationCityUtils=le,exports.loginProviderSchema=I,exports.pickFile=L,exports.tryParse=V; |
+215
-292
@@ -29,76 +29,67 @@ import { z } from "zod"; | ||
| //#endregion | ||
| //#region src/types/auth.d.ts | ||
| declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>; | ||
| type LoginProvider = z.infer<typeof loginProviderSchema>; | ||
| //# sourceMappingURL=auth.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.file.d.ts | ||
| declare function pickFile(onChange: (e: Event) => void | Promise<void>): void; | ||
| //# sourceMappingURL=utils.file.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.jwt.d.ts | ||
| interface JwtPayload { | ||
| sub: string; | ||
| //#region src/metadata/core.d.ts | ||
| type Lang = 'ko' | 'en'; | ||
| type SeoImage = { | ||
| /** Absolute URL or path starting with `/`. */ | ||
| path: string; | ||
| width?: number; | ||
| height?: number; | ||
| type?: string; | ||
| alt?: string; | ||
| }; | ||
| type ArticleMeta = { | ||
| /** ISO 8601 timestamp. */ | ||
| publishedTime: string; | ||
| /** ISO 8601 timestamp. */ | ||
| modifiedTime?: string; | ||
| author?: string; | ||
| section?: string; | ||
| tags?: string[]; | ||
| }; | ||
| /** | ||
| * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다. | ||
| * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등). | ||
| */ | ||
| type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events'; | ||
| /** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */ | ||
| type JsonLdWork = { | ||
| kind: CreativeWorkKind; | ||
| name: string; | ||
| email: string; | ||
| exp: number; | ||
| [key: string]: unknown; | ||
| } | ||
| declare const decodeJwt: (token: string) => JwtPayload | null; | ||
| //#endregion | ||
| //#region src/utils/utils.metadata.d.ts | ||
| type BaseData = { | ||
| keywords: string[]; | ||
| icons: { | ||
| icon: string; | ||
| shortcut: string; | ||
| apple: string; | ||
| }; | ||
| metadataBase: URL; | ||
| appLinks?: { | ||
| ios?: { | ||
| app_name: string; | ||
| app_store_id: string; | ||
| url: string; | ||
| }; | ||
| android?: { | ||
| package: string; | ||
| url?: string | URL; | ||
| class?: string; | ||
| app_name?: string; | ||
| }; | ||
| }; | ||
| twitter?: { | ||
| app?: { | ||
| id: { | ||
| /** | ||
| * app store id | ||
| */ | ||
| iphone: string; | ||
| }; | ||
| name: string; | ||
| url: { | ||
| /** | ||
| * app store url | ||
| */ | ||
| iphone: string; | ||
| }; | ||
| }; | ||
| card: 'app'; | ||
| }; | ||
| openGraph?: { | ||
| siteName: string; | ||
| title: string; | ||
| description: string; | ||
| images?: { | ||
| url: string; | ||
| }[]; | ||
| }; | ||
| /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */ | ||
| by?: string; | ||
| /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */ | ||
| year?: number | string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| image?: string; | ||
| /** 작품 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url?: string; | ||
| /** 외부 출처 — bandcamp · spotify · imdb 등. */ | ||
| sameAs?: string[]; | ||
| }; | ||
| type CustomMusicEvent = { | ||
| type: 'MusicEvent'; | ||
| type JsonLdRating = { | ||
| value: number; | ||
| best?: number; | ||
| worst?: number; | ||
| }; | ||
| type JsonLdEventOffer = { | ||
| price: number; | ||
| currency: string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 티켓 오픈 시각. */ | ||
| validFrom: string; | ||
| name?: string; | ||
| }; | ||
| /** | ||
| * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와 | ||
| * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다. | ||
| */ | ||
| type JsonLdEvent = { | ||
| name: string; | ||
| /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */ | ||
| startDate: string; | ||
| endDate: string; | ||
| /** ISO 8601 */ | ||
| endDate?: string; | ||
| venue: { | ||
@@ -110,233 +101,165 @@ name: string; | ||
| }; | ||
| images: string[]; | ||
| /** Path(`/...`) 또는 절대 URL 목록. */ | ||
| images?: string[]; | ||
| description?: string; | ||
| offers?: JsonLdEventOffer[]; | ||
| /** 주최자명. 미지정 시 `venue.name` 으로 대체. */ | ||
| organizer?: string; | ||
| }; | ||
| /** | ||
| * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다. | ||
| * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다. | ||
| */ | ||
| type JsonLd = { | ||
| type: 'WebSite'; | ||
| } | ||
| /** `input.article` 에서 파생 — article 없으면 무시. */ | { | ||
| type: 'Article'; | ||
| } | { | ||
| type: 'Review'; | ||
| itemReviewed: JsonLdWork; | ||
| rating?: JsonLdRating; | ||
| } | { | ||
| type: 'CreativeWork'; | ||
| item: JsonLdWork; | ||
| } | ||
| /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | { | ||
| type: 'EventPage'; | ||
| event: JsonLdEvent; | ||
| } | { | ||
| type: 'Breadcrumb'; | ||
| items: { | ||
| name: string; | ||
| path: string; | ||
| }[]; | ||
| }; | ||
| type PageSeoInput = { | ||
| title: string; | ||
| description: string; | ||
| offers: { | ||
| price: number; | ||
| currency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name?: string; | ||
| path: string; | ||
| lang?: Lang; | ||
| /** Alternate language versions for this page. Emits hreflang link tags. */ | ||
| alternates?: { | ||
| lang: Lang; | ||
| path: string; | ||
| }[]; | ||
| /** When set, og:type switches to `article` and article:* tags are emitted. */ | ||
| article?: ArticleMeta; | ||
| /** Override the default OG/Twitter share image for this page. */ | ||
| image?: SeoImage; | ||
| /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */ | ||
| jsonLd?: JsonLd[]; | ||
| /** | ||
| * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks` | ||
| * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시. | ||
| */ | ||
| appLinks?: { | ||
| iosUrl?: string; | ||
| androidUrl?: string; | ||
| }; | ||
| }; | ||
| declare class NextMetadataGenerator { | ||
| baseData: BaseData; | ||
| constructor({ | ||
| baseData | ||
| }: { | ||
| baseData: BaseData; | ||
| }); | ||
| generateMetadata<T>(additionalMetadata: T): T; | ||
| generateLdJson(params: { | ||
| type: 'WebSite'; | ||
| url: string; | ||
| type SiteConfig = { | ||
| /** Display name appended to every page title. */ | ||
| name: string; | ||
| /** Origin used to resolve absolute URLs. Trailing slash is normalized. */ | ||
| baseUrl: string; | ||
| /** Default OG/Twitter image used when a page does not override it. */ | ||
| defaultImage?: SeoImage; | ||
| /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */ | ||
| logoPath?: string; | ||
| /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */ | ||
| locales?: Partial<Record<Lang, string>>; | ||
| /** Site-wide keywords merged into every page's `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** | ||
| * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter | ||
| * image URLs (paths, not external `http` URLs). Bump it when an image is replaced | ||
| * in place under the same filename so social scrapers (Threads/Slack/iMessage 등) | ||
| * treat it as a new URL instead of serving their stale cache. | ||
| */ | ||
| assetVersion?: string; | ||
| /** Publishing organization — emitted as the `Organization` JSON-LD node. */ | ||
| publisher?: { | ||
| name: string; | ||
| } | CustomMusicEvent | { | ||
| type: 'Brand'; | ||
| url?: string; | ||
| logoPath?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */ | ||
| editor?: { | ||
| name: string; | ||
| image: string; | ||
| logo: string; | ||
| url: string; | ||
| sameAs: string[]; | ||
| } | { | ||
| type: 'Place'; | ||
| address: string; | ||
| latitude: number; | ||
| longitude: number; | ||
| name: string; | ||
| url: string; | ||
| events: CustomMusicEvent[]; | ||
| description: string; | ||
| } | { | ||
| type: 'PerformingGroup'; | ||
| image: string; | ||
| name: string; | ||
| url: string; | ||
| events: CustomMusicEvent[]; | ||
| } | { | ||
| type: 'WebPageAbout'; | ||
| name: string; | ||
| url: string; | ||
| image: string; | ||
| sameAs: string[]; | ||
| description: string; | ||
| }): { | ||
| '@context': "https://schema.org"; | ||
| '@type': "MusicEvent"; | ||
| url: string; | ||
| name: string; | ||
| startDate: string; | ||
| endDate: string; | ||
| eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode"; | ||
| eventStatus: "https://schema.org/EventScheduled"; | ||
| location: { | ||
| '@type': "Place"; | ||
| name: string; | ||
| address: string; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| }[]; | ||
| image: string[]; | ||
| description: string; | ||
| offers: { | ||
| '@type': "Offer"; | ||
| availability: "https://schema.org/InStock"; | ||
| price: number; | ||
| priceCurrency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name: string | undefined; | ||
| }[]; | ||
| organizer: { | ||
| '@type': "Organization"; | ||
| name: string; | ||
| url?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** | ||
| * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit. | ||
| * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다 | ||
| * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다. | ||
| */ | ||
| appLinks?: { | ||
| ios?: { | ||
| appStoreId: string; | ||
| appName: string; | ||
| }; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "WebSite"; | ||
| url: string; | ||
| name: string; | ||
| image?: undefined; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "Brand"; | ||
| name: string; | ||
| image: string; | ||
| logo: string; | ||
| url: string; | ||
| sameAs: string[]; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "Place"; | ||
| address: string; | ||
| event: { | ||
| '@context': "https://schema.org"; | ||
| '@type': "MusicEvent"; | ||
| url: string; | ||
| name: string; | ||
| startDate: string; | ||
| endDate: string; | ||
| eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode"; | ||
| eventStatus: "https://schema.org/EventScheduled"; | ||
| location: { | ||
| '@type': "Place"; | ||
| name: string; | ||
| address: string; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| }[]; | ||
| image: string[]; | ||
| description: string; | ||
| offers: { | ||
| '@type': "Offer"; | ||
| availability: "https://schema.org/InStock"; | ||
| price: number; | ||
| priceCurrency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name: string | undefined; | ||
| }[]; | ||
| organizer: { | ||
| '@type': "Organization"; | ||
| name: string; | ||
| }; | ||
| }[]; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| android?: { | ||
| packageName: string; | ||
| appName?: string; | ||
| }; | ||
| name: string; | ||
| url: string; | ||
| description: string; | ||
| image?: undefined; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "PerformingGroup"; | ||
| image: string; | ||
| name: string; | ||
| url: string; | ||
| event: { | ||
| '@context': "https://schema.org"; | ||
| '@type': "MusicEvent"; | ||
| url: string; | ||
| name: string; | ||
| startDate: string; | ||
| endDate: string; | ||
| eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode"; | ||
| eventStatus: "https://schema.org/EventScheduled"; | ||
| location: { | ||
| '@type': "Place"; | ||
| name: string; | ||
| address: string; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| }[]; | ||
| image: string[]; | ||
| description: string; | ||
| offers: { | ||
| '@type': "Offer"; | ||
| availability: "https://schema.org/InStock"; | ||
| price: number; | ||
| priceCurrency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name: string | undefined; | ||
| }[]; | ||
| organizer: { | ||
| '@type': "Organization"; | ||
| name: string; | ||
| }; | ||
| }[]; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| address?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| name: string; | ||
| '@type': "AboutPage"; | ||
| url: string; | ||
| image: string; | ||
| sameAs: string[]; | ||
| description: string; | ||
| logo?: undefined; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| } | { | ||
| '@context'?: undefined; | ||
| '@type'?: undefined; | ||
| url?: undefined; | ||
| name?: undefined; | ||
| image?: undefined; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| }; | ||
| }; | ||
| type SeoMeta = { | ||
| name?: string; | ||
| property?: string; | ||
| content: string; | ||
| }; | ||
| type SeoLink = { | ||
| rel: string; | ||
| href: string; | ||
| hreflang?: string; | ||
| }; | ||
| type SeoScript = { | ||
| type: 'application/ld+json'; | ||
| innerHTML: string; | ||
| }; | ||
| type SeoTags = { | ||
| title: string; | ||
| htmlLang: Lang; | ||
| meta: SeoMeta[]; | ||
| link: SeoLink[]; | ||
| /** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */ | ||
| script: SeoScript[]; | ||
| }; | ||
| declare function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags; | ||
| /** | ||
| * `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구 | ||
| * `NextMetadataGenerator` 의 "생성자 1회 주입" 사용감을 stateful 클래스 없이 순수 함수로 재현한다. | ||
| * | ||
| * const seo = createSeo(siteConfig) | ||
| * seo.buildTags({ title, description, path, jsonLd: [...] }) | ||
| */ | ||
| declare function createSeo(site: SiteConfig): { | ||
| buildTags: (input: PageSeoInput) => SeoTags; | ||
| }; | ||
| //# sourceMappingURL=core.d.ts.map | ||
| //#endregion | ||
| //#region src/types/auth.d.ts | ||
| declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>; | ||
| type LoginProvider = z.infer<typeof loginProviderSchema>; | ||
| //# sourceMappingURL=auth.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.file.d.ts | ||
| declare function pickFile(onChange: (e: Event) => void | Promise<void>): void; | ||
| //# sourceMappingURL=utils.file.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.jwt.d.ts | ||
| interface JwtPayload { | ||
| sub: string; | ||
| name: string; | ||
| email: string; | ||
| exp: number; | ||
| [key: string]: unknown; | ||
| } | ||
| declare const decodeJwt: (token: string) => JwtPayload | null; | ||
| //#endregion | ||
@@ -457,3 +380,3 @@ //#region src/utils/utils.number.d.ts | ||
| //#endregion | ||
| export { APP_STORE_ID, APP_STORE_URL, COLDSURF_WEB_URL, LoginProvider, NextMetadataGenerator, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, SERVICE_NAME, SNS_LINKS, TryParseOptions, createConcertSlug, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse }; | ||
| export { APP_STORE_ID, APP_STORE_URL, ArticleMeta, COLDSURF_WEB_URL, CreativeWorkKind, JsonLd, JsonLdEvent, JsonLdEventOffer, JsonLdRating, JsonLdWork, Lang, LoginProvider, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, PageSeoInput, SERVICE_NAME, SNS_LINKS, SeoImage, SeoLink, SeoMeta, SeoScript, SeoTags, SiteConfig, TryParseOptions, buildSeoTags, createConcertSlug, createSeo, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.metadata.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"} | ||
| {"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/metadata/core.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"} |
+215
-292
@@ -29,76 +29,67 @@ import { z } from "zod"; | ||
| //#endregion | ||
| //#region src/types/auth.d.ts | ||
| declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>; | ||
| type LoginProvider = z.infer<typeof loginProviderSchema>; | ||
| //# sourceMappingURL=auth.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.file.d.ts | ||
| declare function pickFile(onChange: (e: Event) => void | Promise<void>): void; | ||
| //# sourceMappingURL=utils.file.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.jwt.d.ts | ||
| interface JwtPayload { | ||
| sub: string; | ||
| //#region src/metadata/core.d.ts | ||
| type Lang = 'ko' | 'en'; | ||
| type SeoImage = { | ||
| /** Absolute URL or path starting with `/`. */ | ||
| path: string; | ||
| width?: number; | ||
| height?: number; | ||
| type?: string; | ||
| alt?: string; | ||
| }; | ||
| type ArticleMeta = { | ||
| /** ISO 8601 timestamp. */ | ||
| publishedTime: string; | ||
| /** ISO 8601 timestamp. */ | ||
| modifiedTime?: string; | ||
| author?: string; | ||
| section?: string; | ||
| tags?: string[]; | ||
| }; | ||
| /** | ||
| * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다. | ||
| * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등). | ||
| */ | ||
| type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events'; | ||
| /** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */ | ||
| type JsonLdWork = { | ||
| kind: CreativeWorkKind; | ||
| name: string; | ||
| email: string; | ||
| exp: number; | ||
| [key: string]: unknown; | ||
| } | ||
| declare const decodeJwt: (token: string) => JwtPayload | null; | ||
| //#endregion | ||
| //#region src/utils/utils.metadata.d.ts | ||
| type BaseData = { | ||
| keywords: string[]; | ||
| icons: { | ||
| icon: string; | ||
| shortcut: string; | ||
| apple: string; | ||
| }; | ||
| metadataBase: URL; | ||
| appLinks?: { | ||
| ios?: { | ||
| app_name: string; | ||
| app_store_id: string; | ||
| url: string; | ||
| }; | ||
| android?: { | ||
| package: string; | ||
| url?: string | URL; | ||
| class?: string; | ||
| app_name?: string; | ||
| }; | ||
| }; | ||
| twitter?: { | ||
| app?: { | ||
| id: { | ||
| /** | ||
| * app store id | ||
| */ | ||
| iphone: string; | ||
| }; | ||
| name: string; | ||
| url: { | ||
| /** | ||
| * app store url | ||
| */ | ||
| iphone: string; | ||
| }; | ||
| }; | ||
| card: 'app'; | ||
| }; | ||
| openGraph?: { | ||
| siteName: string; | ||
| title: string; | ||
| description: string; | ||
| images?: { | ||
| url: string; | ||
| }[]; | ||
| }; | ||
| /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */ | ||
| by?: string; | ||
| /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */ | ||
| year?: number | string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| image?: string; | ||
| /** 작품 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url?: string; | ||
| /** 외부 출처 — bandcamp · spotify · imdb 등. */ | ||
| sameAs?: string[]; | ||
| }; | ||
| type CustomMusicEvent = { | ||
| type: 'MusicEvent'; | ||
| type JsonLdRating = { | ||
| value: number; | ||
| best?: number; | ||
| worst?: number; | ||
| }; | ||
| type JsonLdEventOffer = { | ||
| price: number; | ||
| currency: string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 티켓 오픈 시각. */ | ||
| validFrom: string; | ||
| name?: string; | ||
| }; | ||
| /** | ||
| * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와 | ||
| * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다. | ||
| */ | ||
| type JsonLdEvent = { | ||
| name: string; | ||
| /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */ | ||
| startDate: string; | ||
| endDate: string; | ||
| /** ISO 8601 */ | ||
| endDate?: string; | ||
| venue: { | ||
@@ -110,233 +101,165 @@ name: string; | ||
| }; | ||
| images: string[]; | ||
| /** Path(`/...`) 또는 절대 URL 목록. */ | ||
| images?: string[]; | ||
| description?: string; | ||
| offers?: JsonLdEventOffer[]; | ||
| /** 주최자명. 미지정 시 `venue.name` 으로 대체. */ | ||
| organizer?: string; | ||
| }; | ||
| /** | ||
| * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다. | ||
| * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다. | ||
| */ | ||
| type JsonLd = { | ||
| type: 'WebSite'; | ||
| } | ||
| /** `input.article` 에서 파생 — article 없으면 무시. */ | { | ||
| type: 'Article'; | ||
| } | { | ||
| type: 'Review'; | ||
| itemReviewed: JsonLdWork; | ||
| rating?: JsonLdRating; | ||
| } | { | ||
| type: 'CreativeWork'; | ||
| item: JsonLdWork; | ||
| } | ||
| /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | { | ||
| type: 'EventPage'; | ||
| event: JsonLdEvent; | ||
| } | { | ||
| type: 'Breadcrumb'; | ||
| items: { | ||
| name: string; | ||
| path: string; | ||
| }[]; | ||
| }; | ||
| type PageSeoInput = { | ||
| title: string; | ||
| description: string; | ||
| offers: { | ||
| price: number; | ||
| currency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name?: string; | ||
| path: string; | ||
| lang?: Lang; | ||
| /** Alternate language versions for this page. Emits hreflang link tags. */ | ||
| alternates?: { | ||
| lang: Lang; | ||
| path: string; | ||
| }[]; | ||
| /** When set, og:type switches to `article` and article:* tags are emitted. */ | ||
| article?: ArticleMeta; | ||
| /** Override the default OG/Twitter share image for this page. */ | ||
| image?: SeoImage; | ||
| /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */ | ||
| jsonLd?: JsonLd[]; | ||
| /** | ||
| * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks` | ||
| * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시. | ||
| */ | ||
| appLinks?: { | ||
| iosUrl?: string; | ||
| androidUrl?: string; | ||
| }; | ||
| }; | ||
| declare class NextMetadataGenerator { | ||
| baseData: BaseData; | ||
| constructor({ | ||
| baseData | ||
| }: { | ||
| baseData: BaseData; | ||
| }); | ||
| generateMetadata<T>(additionalMetadata: T): T; | ||
| generateLdJson(params: { | ||
| type: 'WebSite'; | ||
| url: string; | ||
| type SiteConfig = { | ||
| /** Display name appended to every page title. */ | ||
| name: string; | ||
| /** Origin used to resolve absolute URLs. Trailing slash is normalized. */ | ||
| baseUrl: string; | ||
| /** Default OG/Twitter image used when a page does not override it. */ | ||
| defaultImage?: SeoImage; | ||
| /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */ | ||
| logoPath?: string; | ||
| /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */ | ||
| locales?: Partial<Record<Lang, string>>; | ||
| /** Site-wide keywords merged into every page's `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** | ||
| * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter | ||
| * image URLs (paths, not external `http` URLs). Bump it when an image is replaced | ||
| * in place under the same filename so social scrapers (Threads/Slack/iMessage 등) | ||
| * treat it as a new URL instead of serving their stale cache. | ||
| */ | ||
| assetVersion?: string; | ||
| /** Publishing organization — emitted as the `Organization` JSON-LD node. */ | ||
| publisher?: { | ||
| name: string; | ||
| } | CustomMusicEvent | { | ||
| type: 'Brand'; | ||
| url?: string; | ||
| logoPath?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */ | ||
| editor?: { | ||
| name: string; | ||
| image: string; | ||
| logo: string; | ||
| url: string; | ||
| sameAs: string[]; | ||
| } | { | ||
| type: 'Place'; | ||
| address: string; | ||
| latitude: number; | ||
| longitude: number; | ||
| name: string; | ||
| url: string; | ||
| events: CustomMusicEvent[]; | ||
| description: string; | ||
| } | { | ||
| type: 'PerformingGroup'; | ||
| image: string; | ||
| name: string; | ||
| url: string; | ||
| events: CustomMusicEvent[]; | ||
| } | { | ||
| type: 'WebPageAbout'; | ||
| name: string; | ||
| url: string; | ||
| image: string; | ||
| sameAs: string[]; | ||
| description: string; | ||
| }): { | ||
| '@context': "https://schema.org"; | ||
| '@type': "MusicEvent"; | ||
| url: string; | ||
| name: string; | ||
| startDate: string; | ||
| endDate: string; | ||
| eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode"; | ||
| eventStatus: "https://schema.org/EventScheduled"; | ||
| location: { | ||
| '@type': "Place"; | ||
| name: string; | ||
| address: string; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| }[]; | ||
| image: string[]; | ||
| description: string; | ||
| offers: { | ||
| '@type': "Offer"; | ||
| availability: "https://schema.org/InStock"; | ||
| price: number; | ||
| priceCurrency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name: string | undefined; | ||
| }[]; | ||
| organizer: { | ||
| '@type': "Organization"; | ||
| name: string; | ||
| url?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** | ||
| * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit. | ||
| * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다 | ||
| * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다. | ||
| */ | ||
| appLinks?: { | ||
| ios?: { | ||
| appStoreId: string; | ||
| appName: string; | ||
| }; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "WebSite"; | ||
| url: string; | ||
| name: string; | ||
| image?: undefined; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "Brand"; | ||
| name: string; | ||
| image: string; | ||
| logo: string; | ||
| url: string; | ||
| sameAs: string[]; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "Place"; | ||
| address: string; | ||
| event: { | ||
| '@context': "https://schema.org"; | ||
| '@type': "MusicEvent"; | ||
| url: string; | ||
| name: string; | ||
| startDate: string; | ||
| endDate: string; | ||
| eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode"; | ||
| eventStatus: "https://schema.org/EventScheduled"; | ||
| location: { | ||
| '@type': "Place"; | ||
| name: string; | ||
| address: string; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| }[]; | ||
| image: string[]; | ||
| description: string; | ||
| offers: { | ||
| '@type': "Offer"; | ||
| availability: "https://schema.org/InStock"; | ||
| price: number; | ||
| priceCurrency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name: string | undefined; | ||
| }[]; | ||
| organizer: { | ||
| '@type': "Organization"; | ||
| name: string; | ||
| }; | ||
| }[]; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| android?: { | ||
| packageName: string; | ||
| appName?: string; | ||
| }; | ||
| name: string; | ||
| url: string; | ||
| description: string; | ||
| image?: undefined; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| '@type': "PerformingGroup"; | ||
| image: string; | ||
| name: string; | ||
| url: string; | ||
| event: { | ||
| '@context': "https://schema.org"; | ||
| '@type': "MusicEvent"; | ||
| url: string; | ||
| name: string; | ||
| startDate: string; | ||
| endDate: string; | ||
| eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode"; | ||
| eventStatus: "https://schema.org/EventScheduled"; | ||
| location: { | ||
| '@type': "Place"; | ||
| name: string; | ||
| address: string; | ||
| geo: { | ||
| '@type': "GeoCoordinates"; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| }[]; | ||
| image: string[]; | ||
| description: string; | ||
| offers: { | ||
| '@type': "Offer"; | ||
| availability: "https://schema.org/InStock"; | ||
| price: number; | ||
| priceCurrency: string; | ||
| url: string; | ||
| validFrom: string; | ||
| name: string | undefined; | ||
| }[]; | ||
| organizer: { | ||
| '@type': "Organization"; | ||
| name: string; | ||
| }; | ||
| }[]; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| address?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| } | { | ||
| '@context': "https://schema.org"; | ||
| name: string; | ||
| '@type': "AboutPage"; | ||
| url: string; | ||
| image: string; | ||
| sameAs: string[]; | ||
| description: string; | ||
| logo?: undefined; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| } | { | ||
| '@context'?: undefined; | ||
| '@type'?: undefined; | ||
| url?: undefined; | ||
| name?: undefined; | ||
| image?: undefined; | ||
| logo?: undefined; | ||
| sameAs?: undefined; | ||
| address?: undefined; | ||
| event?: undefined; | ||
| geo?: undefined; | ||
| description?: undefined; | ||
| }; | ||
| }; | ||
| type SeoMeta = { | ||
| name?: string; | ||
| property?: string; | ||
| content: string; | ||
| }; | ||
| type SeoLink = { | ||
| rel: string; | ||
| href: string; | ||
| hreflang?: string; | ||
| }; | ||
| type SeoScript = { | ||
| type: 'application/ld+json'; | ||
| innerHTML: string; | ||
| }; | ||
| type SeoTags = { | ||
| title: string; | ||
| htmlLang: Lang; | ||
| meta: SeoMeta[]; | ||
| link: SeoLink[]; | ||
| /** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */ | ||
| script: SeoScript[]; | ||
| }; | ||
| declare function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags; | ||
| /** | ||
| * `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구 | ||
| * `NextMetadataGenerator` 의 "생성자 1회 주입" 사용감을 stateful 클래스 없이 순수 함수로 재현한다. | ||
| * | ||
| * const seo = createSeo(siteConfig) | ||
| * seo.buildTags({ title, description, path, jsonLd: [...] }) | ||
| */ | ||
| declare function createSeo(site: SiteConfig): { | ||
| buildTags: (input: PageSeoInput) => SeoTags; | ||
| }; | ||
| //# sourceMappingURL=core.d.ts.map | ||
| //#endregion | ||
| //#region src/types/auth.d.ts | ||
| declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>; | ||
| type LoginProvider = z.infer<typeof loginProviderSchema>; | ||
| //# sourceMappingURL=auth.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.file.d.ts | ||
| declare function pickFile(onChange: (e: Event) => void | Promise<void>): void; | ||
| //# sourceMappingURL=utils.file.d.ts.map | ||
| //#endregion | ||
| //#region src/utils/utils.jwt.d.ts | ||
| interface JwtPayload { | ||
| sub: string; | ||
| name: string; | ||
| email: string; | ||
| exp: number; | ||
| [key: string]: unknown; | ||
| } | ||
| declare const decodeJwt: (token: string) => JwtPayload | null; | ||
| //#endregion | ||
@@ -457,3 +380,3 @@ //#region src/utils/utils.number.d.ts | ||
| //#endregion | ||
| export { APP_STORE_ID, APP_STORE_URL, COLDSURF_WEB_URL, LoginProvider, NextMetadataGenerator, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, SERVICE_NAME, SNS_LINKS, TryParseOptions, createConcertSlug, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse }; | ||
| export { APP_STORE_ID, APP_STORE_URL, ArticleMeta, COLDSURF_WEB_URL, CreativeWorkKind, JsonLd, JsonLdEvent, JsonLdEventOffer, JsonLdRating, JsonLdWork, Lang, LoginProvider, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, PageSeoInput, SERVICE_NAME, SNS_LINKS, SeoImage, SeoLink, SeoMeta, SeoScript, SeoTags, SiteConfig, TryParseOptions, buildSeoTags, createConcertSlug, createSeo, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse }; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.metadata.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"} | ||
| {"version":3,"file":"index.d.ts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/metadata/core.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"} |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import{z as e}from"zod";import{jwtDecode as t}from"jwt-decode";import{addDays as n,format as r,getDay as i,isSameYear as a,parse as o,startOfDay as s}from"date-fns";import c from"slugify";import{fromZonedTime as l,toZonedTime as u}from"date-fns-tz";import{enUS as d,ko as f}from"date-fns/locale";const p=`1632802589`,m=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${p}`,h=`COLDSURF`,g=`com.fstvllife.android`,_=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,v=`COLDSURF`,y={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},b=`https://coldsurf.io`,x=e.union([e.literal(`google`),e.literal(`apple`),e.literal(`email`)]);function S(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const C=e=>{try{let n=t(e);return n}catch(e){return console.error(`Error decoding JWT:`,e),null}};function w(e){return{"@context":`https://schema.org`,"@type":`MusicEvent`,url:e.url,name:e.name,startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:[{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}}],image:e.images,description:e.description,offers:e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:e.url,validFrom:e.validFrom,name:e.name})),organizer:{"@type":`Organization`,name:e.venue.name}}}var T=class{baseData;constructor({baseData:e}){this.baseData=e}generateMetadata(e){let{icons:t,metadataBase:n,appLinks:r,keywords:i,twitter:a,openGraph:o}=this.baseData,s=[{url:`https://coldsurf.io/icons/favicon.ico`}],c={...e,icons:t,metadataBase:n,appLinks:r,keywords:[...e.keywords??[],...i],twitter:a,openGraph:{siteName:o?.siteName,images:Array.isArray(o?.images)&&o.images.length>0?o.images:s,title:o?.title,description:o?.description,...e.openGraph},locale:`ko`};return c}generateLdJson(e){switch(e.type){case`WebSite`:return{"@context":`https://schema.org`,"@type":`WebSite`,url:e.url,name:e.name};case`MusicEvent`:return w(e);case`Brand`:return{"@context":`https://schema.org`,"@type":`Brand`,name:e.name,image:e.image,logo:e.logo,url:e.url,sameAs:e.sameAs};case`Place`:return{"@context":`https://schema.org`,"@type":`Place`,address:e.address,event:e.events.map(e=>w(e)),geo:{"@type":`GeoCoordinates`,latitude:e.latitude,longitude:e.longitude},name:e.name,url:e.url,description:e.description};case`PerformingGroup`:return{"@context":`https://schema.org`,"@type":`PerformingGroup`,image:e.image,name:e.name,url:e.url,event:e.events.map(e=>w(e))};case`WebPageAbout`:return{"@context":`https://schema.org`,name:e.name,"@type":`AboutPage`,url:e.url,image:e.image,sameAs:e.sameAs,description:e.description};default:return{}}}};function E(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function D(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function O(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const k=e=>c(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function A(e,t){let n=F(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const j=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function M(e){return j.reduce((e,[t,n])=>e.replace(t,n),e)}function N(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function P(e){let t=Number(r(e,`d`)),n=r(e,`MMM`).toLowerCase(),i=N(t);return`${t}${i}-${n}`}const F=e=>{let t=c(M(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},I=e=>{let t=c(M(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},L=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${P(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=c(M(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function R({title:e,date:t,venueName:n,area:r},i){let a=L({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const z=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},B={getEventCategoryUIName:z},V=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},H={getLocationCityUIName:V},U=`Asia/Seoul`;function W(e,t=new Date){return!a(e,t)}function G(e,t){let n=u(e,U),i=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>W(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return r(n,e,{locale:d})}let a=(()=>W(n)?i===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:i===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return r(n,a,{locale:f})}function K({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=o(e,`yyyyMMdd`,new Date),r=n(t,1),i=l(t,U),a=l(r,U);return[i,a]}function q(e){let t=e?.yyyymmdd?o(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,r=i(t),a=(5-r+7)%7,c=s(n(t,a)),u=n(c,1),d=n(c,2),f=n(c,3);return[{label:`FRIDAY`,utcStart:l(c,U),utcEnd:l(u,U)},{label:`SATURDAY`,utcStart:l(u,U),utcEnd:l(d,U)},{label:`SUNDAY`,utcStart:l(d,U),utcEnd:l(f,U)}]}const J={parseEventDate:G,toUTCDayRangeFromYYYYMMDD:K,getWeekendUTCStartDates:q};function Y(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function X(e){try{let t=new URL(e);return t.pathname=Y(t.pathname),t.toString()}catch{return Y(e)}}function Z(e){return e.includes(`%`)}function Q(e){return/%25(25)+/i.test(e)}export{p as APP_STORE_ID,m as APP_STORE_URL,b as COLDSURF_WEB_URL,T as NextMetadataGenerator,v as PLAYSTORE_APP_NAME,g as PLAYSTORE_PACKAGE,_ as PLAYSTORE_URL,h as SERVICE_NAME,y as SNS_LINKS,L as createConcertSlug,F as createSlug,I as createSlugHashtag,J as dateUtils,C as decodeJwt,B as eventCategoryUtils,X as fullyDecodePathname,Y as fullyDecodeURI,R as generateConcertSlug,A as generateSlug,D as generateUUID,E as getRandomInt,k as getSafeSlug,Q as isDoubleEncoded,Z as isEncoded,H as locationCityUtils,x as loginProviderSchema,S as pickFile,O as tryParse}; | ||
| import{z as e}from"zod";import{jwtDecode as t}from"jwt-decode";import{addDays as n,format as r,getDay as i,isSameYear as a,parse as o,startOfDay as s}from"date-fns";import c from"slugify";import{fromZonedTime as l,toZonedTime as u}from"date-fns-tz";import{enUS as d,ko as f}from"date-fns/locale";const p=`1632802589`,m=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${p}`,h=`COLDSURF`,ee=`com.fstvllife.android`,te=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,ne=`COLDSURF`,g={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},_=`https://coldsurf.io`,re={ko:`ko_KR`,en:`en_US`},v=`https://schema.org`;function y(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function b(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function x(e,t){return t.startsWith(`http`)?t:b(e,t)}function ie(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function S(e,t){return x(e,t)}function C(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?b(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function w(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function T(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function E(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function D(e,t){let n=e.name,r=e.image?x(t,e.image):void 0,i=e.url?S(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),s=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,c=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:s,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:s,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:c,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:c,image:r,url:i,sameAs:a,datePublished:o}}}function O(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:D(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function k(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:S(t,e.path)}))}}function A(e,t){return{"@type":`MusicEvent`,name:e.name,url:S(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>x(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:x(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function j(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let i=t.baseUrl.replace(/\/$/,``),a=`${i}/#org`,o=`${i}/#editor`,s=`${i}/#website`,c=[];for(let l of r)switch(l.type){case`WebSite`:c.push(T(t,n,s,a));break;case`Article`:{let r=E(e,t,n,a,o);r&&c.push(r);break}case`Review`:c.push(O(l.itemReviewed,l.rating,e,t,n,i,a,o));break;case`CreativeWork`:c.push(D(l.item,i));break;case`EventPage`:c.push(A(l.event,i));break;case`Breadcrumb`:c.push(k(l.items,i));break}if(c.length===0)return[];let l=[],u=C(t,a);u&&l.push(u);let d=w(t,o);d&&l.push(d);let f={"@context":v,"@graph":[...l,...c]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(f)}]}function M(e,t){let n=e.lang??`ko`,r=RegExp(`\\b${y(t.name)}\\b`).test(e.title),i=r?e.title:`${e.title} | ${t.name}`,a=S(t.baseUrl,e.path),o=e.alternates??[],s={...re,...t.locales},c=[{rel:`canonical`,href:a}];if(o.length>0){c.push({rel:`alternate`,hreflang:n,href:a});for(let e of o)c.push({rel:`alternate`,hreflang:e.lang,href:S(t.baseUrl,e.path)});let r=o.find(e=>e.lang===`ko`)?.path??e.path;c.push({rel:`alternate`,hreflang:`x-default`,href:S(t.baseUrl,r)})}let l=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:i},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:a},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:s[n]}],u=[...new Set([...e.keywords??[],...t.keywords??[]])];u.length>0&&l.push({name:`keywords`,content:u.join(`, `)});let d=e.image??t.defaultImage,f=d?ie(x(t.baseUrl,d.path),t.assetVersion,d.path.startsWith(`http`)):void 0;d&&f&&(l.push({property:`og:image`,content:f}),d.type&&l.push({property:`og:image:type`,content:d.type}),d.width&&l.push({property:`og:image:width`,content:String(d.width)}),d.height&&l.push({property:`og:image:height`,content:String(d.height)}),d.alt&&l.push({property:`og:image:alt`,content:d.alt}),l.push({name:`twitter:image`,content:f}),d.alt&&l.push({name:`twitter:image:alt`,content:d.alt})),t.logoPath&&l.push({property:`og:logo`,content:b(t.baseUrl,t.logoPath)});let p=!!d&&!!d.width&&!!d.height&&d.width>=d.height*1.5,m=p?`summary_large_image`:`summary`;l.push({name:`twitter:card`,content:m},{name:`twitter:title`,content:i},{name:`twitter:description`,content:e.description});for(let e of o)e.lang!==n&&l.push({property:`og:locale:alternate`,content:s[e.lang]});if(e.article){l.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&l.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&l.push({property:`article:author`,content:e.article.author}),e.article.section&&l.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])l.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(l.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),l.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&l.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(l.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&l.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&l.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let h=j(e,t,{url:a,fullTitle:i,imageUrl:f,lang:n});return{title:i,htmlLang:n,meta:l,link:c,script:h}}function N(e){return{buildTags:t=>M(t,e)}}const P=e.union([e.literal(`google`),e.literal(`apple`),e.literal(`email`)]);function F(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const I=e=>{try{let n=t(e);return n}catch(e){return console.error(`Error decoding JWT:`,e),null}};function L(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function R(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function z(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const B=e=>c(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function V(e,t){let n=K(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const H=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function U(e){return H.reduce((e,[t,n])=>e.replace(t,n),e)}function W(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function G(e){let t=Number(r(e,`d`)),n=r(e,`MMM`).toLowerCase(),i=W(t);return`${t}${i}-${n}`}const K=e=>{let t=c(U(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},q=e=>{let t=c(U(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},J=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${G(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=c(U(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function Y({title:e,date:t,venueName:n,area:r},i){let a=J({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const ae=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},oe={getEventCategoryUIName:ae},se=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},ce={getLocationCityUIName:se},X=`Asia/Seoul`;function Z(e,t=new Date){return!a(e,t)}function le(e,t){let n=u(e,X),i=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>Z(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return r(n,e,{locale:d})}let a=(()=>Z(n)?i===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:i===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return r(n,a,{locale:f})}function ue({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=o(e,`yyyyMMdd`,new Date),r=n(t,1),i=l(t,X),a=l(r,X);return[i,a]}function Q(e){let t=e?.yyyymmdd?o(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,r=i(t),a=(5-r+7)%7,c=s(n(t,a)),u=n(c,1),d=n(c,2),f=n(c,3);return[{label:`FRIDAY`,utcStart:l(c,X),utcEnd:l(u,X)},{label:`SATURDAY`,utcStart:l(u,X),utcEnd:l(d,X)},{label:`SUNDAY`,utcStart:l(d,X),utcEnd:l(f,X)}]}const de={parseEventDate:le,toUTCDayRangeFromYYYYMMDD:ue,getWeekendUTCStartDates:Q};function $(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function fe(e){try{let t=new URL(e);return t.pathname=$(t.pathname),t.toString()}catch{return $(e)}}function pe(e){return e.includes(`%`)}function me(e){return/%25(25)+/i.test(e)}export{p as APP_STORE_ID,m as APP_STORE_URL,_ as COLDSURF_WEB_URL,ne as PLAYSTORE_APP_NAME,ee as PLAYSTORE_PACKAGE,te as PLAYSTORE_URL,h as SERVICE_NAME,g as SNS_LINKS,M as buildSeoTags,J as createConcertSlug,N as createSeo,K as createSlug,q as createSlugHashtag,de as dateUtils,I as decodeJwt,oe as eventCategoryUtils,fe as fullyDecodePathname,$ as fullyDecodeURI,Y as generateConcertSlug,V as generateSlug,R as generateUUID,L as getRandomInt,B as getSafeSlug,me as isDoubleEncoded,pe as isEncoded,ce as locationCityUtils,P as loginProviderSchema,F as pickFile,z as tryParse}; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","names":["onChange: (e: Event) => void | Promise<void>","token: string","event: CustomMusicEvent","additionalMetadata: T","params:\n | {\n type: 'WebSite';\n url: string;\n name: string;\n }\n | CustomMusicEvent\n | {\n type: 'Brand';\n name: string;\n image: string;\n logo: string;\n url: string;\n sameAs: string[];\n }\n | {\n type: 'Place';\n address: string;\n latitude: number;\n longitude: number;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n description: string;\n }\n | {\n type: 'PerformingGroup';\n image: string;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n }\n | {\n type: 'WebPageAbout';\n name: string;\n url: string;\n image: string;\n sameAs: string[];\n description: string;\n }","minimum: number","maximum: number","jsonString: string","slug: string","title: string","existingCallback: (newSlug: string) => boolean | Promise<boolean>","newSlug: string","day: number","date: Date","valueToSlugify: string","originalName: string","originalName: string","date: Date","now: Date","eventDate: Date","options?: {\n formatStyle: 'korean' | 'english';\n }","timeFormat","params?: {\n yyyymmdd?: string;\n}","uri: string","url: string","str: string"],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.metadata.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":["export const APP_STORE_ID = '1632802589' as const;\nexport const APP_STORE_URL =\n `https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${APP_STORE_ID}` as const;\n","export const SERVICE_NAME = 'COLDSURF';\n","export const PLAYSTORE_PACKAGE = 'com.fstvllife.android' as const;\nexport const PLAYSTORE_URL =\n 'https://play.google.com/store/apps/details?id=com.fstvllife.android' as const;\nexport const PLAYSTORE_APP_NAME = 'COLDSURF' as const;\n","export const SNS_LINKS = {\n INSTAGRAM: 'https://www.instagram.com/coldsurf.io',\n X: 'https://x.com/coldsurf_io',\n} as const;\n","export const COLDSURF_WEB_URL = 'https://coldsurf.io';\n","import { z } from 'zod';\n\nexport const loginProviderSchema = z.union([\n z.literal('google'),\n z.literal('apple'),\n z.literal('email'),\n]);\nexport type LoginProvider = z.infer<typeof loginProviderSchema>;\n","export function pickFile(onChange: (e: Event) => void | Promise<void>) {\n const input = document.createElement('input');\n input.type = 'file';\n input.click();\n input.onchange = async (e) => {\n await onChange(e);\n input.remove();\n };\n}\n","// Import the jwt-decode library\nimport { jwtDecode } from 'jwt-decode';\n\n// Define the structure of the decoded JWT payload (optional)\ninterface JwtPayload {\n sub: string; // Subject (user identifier)\n name: string; // Name of the user\n email: string; // Email of the user\n exp: number; // Expiration time\n [key: string]: unknown; // Any other properties\n}\n\n// Function to parse JWT token\nexport const decodeJwt = (token: string): JwtPayload | null => {\n try {\n // Decode the token\n const decodedToken = jwtDecode<JwtPayload>(token);\n return decodedToken;\n } catch (error) {\n console.error('Error decoding JWT:', error);\n return null;\n }\n};\n","import type {\n Brand,\n MusicEvent,\n PerformingGroup,\n Place,\n WebPage,\n WebSite,\n WithContext,\n} from 'schema-dts';\n\ntype BaseData = {\n keywords: string[];\n icons: {\n icon: string;\n shortcut: string;\n apple: string;\n };\n metadataBase: URL;\n appLinks?: {\n ios?: {\n app_name: string;\n app_store_id: string;\n url: string;\n };\n android?: {\n package: string;\n url?: string | URL;\n class?: string;\n app_name?: string;\n };\n };\n twitter?: {\n app?: {\n id: {\n /**\n * app store id\n */\n iphone: string;\n };\n name: string;\n url: {\n /**\n * app store url\n */\n iphone: string;\n };\n };\n card: 'app';\n };\n openGraph?: {\n siteName: string;\n title: string;\n description: string;\n images?: {\n url: string;\n }[];\n };\n};\n\ntype CustomMusicEvent = {\n type: 'MusicEvent';\n url: string;\n name: string;\n startDate: string;\n endDate: string;\n venue: {\n name: string;\n address: string;\n latitude: number;\n longitude: number;\n };\n images: string[];\n description: string;\n offers: {\n price: number;\n currency: string;\n url: string;\n validFrom: string;\n name?: string;\n }[];\n};\n\nfunction generateMusicEvent(event: CustomMusicEvent) {\n return {\n '@context': 'https://schema.org',\n '@type': 'MusicEvent',\n url: event.url,\n name: event.name,\n startDate: event.startDate,\n endDate: event.endDate,\n eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',\n eventStatus: 'https://schema.org/EventScheduled',\n location: [\n {\n '@type': 'Place',\n name: event.venue.name,\n address: event.venue.address,\n geo: {\n '@type': 'GeoCoordinates',\n latitude: event.venue.latitude,\n longitude: event.venue.longitude,\n },\n },\n ],\n image: event.images,\n description: event.description,\n offers: event.offers.map((offer) => {\n return {\n '@type': 'Offer',\n availability: 'https://schema.org/InStock',\n price: offer.price,\n priceCurrency: offer.currency,\n url: offer.url,\n validFrom: offer.validFrom,\n name: offer.name,\n };\n }),\n organizer: {\n '@type': 'Organization',\n name: event.venue.name,\n },\n } satisfies WithContext<MusicEvent>;\n}\n\nexport class NextMetadataGenerator {\n public baseData: BaseData;\n constructor({ baseData }: { baseData: BaseData }) {\n this.baseData = baseData;\n }\n\n public generateMetadata<T>(additionalMetadata: T): T {\n const { icons, metadataBase, appLinks, keywords, twitter, openGraph } =\n this.baseData;\n const defaultOgImages = [\n {\n url: 'https://coldsurf.io/icons/favicon.ico',\n },\n ];\n const value = {\n ...additionalMetadata,\n icons,\n metadataBase,\n appLinks,\n // @ts-ignore\n keywords: [...(additionalMetadata.keywords ?? []), ...keywords],\n twitter,\n openGraph: {\n siteName: openGraph?.siteName,\n images:\n Array.isArray(openGraph?.images) && openGraph.images.length > 0\n ? openGraph.images\n : defaultOgImages,\n title: openGraph?.title,\n description: openGraph?.description,\n // @ts-ignore\n ...additionalMetadata.openGraph,\n },\n locale: 'ko',\n } as T;\n\n return value;\n }\n\n public generateLdJson(\n params:\n | {\n type: 'WebSite';\n url: string;\n name: string;\n }\n | CustomMusicEvent\n | {\n type: 'Brand';\n name: string;\n image: string;\n logo: string;\n url: string;\n sameAs: string[];\n }\n | {\n type: 'Place';\n address: string;\n latitude: number;\n longitude: number;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n description: string;\n }\n | {\n type: 'PerformingGroup';\n image: string;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n }\n | {\n type: 'WebPageAbout';\n name: string;\n url: string;\n image: string;\n sameAs: string[];\n description: string;\n }\n ) {\n switch (params.type) {\n case 'WebSite':\n return {\n '@context': 'https://schema.org',\n '@type': 'WebSite',\n url: params.url,\n name: params.name,\n } satisfies WithContext<WebSite>;\n case 'MusicEvent':\n return generateMusicEvent(params);\n case 'Brand':\n return {\n '@context': 'https://schema.org',\n '@type': 'Brand',\n name: params.name,\n image: params.image,\n logo: params.logo,\n url: params.url,\n sameAs: params.sameAs,\n } satisfies WithContext<Brand>;\n case 'Place':\n return {\n '@context': 'https://schema.org',\n '@type': 'Place',\n address: params.address,\n event: params.events.map((event) => generateMusicEvent(event)),\n geo: {\n '@type': 'GeoCoordinates',\n latitude: params.latitude,\n longitude: params.longitude,\n },\n name: params.name,\n url: params.url,\n description: params.description,\n } satisfies WithContext<Place>;\n case 'PerformingGroup':\n return {\n '@context': 'https://schema.org',\n '@type': 'PerformingGroup',\n image: params.image,\n name: params.name,\n url: params.url,\n event: params.events.map((event) => generateMusicEvent(event)),\n } satisfies WithContext<PerformingGroup>;\n case 'WebPageAbout':\n return {\n '@context': 'https://schema.org',\n name: params.name,\n '@type': 'AboutPage',\n url: params.url,\n image: params.image,\n sameAs: params.sameAs,\n description: params.description,\n } satisfies WithContext<WebPage>;\n default:\n return {};\n }\n }\n}\n","/**\n * Returns a random integer between the specified values, inclusive.\n * The value is no lower than `min`, and is less than or equal to `max`.\n *\n * @param {number} minimum - The smallest integer value that can be returned, inclusive.\n * @param {number} maximum - The largest integer value that can be returned, inclusive.\n * @returns {number} - A random integer between `min` and `max`, inclusive.\n */\nexport function getRandomInt(minimum: number, maximum: number) {\n const min = Math.ceil(minimum);\n const max = Math.floor(maximum);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n","export function generateUUID() {\n // Public Domain/MIT\n let d = new Date().getTime(); // Timestamp\n let d2 =\n (typeof performance !== 'undefined' &&\n performance.now &&\n performance.now() * 1000) ||\n 0; // Time in microseconds since page-load or 0 if unsupported\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n let r = Math.random() * 16; // random number between 0 and 16\n if (d > 0) {\n // Use timestamp until depleted\n r = ((d + r) % 16) | 0;\n d = Math.floor(d / 16);\n } else {\n // Use microseconds since page-load if supported\n r = ((d2 + r) % 16) | 0;\n d2 = Math.floor(d2 / 16);\n }\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","export interface TryParseOptions<T> {\n fallback?: T;\n silent?: boolean;\n}\n\nexport function tryParse<T = any>(\n jsonString: string,\n { silent = true, fallback }: TryParseOptions<T> = {}\n): T | undefined {\n try {\n return JSON.parse(jsonString) as T;\n } catch (e) {\n if (!silent) {\n console.warn(\n 'JSON parse error, return fallback:',\n e,\n '| input:',\n jsonString\n );\n }\n\n return fallback;\n }\n}\n","import { format } from 'date-fns';\nimport slugify from 'slugify';\n\nexport const getSafeSlug = (slug: string) => {\n return slugify(slug, {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[[\\]*+~.()'\"?!:@,&<>〈〉#]/g, // 특정 특수문자 제거\n });\n};\n\n// Function to generate unique slugs\nexport async function generateSlug(\n title: string,\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createSlug(title);\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n\nconst replacements = [\n [/#/g, 'no'],\n [/&/g, 'and'],\n [/%/g, 'percent'],\n] as const;\n\nfunction preprocess(title: string) {\n return replacements.reduce(\n (acc, [regex, value]) => acc.replace(regex, value),\n title\n );\n}\n\n// 서수 접미사 함수\nfunction getOrdinalSuffix(day: number): string {\n if (day > 3 && day < 21) return 'th';\n switch (day % 10) {\n case 1:\n return 'st';\n case 2:\n return 'nd';\n case 3:\n return 'rd';\n default:\n return 'th';\n }\n}\n\nfunction formatDateSlug(date: Date): string {\n const day = Number(format(date, 'd')); // 1~31\n const month = format(date, 'MMM').toLowerCase(); // \"Oct\" → \"oct\"\n const ordinal = getOrdinalSuffix(day);\n return `${day}${ordinal}-${month}`;\n}\n\nexport const createSlug = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\nexport const createSlugHashtag = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '_', // 공백을 \"_\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\n// Function to generate unique slugs\nexport const createConcertSlug = ({\n title,\n date,\n venueName,\n area,\n}: {\n title: string;\n date: Date;\n venueName?: string;\n area?: string;\n}) => {\n let value = `${title}-${formatDateSlug(date)}`;\n if (venueName) {\n value += `-${venueName}`;\n }\n if (area) {\n value += `-${area}`;\n }\n value += '-티켓';\n const slug = slugify(preprocess(`${value}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n\n return slug;\n};\n\n// Function to generate unique slugs\nexport async function generateConcertSlug(\n { title, date, venueName, area }: Parameters<typeof createConcertSlug>[0],\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createConcertSlug({ title, date, venueName, area });\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n","const getEventCategoryUIName = (originalName: string) => {\n switch (originalName) {\n case 'Gigs':\n return '콘서트';\n case 'Theatre':\n return '연극 / 뮤지컬';\n case 'Dance':\n return '무용';\n case 'Korean-Traditional':\n return '국악';\n case 'Classic':\n return '클래식';\n case 'Party':\n return '파티 / 오프라인';\n case 'Dj':\n return '디제잉';\n default:\n return originalName;\n }\n};\n\nexport const eventCategoryUtils = {\n getEventCategoryUIName,\n};\n","const getLocationCityUIName = (originalName: string) => {\n switch (originalName.toLowerCase()) {\n case 'seoul':\n return '서울';\n case 'incheon':\n return '인천';\n case 'yeongjongdo':\n return '영종도';\n case 'ulsan':\n return '울산';\n case 'busan':\n return '부산';\n case 'daegu':\n return '대구';\n case 'jeju':\n return '제주';\n case 'gyeongsangbuk-do':\n return '경상북도';\n case 'gyeongsangnam-do':\n return '경상남도';\n case 'gwangju':\n return '광주';\n case 'daejeon':\n return '대전';\n case 'sejong-city':\n return '세종시';\n case 'gyeonggi-do':\n return '경기도';\n case 'gangwon-do':\n return '강원도';\n case 'chungcheongbuk-do':\n return '충청북도';\n case 'chungcheongnam-do':\n return '충청남도';\n case 'jeollabuk-do':\n return '전라북도';\n case 'jeollanam-do':\n return '전라남도';\n case 'tokyo':\n return '도쿄';\n case 'osaka':\n return '오사카';\n case 'hochiminh':\n return '호치민';\n default:\n return originalName;\n }\n};\n\nexport const locationCityUtils = {\n getLocationCityUIName,\n};\n","import { format, isSameYear, addDays, getDay, parse, startOfDay } from 'date-fns';\nimport { fromZonedTime } from 'date-fns-tz';\nimport { toZonedTime } from 'date-fns-tz';\nimport { enUS, ko } from 'date-fns/locale';\n\nconst timeZone = 'Asia/Seoul';\n\nfunction isDifferentYear(date: Date, now: Date = new Date()) {\n return !isSameYear(date, now);\n}\n\nfunction parseEventDate(\n eventDate: Date,\n options?: {\n formatStyle: 'korean' | 'english';\n }\n) {\n const zonedDate = toZonedTime(eventDate, timeZone);\n const minutes = zonedDate.getMinutes();\n if (options?.formatStyle === 'english') {\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return 'MMM dd, yyyy, h:mm a';\n }\n return 'MMM dd, h:mm a';\n })();\n return format(zonedDate, timeFormat, { locale: enUS });\n }\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return minutes === 0\n ? 'EEEE a h시, yyyy년 MMMM d일'\n : 'EEEE a h시 m분, yyyy년 MMMM d일';\n }\n return minutes === 0 ? 'EEEE a h시, MMMM d일' : 'EEEE a h시 m분, MMMM d일';\n })();\n return format(zonedDate, timeFormat, { locale: ko });\n}\n\nfunction toUTCDayRangeFromYYYYMMDD({\n yyyymmdd,\n}: {\n yyyymmdd: string;\n}) {\n if (!/^\\d{8}$/.test(yyyymmdd)) {\n throw new Error('Invalid date format. Expected YYYYMMDD');\n }\n const kstStart = parse(yyyymmdd, 'yyyyMMdd', new Date());\n const kstEnd = addDays(kstStart, 1);\n // 2. KST → UTC 변환\n const utcStart = fromZonedTime(kstStart, timeZone);\n const utcEnd = fromZonedTime(kstEnd, timeZone);\n return [utcStart, utcEnd];\n}\n\ntype UTCDayRange = {\n label: 'FRIDAY' | 'SATURDAY' | 'SUNDAY';\n utcStart: Date;\n utcEnd: Date;\n};\n\n/**\n * 기준 날짜(yyyymmdd)가 속한 주의\n * 금/토/일 KST 00:00 → UTC Date 반환\n *\n * yyyymmdd가 없으면 현재 시점 기준\n */\nfunction getWeekendUTCStartDates(params?: {\n yyyymmdd?: string;\n}): UTCDayRange[] {\n const baseDate = params?.yyyymmdd ? parse(params.yyyymmdd, 'yyyyMMdd', new Date()) : new Date();\n\n // JS 기준: Sun=0, Mon=1, ... Sat=6\n const baseDay = getDay(baseDate);\n\n // 해당 주의 금요일(5)까지의 offset\n const diffToFriday = (5 - baseDay + 7) % 7;\n\n const kstFridayStart = startOfDay(addDays(baseDate, diffToFriday));\n const kstSaturdayStart = addDays(kstFridayStart, 1);\n const kstSundayStart = addDays(kstFridayStart, 2);\n const kstMondayStart = addDays(kstFridayStart, 3); // Sunday end\n\n return [\n {\n label: 'FRIDAY',\n utcStart: fromZonedTime(kstFridayStart, timeZone),\n utcEnd: fromZonedTime(kstSaturdayStart, timeZone),\n },\n {\n label: 'SATURDAY',\n utcStart: fromZonedTime(kstSaturdayStart, timeZone),\n utcEnd: fromZonedTime(kstSundayStart, timeZone),\n },\n {\n label: 'SUNDAY',\n utcStart: fromZonedTime(kstSundayStart, timeZone),\n utcEnd: fromZonedTime(kstMondayStart, timeZone),\n },\n ];\n}\n\n\nexport const dateUtils = {\n parseEventDate,\n toUTCDayRangeFromYYYYMMDD,\n getWeekendUTCStartDates,\n};\n\n","/**\n * URL을 완전히 디코딩 (이중/삼중 인코딩 모두 해결)\n */\nexport function fullyDecodeURI(uri: string): string {\n let decoded = uri;\n let prevDecoded = '';\n\n // 더 이상 디코딩되지 않을 때까지 반복\n while (decoded !== prevDecoded) {\n prevDecoded = decoded;\n try {\n decoded = decodeURIComponent(decoded);\n } catch (e) {\n // 잘못된 URI 시퀀스면 중단\n break;\n }\n }\n\n return decoded;\n}\n\n/**\n * pathname만 디코딩 (프로토콜, 도메인은 유지)\n */\nexport function fullyDecodePathname(url: string): string {\n try {\n const urlObj = new URL(url);\n urlObj.pathname = fullyDecodeURI(urlObj.pathname);\n return urlObj.toString();\n } catch {\n // URL 파싱 실패 시 전체 문자열 디코딩\n return fullyDecodeURI(url);\n }\n}\n\n/**\n * %가 포함되어 있는지 확인 (인코딩 여부 체크)\n */\nexport function isEncoded(str: string): boolean {\n return str.includes('%');\n}\n\n/**\n * 실제 double-encoding 여부 확인\n * 예: \"%2525\" => true\n * \"%25\" => false (단일 인코딩)\n */\nexport function isDoubleEncoded(str: string): boolean {\n // \"%25\"가 아닌 \"%2525\" 패턴을 찾아야 double-encoding\n return /%25(25)+/i.test(str);\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.js","names":["DEFAULT_LOCALES: Record<Lang, string>","s: string","base: string","path: string","value: string","url: string","version: string | undefined","external: boolean","site: SiteConfig","orgId: string","editorId: string","ctx: JsonLdContext","websiteId: string","input: PageSeoInput","work: JsonLdWork","itemReviewed: JsonLdWork","rating: JsonLdRating | undefined","items: { name: string; path: string }[]","event: JsonLdEvent","pageNodes: Thing[]","baseNodes: Thing[]","lang: Lang","link: SeoLink[]","meta: SeoMeta[]","onChange: (e: Event) => void | Promise<void>","token: string","minimum: number","maximum: number","jsonString: string","slug: string","title: string","existingCallback: (newSlug: string) => boolean | Promise<boolean>","newSlug: string","day: number","date: Date","valueToSlugify: string","originalName: string","originalName: string","date: Date","now: Date","eventDate: Date","options?: {\n formatStyle: 'korean' | 'english';\n }","timeFormat","params?: {\n yyyymmdd?: string;\n}","uri: string","url: string","str: string"],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/metadata/core.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":["export const APP_STORE_ID = '1632802589' as const;\nexport const APP_STORE_URL =\n `https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${APP_STORE_ID}` as const;\n","export const SERVICE_NAME = 'COLDSURF';\n","export const PLAYSTORE_PACKAGE = 'com.fstvllife.android' as const;\nexport const PLAYSTORE_URL =\n 'https://play.google.com/store/apps/details?id=com.fstvllife.android' as const;\nexport const PLAYSTORE_APP_NAME = 'COLDSURF' as const;\n","export const SNS_LINKS = {\n INSTAGRAM: 'https://www.instagram.com/coldsurf.io',\n X: 'https://x.com/coldsurf_io',\n} as const;\n","export const COLDSURF_WEB_URL = 'https://coldsurf.io';\n","import type {\n Article,\n Book,\n BreadcrumbList,\n Event,\n Graph,\n Movie,\n MusicAlbum,\n MusicEvent,\n MusicRecording,\n Organization,\n Person,\n Review,\n Thing,\n WebSite,\n} from 'schema-dts';\n\nexport type Lang = 'ko' | 'en';\n\nexport type SeoImage = {\n /** Absolute URL or path starting with `/`. */\n path: string;\n width?: number;\n height?: number;\n type?: string;\n alt?: string;\n};\n\nexport type ArticleMeta = {\n /** ISO 8601 timestamp. */\n publishedTime: string;\n /** ISO 8601 timestamp. */\n modifiedTime?: string;\n author?: string;\n section?: string;\n tags?: string[];\n};\n\n/**\n * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.\n * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).\n */\nexport type CreativeWorkKind =\n | 'albums'\n | 'tracks'\n | 'concerts'\n | 'films'\n | 'books'\n | 'events';\n\n/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */\nexport type JsonLdWork = {\n kind: CreativeWorkKind;\n name: string;\n /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */\n by?: string;\n /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */\n year?: number | string;\n /** Path(`/...`) 또는 절대 URL. */\n image?: string;\n /** 작품 페이지 path(`/...`) 또는 절대 URL. */\n url?: string;\n /** 외부 출처 — bandcamp · spotify · imdb 등. */\n sameAs?: string[];\n};\n\nexport type JsonLdRating = { value: number; best?: number; worst?: number };\n\nexport type JsonLdEventOffer = {\n price: number;\n currency: string;\n /** Path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 티켓 오픈 시각. */\n validFrom: string;\n name?: string;\n};\n\n/**\n * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와\n * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.\n */\nexport type JsonLdEvent = {\n name: string;\n /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */\n startDate: string;\n /** ISO 8601 */\n endDate?: string;\n venue: {\n name: string;\n address: string;\n latitude: number;\n longitude: number;\n };\n /** Path(`/...`) 또는 절대 URL 목록. */\n images?: string[];\n description?: string;\n offers?: JsonLdEventOffer[];\n /** 주최자명. 미지정 시 `venue.name` 으로 대체. */\n organizer?: string;\n};\n\n/**\n * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.\n * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.\n */\nexport type JsonLd =\n | { type: 'WebSite' }\n /** `input.article` 에서 파생 — article 없으면 무시. */\n | { type: 'Article' }\n | { type: 'Review'; itemReviewed: JsonLdWork; rating?: JsonLdRating }\n | { type: 'CreativeWork'; item: JsonLdWork }\n /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */\n | { type: 'EventPage'; event: JsonLdEvent }\n | { type: 'Breadcrumb'; items: { name: string; path: string }[] };\n\nexport type PageSeoInput = {\n title: string;\n description: string;\n path: string;\n lang?: Lang;\n /** Alternate language versions for this page. Emits hreflang link tags. */\n alternates?: { lang: Lang; path: string }[];\n /** When set, og:type switches to `article` and article:* tags are emitted. */\n article?: ArticleMeta;\n /** Override the default OG/Twitter share image for this page. */\n image?: SeoImage;\n /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */\n keywords?: string[];\n /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */\n jsonLd?: JsonLd[];\n /**\n * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`\n * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.\n */\n appLinks?: { iosUrl?: string; androidUrl?: string };\n};\n\nexport type SiteConfig = {\n /** Display name appended to every page title. */\n name: string;\n /** Origin used to resolve absolute URLs. Trailing slash is normalized. */\n baseUrl: string;\n /** Default OG/Twitter image used when a page does not override it. */\n defaultImage?: SeoImage;\n /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */\n logoPath?: string;\n /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */\n locales?: Partial<Record<Lang, string>>;\n /** Site-wide keywords merged into every page's `keywords` meta. */\n keywords?: string[];\n /**\n * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter\n * image URLs (paths, not external `http` URLs). Bump it when an image is replaced\n * in place under the same filename so social scrapers (Threads/Slack/iMessage 등)\n * treat it as a new URL instead of serving their stale cache.\n */\n assetVersion?: string;\n /** Publishing organization — emitted as the `Organization` JSON-LD node. */\n publisher?: {\n name: string;\n url?: string;\n logoPath?: string;\n sameAs?: string[];\n };\n /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */\n editor?: { name: string; url?: string; sameAs?: string[] };\n /**\n * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.\n * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다\n * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.\n */\n appLinks?: {\n ios?: { appStoreId: string; appName: string };\n android?: { packageName: string; appName?: string };\n };\n};\n\nexport type SeoMeta = { name?: string; property?: string; content: string };\nexport type SeoLink = { rel: string; href: string; hreflang?: string };\nexport type SeoScript = { type: 'application/ld+json'; innerHTML: string };\n\nexport type SeoTags = {\n title: string;\n htmlLang: Lang;\n meta: SeoMeta[];\n link: SeoLink[];\n /** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */\n script: SeoScript[];\n};\n\nconst DEFAULT_LOCALES: Record<Lang, string> = {\n ko: 'ko_KR',\n en: 'en_US',\n};\n\nconst SCHEMA_CONTEXT = 'https://schema.org';\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction joinUrl(base: string, path: string): string {\n const b = base.replace(/\\/$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${b}${p}`;\n}\n\n/** Resolve a value that may be a path(`/...`) or an already-absolute URL. */\nfunction absoluteUrl(base: string, value: string): string {\n return value.startsWith('http') ? value : joinUrl(base, value);\n}\n\n/**\n * Append `?v=<version>` (or `&v=`) for cache-busting. No-op when version is empty.\n * `external` skips the param entirely — we don't control third-party CDN URLs\n * (signed poster URLs could break) and only want to bust our own re-uploaded assets.\n */\nfunction withAssetVersion(\n url: string,\n version: string | undefined,\n external: boolean\n): string {\n if (!version || external) return url;\n return `${url}${url.includes('?') ? '&' : '?'}v=${encodeURIComponent(version)}`;\n}\n\n/**\n * Absolute *page* (route) URL.\n *\n * 과거 Next.js (`trailingSlash: true` + directory-index 호스팅) 시절엔 모든 page URL 에\n * `/` 를 강제로 붙여 canonical 과 sitemap 을 호스트 redirect 와 정합시켰지만, TSS +\n * Cloudflare Workers SSR 로 이관 후엔 라우트가 slash 없이 서빙된다 (`/event/foo` 는 200,\n * `/event/foo/` 는 의도된 canonical 이 아님). 따라서 `absoluteUrl` 결과를 그대로 둔다.\n */\nfunction pageUrl(base: string, value: string): string {\n return absoluteUrl(base, value);\n}\n\ntype JsonLdContext = {\n url: string;\n fullTitle: string;\n imageUrl?: string;\n lang: Lang;\n};\n\nfunction orgNode(site: SiteConfig, orgId: string): Organization | null {\n const p = site.publisher;\n if (!p) return null;\n return {\n '@type': 'Organization',\n '@id': orgId,\n name: p.name,\n url: p.url ?? site.baseUrl.replace(/\\/$/, ''),\n logo: p.logoPath ? joinUrl(site.baseUrl, p.logoPath) : undefined,\n sameAs: p.sameAs,\n } satisfies Organization;\n}\n\nfunction personNode(site: SiteConfig, editorId: string): Person | null {\n const e = site.editor;\n if (!e) return null;\n return {\n '@type': 'Person',\n '@id': editorId,\n name: e.name,\n url: e.url,\n sameAs: e.sameAs,\n } satisfies Person;\n}\n\nfunction websiteNode(\n site: SiteConfig,\n ctx: JsonLdContext,\n websiteId: string,\n orgId: string\n): WebSite {\n return {\n '@type': 'WebSite',\n '@id': websiteId,\n name: site.name,\n url: site.baseUrl.replace(/\\/$/, ''),\n inLanguage: ctx.lang,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies WebSite;\n}\n\nfunction articleNode(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n orgId: string,\n editorId: string\n): Article | null {\n const a = input.article;\n if (!a) return null;\n return {\n '@type': 'BlogPosting',\n headline: input.title,\n description: input.description,\n url: ctx.url,\n mainEntityOfPage: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a.publishedTime,\n dateModified: a.modifiedTime,\n image: ctx.imageUrl,\n articleSection: a.section,\n keywords: a.tags,\n author: site.editor\n ? { '@id': editorId }\n : a.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies Article;\n}\n\n/**\n * `JsonLdWork` → schema.org 작품 노드. kind 에 따라 @type 과 creator 프로퍼티를 매핑한다.\n * creator(`by`)는 문자열이 아니라 `MusicGroup`/`Person` 객체로 감싸야 schema-dts 가 받는다.\n */\nfunction workNode(work: JsonLdWork, base: string): Thing {\n const name = work.name;\n const image = work.image ? absoluteUrl(base, work.image) : undefined;\n const url = work.url ? pageUrl(base, work.url) : undefined;\n const sameAs = work.sameAs?.length ? work.sameAs : undefined;\n const year = work.year != null ? String(work.year) : undefined;\n const musicGroup = work.by\n ? ({ '@type': 'MusicGroup', name: work.by } as const)\n : undefined;\n const person = work.by\n ? ({ '@type': 'Person', name: work.by } as const)\n : undefined;\n\n switch (work.kind) {\n case 'albums':\n return {\n '@type': 'MusicAlbum',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies MusicAlbum;\n case 'tracks':\n return {\n '@type': 'MusicRecording',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n } satisfies MusicRecording;\n case 'concerts':\n return {\n '@type': 'MusicEvent',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies MusicEvent;\n case 'events':\n return {\n '@type': 'Event',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies Event;\n case 'films':\n return {\n '@type': 'Movie',\n name,\n director: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Movie;\n case 'books':\n return {\n '@type': 'Book',\n name,\n author: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Book;\n }\n}\n\nfunction reviewNode(\n itemReviewed: JsonLdWork,\n rating: JsonLdRating | undefined,\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n base: string,\n orgId: string,\n editorId: string\n): Review {\n const a = input.article;\n return {\n '@type': 'Review',\n name: ctx.fullTitle,\n reviewBody: input.description,\n url: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a?.publishedTime,\n author: site.editor\n ? { '@id': editorId }\n : a?.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n itemReviewed: workNode(itemReviewed, base),\n reviewRating: rating\n ? {\n '@type': 'Rating',\n ratingValue: rating.value,\n bestRating: rating.best,\n worstRating: rating.worst,\n }\n : undefined,\n } satisfies Review;\n}\n\nfunction breadcrumbNode(\n items: { name: string; path: string }[],\n base: string\n): BreadcrumbList {\n return {\n '@type': 'BreadcrumbList',\n itemListElement: items.map((it, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: it.name,\n item: pageUrl(base, it.path),\n })),\n } satisfies BreadcrumbList;\n}\n\n/**\n * `JsonLdEvent` → 풀 `MusicEvent` 노드. venue(Place+GeoCoordinates) · offers(Offer) · organizer\n * 까지 채워 Google Event 리치 결과 자격을 충족한다. `@graph` 노드라 `@context` 는 붙이지 않는다.\n */\nfunction eventNode(event: JsonLdEvent, base: string): MusicEvent {\n return {\n '@type': 'MusicEvent',\n name: event.name,\n url: pageUrl(base, event.url),\n startDate: event.startDate,\n endDate: event.endDate,\n eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',\n eventStatus: 'https://schema.org/EventScheduled',\n location: {\n '@type': 'Place',\n name: event.venue.name,\n address: event.venue.address,\n geo: {\n '@type': 'GeoCoordinates',\n latitude: event.venue.latitude,\n longitude: event.venue.longitude,\n },\n },\n image: event.images?.length\n ? event.images.map((img) => absoluteUrl(base, img))\n : undefined,\n description: event.description,\n offers: event.offers?.length\n ? event.offers.map((offer) => ({\n '@type': 'Offer',\n availability: 'https://schema.org/InStock',\n price: offer.price,\n priceCurrency: offer.currency,\n url: absoluteUrl(base, offer.url),\n validFrom: offer.validFrom,\n name: offer.name,\n }))\n : undefined,\n organizer: {\n '@type': 'Organization',\n name: event.organizer ?? event.venue.name,\n },\n } satisfies MusicEvent;\n}\n\n/**\n * 페이지 디스크립터 → 단일 `@graph` JSON-LD `<script>`.\n * publisher/editor base 노드를 항상 prepend 해 페이지 단위로 `@id` 참조가 해소되게 한다.\n */\nfunction buildJsonLd(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext\n): SeoScript[] {\n const descriptors = input.jsonLd ?? [];\n if (descriptors.length === 0) return [];\n\n const base = site.baseUrl.replace(/\\/$/, '');\n const orgId = `${base}/#org`;\n const editorId = `${base}/#editor`;\n const websiteId = `${base}/#website`;\n\n const pageNodes: Thing[] = [];\n for (const d of descriptors) {\n switch (d.type) {\n case 'WebSite':\n pageNodes.push(websiteNode(site, ctx, websiteId, orgId));\n break;\n case 'Article': {\n const node = articleNode(input, site, ctx, orgId, editorId);\n if (node) pageNodes.push(node);\n break;\n }\n case 'Review':\n pageNodes.push(\n reviewNode(\n d.itemReviewed,\n d.rating,\n input,\n site,\n ctx,\n base,\n orgId,\n editorId\n )\n );\n break;\n case 'CreativeWork':\n pageNodes.push(workNode(d.item, base));\n break;\n case 'EventPage':\n pageNodes.push(eventNode(d.event, base));\n break;\n case 'Breadcrumb':\n pageNodes.push(breadcrumbNode(d.items, base));\n break;\n }\n }\n if (pageNodes.length === 0) return [];\n\n const baseNodes: Thing[] = [];\n const org = orgNode(site, orgId);\n if (org) baseNodes.push(org);\n const person = personNode(site, editorId);\n if (person) baseNodes.push(person);\n\n const graph = {\n '@context': SCHEMA_CONTEXT,\n '@graph': [...baseNodes, ...pageNodes],\n } satisfies Graph;\n\n return [{ type: 'application/ld+json', innerHTML: JSON.stringify(graph) }];\n}\n\nexport function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags {\n const lang: Lang = input.lang ?? 'ko';\n // 라우트 측에서 이미 브랜드 suffix(`… | COLDSURF 공연 정보`, `… — COLDSURF`, `Engineering — COLDSURF`)를\n // 직접 박은 경우, 여기서 다시 `| ${site.name}` 을 더하면 `... | COLDSURF 공연 정보 | COLDSURF` 처럼 두 번 노출된다.\n // title 안에 site.name 토큰이 이미 등장하면 그대로 둔다 — 라우트의 의도를 보존하면서 중복만 제거.\n const titleHasBrand = new RegExp(`\\\\b${escapeRegExp(site.name)}\\\\b`).test(\n input.title\n );\n const fullTitle = titleHasBrand\n ? input.title\n : `${input.title} | ${site.name}`;\n const url = pageUrl(site.baseUrl, input.path);\n const alternates = input.alternates ?? [];\n const locales = { ...DEFAULT_LOCALES, ...site.locales };\n\n const link: SeoLink[] = [{ rel: 'canonical', href: url }];\n if (alternates.length > 0) {\n link.push({ rel: 'alternate', hreflang: lang, href: url });\n for (const alt of alternates) {\n link.push({\n rel: 'alternate',\n hreflang: alt.lang,\n href: pageUrl(site.baseUrl, alt.path),\n });\n }\n // x-default points at the primary (Korean) language when present.\n const xDefault =\n alternates.find((a) => a.lang === 'ko')?.path ?? input.path;\n link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: pageUrl(site.baseUrl, xDefault),\n });\n }\n\n const meta: SeoMeta[] = [\n { name: 'description', content: input.description },\n { name: 'robots', content: 'index, follow' },\n { property: 'og:title', content: fullTitle },\n { property: 'og:description', content: input.description },\n { property: 'og:type', content: input.article ? 'article' : 'website' },\n { property: 'og:url', content: url },\n { property: 'og:site_name', content: site.name },\n { property: 'og:locale', content: locales[lang] },\n ];\n\n const keywords = [\n ...new Set([...(input.keywords ?? []), ...(site.keywords ?? [])]),\n ];\n if (keywords.length > 0) {\n meta.push({ name: 'keywords', content: keywords.join(', ') });\n }\n\n const image = input.image ?? site.defaultImage;\n // SeoImage.path 는 doc 상 \"절대 URL 또는 `/` 시작 path\" — venue 포스터처럼 외부 CDN 풀\n // URL 도 받기 위해 absoluteUrl(http 접두면 그대로) 로 통과시킨다.\n // 자체 도메인 자산만 `?v=` cache-bust — 외부 http URL 은 건드리지 않는다.\n const imageUrl = image\n ? withAssetVersion(\n absoluteUrl(site.baseUrl, image.path),\n site.assetVersion,\n image.path.startsWith('http')\n )\n : undefined;\n if (image && imageUrl) {\n meta.push({ property: 'og:image', content: imageUrl });\n if (image.type)\n meta.push({ property: 'og:image:type', content: image.type });\n if (image.width)\n meta.push({ property: 'og:image:width', content: String(image.width) });\n if (image.height)\n meta.push({ property: 'og:image:height', content: String(image.height) });\n if (image.alt) meta.push({ property: 'og:image:alt', content: image.alt });\n meta.push({ name: 'twitter:image', content: imageUrl });\n if (image.alt) meta.push({ name: 'twitter:image:alt', content: image.alt });\n }\n\n if (site.logoPath) {\n meta.push({\n property: 'og:logo',\n content: joinUrl(site.baseUrl, site.logoPath),\n });\n }\n\n // summary_large_image (1.91:1) 에서 잘리지 않을 만큼 가로로 긴 이미지일 때만 큰 카드로 노출.\n // 정사각형 이하 비율은 summary (작은 카드)로 떨어뜨려 안전하게 보여준다.\n const isWideImage =\n !!image &&\n !!image.width &&\n !!image.height &&\n image.width >= image.height * 1.5;\n const twitterCard = isWideImage ? 'summary_large_image' : 'summary';\n meta.push(\n { name: 'twitter:card', content: twitterCard },\n { name: 'twitter:title', content: fullTitle },\n { name: 'twitter:description', content: input.description }\n );\n\n for (const alt of alternates) {\n if (alt.lang !== lang) {\n meta.push({\n property: 'og:locale:alternate',\n content: locales[alt.lang],\n });\n }\n }\n\n if (input.article) {\n meta.push({\n property: 'article:published_time',\n content: input.article.publishedTime,\n });\n if (input.article.modifiedTime) {\n meta.push({\n property: 'article:modified_time',\n content: input.article.modifiedTime,\n });\n }\n if (input.article.author) {\n meta.push({ property: 'article:author', content: input.article.author });\n }\n if (input.article.section) {\n meta.push({\n property: 'article:section',\n content: input.article.section,\n });\n }\n for (const tag of input.article.tags ?? []) {\n meta.push({ property: 'article:tag', content: tag });\n }\n }\n\n // App Links (al:*) — 앱 아이덴티티는 site, 딥링크 URL 은 페이지에서. Twitter app 카드는 의도적으로\n // 쓰지 않는다(이미지 카드 우선) — al:* 는 카드 종류와 무관하게 네이티브 앱 오픈을 동작시킨다.\n if (site.appLinks?.ios) {\n meta.push({\n property: 'al:ios:app_store_id',\n content: site.appLinks.ios.appStoreId,\n });\n meta.push({\n property: 'al:ios:app_name',\n content: site.appLinks.ios.appName,\n });\n if (input.appLinks?.iosUrl) {\n meta.push({ property: 'al:ios:url', content: input.appLinks.iosUrl });\n }\n }\n if (site.appLinks?.android) {\n meta.push({\n property: 'al:android:package',\n content: site.appLinks.android.packageName,\n });\n if (site.appLinks.android.appName) {\n meta.push({\n property: 'al:android:app_name',\n content: site.appLinks.android.appName,\n });\n }\n if (input.appLinks?.androidUrl) {\n meta.push({\n property: 'al:android:url',\n content: input.appLinks.androidUrl,\n });\n }\n }\n\n const script = buildJsonLd(input, site, { url, fullTitle, imageUrl, lang });\n\n return { title: fullTitle, htmlLang: lang, meta, link, script };\n}\n\n/**\n * `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구\n * `NextMetadataGenerator` 의 \"생성자 1회 주입\" 사용감을 stateful 클래스 없이 순수 함수로 재현한다.\n *\n * const seo = createSeo(siteConfig)\n * seo.buildTags({ title, description, path, jsonLd: [...] })\n */\nexport function createSeo(site: SiteConfig) {\n return {\n buildTags: (input: PageSeoInput): SeoTags => buildSeoTags(input, site),\n };\n}\n","import { z } from 'zod';\n\nexport const loginProviderSchema = z.union([\n z.literal('google'),\n z.literal('apple'),\n z.literal('email'),\n]);\nexport type LoginProvider = z.infer<typeof loginProviderSchema>;\n","export function pickFile(onChange: (e: Event) => void | Promise<void>) {\n const input = document.createElement('input');\n input.type = 'file';\n input.click();\n input.onchange = async (e) => {\n await onChange(e);\n input.remove();\n };\n}\n","// Import the jwt-decode library\nimport { jwtDecode } from 'jwt-decode';\n\n// Define the structure of the decoded JWT payload (optional)\ninterface JwtPayload {\n sub: string; // Subject (user identifier)\n name: string; // Name of the user\n email: string; // Email of the user\n exp: number; // Expiration time\n [key: string]: unknown; // Any other properties\n}\n\n// Function to parse JWT token\nexport const decodeJwt = (token: string): JwtPayload | null => {\n try {\n // Decode the token\n const decodedToken = jwtDecode<JwtPayload>(token);\n return decodedToken;\n } catch (error) {\n console.error('Error decoding JWT:', error);\n return null;\n }\n};\n","/**\n * Returns a random integer between the specified values, inclusive.\n * The value is no lower than `min`, and is less than or equal to `max`.\n *\n * @param {number} minimum - The smallest integer value that can be returned, inclusive.\n * @param {number} maximum - The largest integer value that can be returned, inclusive.\n * @returns {number} - A random integer between `min` and `max`, inclusive.\n */\nexport function getRandomInt(minimum: number, maximum: number) {\n const min = Math.ceil(minimum);\n const max = Math.floor(maximum);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n","export function generateUUID() {\n // Public Domain/MIT\n let d = new Date().getTime(); // Timestamp\n let d2 =\n (typeof performance !== 'undefined' &&\n performance.now &&\n performance.now() * 1000) ||\n 0; // Time in microseconds since page-load or 0 if unsupported\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n let r = Math.random() * 16; // random number between 0 and 16\n if (d > 0) {\n // Use timestamp until depleted\n r = ((d + r) % 16) | 0;\n d = Math.floor(d / 16);\n } else {\n // Use microseconds since page-load if supported\n r = ((d2 + r) % 16) | 0;\n d2 = Math.floor(d2 / 16);\n }\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","export interface TryParseOptions<T> {\n fallback?: T;\n silent?: boolean;\n}\n\nexport function tryParse<T = any>(\n jsonString: string,\n { silent = true, fallback }: TryParseOptions<T> = {}\n): T | undefined {\n try {\n return JSON.parse(jsonString) as T;\n } catch (e) {\n if (!silent) {\n console.warn(\n 'JSON parse error, return fallback:',\n e,\n '| input:',\n jsonString\n );\n }\n\n return fallback;\n }\n}\n","import { format } from 'date-fns';\nimport slugify from 'slugify';\n\nexport const getSafeSlug = (slug: string) => {\n return slugify(slug, {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[[\\]*+~.()'\"?!:@,&<>〈〉#]/g, // 특정 특수문자 제거\n });\n};\n\n// Function to generate unique slugs\nexport async function generateSlug(\n title: string,\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createSlug(title);\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n\nconst replacements = [\n [/#/g, 'no'],\n [/&/g, 'and'],\n [/%/g, 'percent'],\n] as const;\n\nfunction preprocess(title: string) {\n return replacements.reduce(\n (acc, [regex, value]) => acc.replace(regex, value),\n title\n );\n}\n\n// 서수 접미사 함수\nfunction getOrdinalSuffix(day: number): string {\n if (day > 3 && day < 21) return 'th';\n switch (day % 10) {\n case 1:\n return 'st';\n case 2:\n return 'nd';\n case 3:\n return 'rd';\n default:\n return 'th';\n }\n}\n\nfunction formatDateSlug(date: Date): string {\n const day = Number(format(date, 'd')); // 1~31\n const month = format(date, 'MMM').toLowerCase(); // \"Oct\" → \"oct\"\n const ordinal = getOrdinalSuffix(day);\n return `${day}${ordinal}-${month}`;\n}\n\nexport const createSlug = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\nexport const createSlugHashtag = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '_', // 공백을 \"_\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\n// Function to generate unique slugs\nexport const createConcertSlug = ({\n title,\n date,\n venueName,\n area,\n}: {\n title: string;\n date: Date;\n venueName?: string;\n area?: string;\n}) => {\n let value = `${title}-${formatDateSlug(date)}`;\n if (venueName) {\n value += `-${venueName}`;\n }\n if (area) {\n value += `-${area}`;\n }\n value += '-티켓';\n const slug = slugify(preprocess(`${value}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n\n return slug;\n};\n\n// Function to generate unique slugs\nexport async function generateConcertSlug(\n { title, date, venueName, area }: Parameters<typeof createConcertSlug>[0],\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createConcertSlug({ title, date, venueName, area });\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n","const getEventCategoryUIName = (originalName: string) => {\n switch (originalName) {\n case 'Gigs':\n return '콘서트';\n case 'Theatre':\n return '연극 / 뮤지컬';\n case 'Dance':\n return '무용';\n case 'Korean-Traditional':\n return '국악';\n case 'Classic':\n return '클래식';\n case 'Party':\n return '파티 / 오프라인';\n case 'Dj':\n return '디제잉';\n default:\n return originalName;\n }\n};\n\nexport const eventCategoryUtils = {\n getEventCategoryUIName,\n};\n","const getLocationCityUIName = (originalName: string) => {\n switch (originalName.toLowerCase()) {\n case 'seoul':\n return '서울';\n case 'incheon':\n return '인천';\n case 'yeongjongdo':\n return '영종도';\n case 'ulsan':\n return '울산';\n case 'busan':\n return '부산';\n case 'daegu':\n return '대구';\n case 'jeju':\n return '제주';\n case 'gyeongsangbuk-do':\n return '경상북도';\n case 'gyeongsangnam-do':\n return '경상남도';\n case 'gwangju':\n return '광주';\n case 'daejeon':\n return '대전';\n case 'sejong-city':\n return '세종시';\n case 'gyeonggi-do':\n return '경기도';\n case 'gangwon-do':\n return '강원도';\n case 'chungcheongbuk-do':\n return '충청북도';\n case 'chungcheongnam-do':\n return '충청남도';\n case 'jeollabuk-do':\n return '전라북도';\n case 'jeollanam-do':\n return '전라남도';\n case 'tokyo':\n return '도쿄';\n case 'osaka':\n return '오사카';\n case 'hochiminh':\n return '호치민';\n default:\n return originalName;\n }\n};\n\nexport const locationCityUtils = {\n getLocationCityUIName,\n};\n","import {\n addDays,\n format,\n getDay,\n isSameYear,\n parse,\n startOfDay,\n} from 'date-fns';\nimport { fromZonedTime } from 'date-fns-tz';\nimport { toZonedTime } from 'date-fns-tz';\nimport { enUS, ko } from 'date-fns/locale';\n\nconst timeZone = 'Asia/Seoul';\n\nfunction isDifferentYear(date: Date, now: Date = new Date()) {\n return !isSameYear(date, now);\n}\n\nfunction parseEventDate(\n eventDate: Date,\n options?: {\n formatStyle: 'korean' | 'english';\n }\n) {\n const zonedDate = toZonedTime(eventDate, timeZone);\n const minutes = zonedDate.getMinutes();\n if (options?.formatStyle === 'english') {\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return 'MMM dd, yyyy, h:mm a';\n }\n return 'MMM dd, h:mm a';\n })();\n return format(zonedDate, timeFormat, { locale: enUS });\n }\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return minutes === 0\n ? 'EEEE a h시, yyyy년 MMMM d일'\n : 'EEEE a h시 m분, yyyy년 MMMM d일';\n }\n return minutes === 0 ? 'EEEE a h시, MMMM d일' : 'EEEE a h시 m분, MMMM d일';\n })();\n return format(zonedDate, timeFormat, { locale: ko });\n}\n\nfunction toUTCDayRangeFromYYYYMMDD({\n yyyymmdd,\n}: {\n yyyymmdd: string;\n}) {\n if (!/^\\d{8}$/.test(yyyymmdd)) {\n throw new Error('Invalid date format. Expected YYYYMMDD');\n }\n const kstStart = parse(yyyymmdd, 'yyyyMMdd', new Date());\n const kstEnd = addDays(kstStart, 1);\n // 2. KST → UTC 변환\n const utcStart = fromZonedTime(kstStart, timeZone);\n const utcEnd = fromZonedTime(kstEnd, timeZone);\n return [utcStart, utcEnd];\n}\n\ntype UTCDayRange = {\n label: 'FRIDAY' | 'SATURDAY' | 'SUNDAY';\n utcStart: Date;\n utcEnd: Date;\n};\n\n/**\n * 기준 날짜(yyyymmdd)가 속한 주의\n * 금/토/일 KST 00:00 → UTC Date 반환\n *\n * yyyymmdd가 없으면 현재 시점 기준\n */\nfunction getWeekendUTCStartDates(params?: {\n yyyymmdd?: string;\n}): UTCDayRange[] {\n const baseDate = params?.yyyymmdd\n ? parse(params.yyyymmdd, 'yyyyMMdd', new Date())\n : new Date();\n\n // JS 기준: Sun=0, Mon=1, ... Sat=6\n const baseDay = getDay(baseDate);\n\n // 해당 주의 금요일(5)까지의 offset\n const diffToFriday = (5 - baseDay + 7) % 7;\n\n const kstFridayStart = startOfDay(addDays(baseDate, diffToFriday));\n const kstSaturdayStart = addDays(kstFridayStart, 1);\n const kstSundayStart = addDays(kstFridayStart, 2);\n const kstMondayStart = addDays(kstFridayStart, 3); // Sunday end\n\n return [\n {\n label: 'FRIDAY',\n utcStart: fromZonedTime(kstFridayStart, timeZone),\n utcEnd: fromZonedTime(kstSaturdayStart, timeZone),\n },\n {\n label: 'SATURDAY',\n utcStart: fromZonedTime(kstSaturdayStart, timeZone),\n utcEnd: fromZonedTime(kstSundayStart, timeZone),\n },\n {\n label: 'SUNDAY',\n utcStart: fromZonedTime(kstSundayStart, timeZone),\n utcEnd: fromZonedTime(kstMondayStart, timeZone),\n },\n ];\n}\n\nexport const dateUtils = {\n parseEventDate,\n toUTCDayRangeFromYYYYMMDD,\n getWeekendUTCStartDates,\n};\n","/**\n * URL을 완전히 디코딩 (이중/삼중 인코딩 모두 해결)\n */\nexport function fullyDecodeURI(uri: string): string {\n let decoded = uri;\n let prevDecoded = '';\n\n // 더 이상 디코딩되지 않을 때까지 반복\n while (decoded !== prevDecoded) {\n prevDecoded = decoded;\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n // 잘못된 URI 시퀀스면 중단\n break;\n }\n }\n\n return decoded;\n}\n\n/**\n * pathname만 디코딩 (프로토콜, 도메인은 유지)\n */\nexport function fullyDecodePathname(url: string): string {\n try {\n const urlObj = new URL(url);\n urlObj.pathname = fullyDecodeURI(urlObj.pathname);\n return urlObj.toString();\n } catch {\n // URL 파싱 실패 시 전체 문자열 디코딩\n return fullyDecodeURI(url);\n }\n}\n\n/**\n * %가 포함되어 있는지 확인 (인코딩 여부 체크)\n */\nexport function isEncoded(str: string): boolean {\n return str.includes('%');\n}\n\n/**\n * 실제 double-encoding 여부 확인\n * 예: \"%2525\" => true\n * \"%25\" => false (단일 인코딩)\n */\nexport function isDoubleEncoded(str: string): boolean {\n // \"%25\"가 아닌 \"%2525\" 패턴을 찾아야 double-encoding\n return /%25(25)+/i.test(str);\n}\n"],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`react`));function l(e,t){let n=(0,c.useRef)(e),r=(0,c.useRef)(null);return(0,c.useEffect)(()=>{n.current=e}),(0,c.useCallback)((...e)=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>n.current(...e),t)},[t])}exports.useDebouncedCallback=l; | ||
| var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`react`)),l={ko:`ko_KR`,en:`en_US`},u=`https://schema.org`;function d(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function f(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function p(e,t){return t.startsWith(`http`)?t:f(e,t)}function m(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function h(e,t){return p(e,t)}function g(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?f(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function _(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function v(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function y(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function b(e,t){let n=e.name,r=e.image?p(t,e.image):void 0,i=e.url?h(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),s=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,c=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:s,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:s,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:c,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:c,image:r,url:i,sameAs:a,datePublished:o}}}function x(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:b(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function S(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:h(t,e.path)}))}}function C(e,t){return{"@type":`MusicEvent`,name:e.name,url:h(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>p(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:p(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function w(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let i=t.baseUrl.replace(/\/$/,``),a=`${i}/#org`,o=`${i}/#editor`,s=`${i}/#website`,c=[];for(let l of r)switch(l.type){case`WebSite`:c.push(v(t,n,s,a));break;case`Article`:{let r=y(e,t,n,a,o);r&&c.push(r);break}case`Review`:c.push(x(l.itemReviewed,l.rating,e,t,n,i,a,o));break;case`CreativeWork`:c.push(b(l.item,i));break;case`EventPage`:c.push(C(l.event,i));break;case`Breadcrumb`:c.push(S(l.items,i));break}if(c.length===0)return[];let l=[],d=g(t,a);d&&l.push(d);let f=_(t,o);f&&l.push(f);let p={"@context":u,"@graph":[...l,...c]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(p)}]}function T(e,t){let n=e.lang??`ko`,r=RegExp(`\\b${d(t.name)}\\b`).test(e.title),i=r?e.title:`${e.title} | ${t.name}`,a=h(t.baseUrl,e.path),o=e.alternates??[],s={...l,...t.locales},c=[{rel:`canonical`,href:a}];if(o.length>0){c.push({rel:`alternate`,hreflang:n,href:a});for(let e of o)c.push({rel:`alternate`,hreflang:e.lang,href:h(t.baseUrl,e.path)});let r=o.find(e=>e.lang===`ko`)?.path??e.path;c.push({rel:`alternate`,hreflang:`x-default`,href:h(t.baseUrl,r)})}let u=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:i},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:a},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:s[n]}],g=[...new Set([...e.keywords??[],...t.keywords??[]])];g.length>0&&u.push({name:`keywords`,content:g.join(`, `)});let _=e.image??t.defaultImage,v=_?m(p(t.baseUrl,_.path),t.assetVersion,_.path.startsWith(`http`)):void 0;_&&v&&(u.push({property:`og:image`,content:v}),_.type&&u.push({property:`og:image:type`,content:_.type}),_.width&&u.push({property:`og:image:width`,content:String(_.width)}),_.height&&u.push({property:`og:image:height`,content:String(_.height)}),_.alt&&u.push({property:`og:image:alt`,content:_.alt}),u.push({name:`twitter:image`,content:v}),_.alt&&u.push({name:`twitter:image:alt`,content:_.alt})),t.logoPath&&u.push({property:`og:logo`,content:f(t.baseUrl,t.logoPath)});let y=!!_&&!!_.width&&!!_.height&&_.width>=_.height*1.5,b=y?`summary_large_image`:`summary`;u.push({name:`twitter:card`,content:b},{name:`twitter:title`,content:i},{name:`twitter:description`,content:e.description});for(let e of o)e.lang!==n&&u.push({property:`og:locale:alternate`,content:s[e.lang]});if(e.article){u.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&u.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&u.push({property:`article:author`,content:e.article.author}),e.article.section&&u.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])u.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(u.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),u.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&u.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(u.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&u.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&u.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let x=w(e,t,{url:a,fullTitle:i,imageUrl:v,lang:n});return{title:i,htmlLang:n,meta:u,link:c,script:x}}function E(e,t){let n=(0,c.useRef)(e),r=(0,c.useRef)(null);return(0,c.useEffect)(()=>{n.current=e}),(0,c.useCallback)((...e)=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>n.current(...e),t)},[t])}function D(e){return t=>{let{robots:n,...r}=t,i=T(r,e),a=[{title:i.title}];for(let e of i.meta)if(e.name){let t=e.name===`robots`&&n?n:e.content;a.push({name:e.name,content:t})}else e.property&&a.push({property:e.property,content:e.content});let o=i.link.map(e=>({rel:e.rel,href:e.href,...e.hreflang?{hreflang:e.hreflang}:{}})),s=i.script.map(e=>({type:e.type,children:e.innerHTML}));return{meta:a,links:o,scripts:s}}}exports.createSeoHead=D,exports.useDebouncedCallback=E; |
+232
-3
@@ -0,7 +1,236 @@ | ||
| //#region src/metadata/core.d.ts | ||
| type Lang = 'ko' | 'en'; | ||
| type SeoImage = { | ||
| /** Absolute URL or path starting with `/`. */ | ||
| path: string; | ||
| width?: number; | ||
| height?: number; | ||
| type?: string; | ||
| alt?: string; | ||
| }; | ||
| type ArticleMeta = { | ||
| /** ISO 8601 timestamp. */ | ||
| publishedTime: string; | ||
| /** ISO 8601 timestamp. */ | ||
| modifiedTime?: string; | ||
| author?: string; | ||
| section?: string; | ||
| tags?: string[]; | ||
| }; | ||
| /** | ||
| * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다. | ||
| * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등). | ||
| */ | ||
| type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events'; | ||
| /** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */ | ||
| type JsonLdWork = { | ||
| kind: CreativeWorkKind; | ||
| name: string; | ||
| /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */ | ||
| by?: string; | ||
| /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */ | ||
| year?: number | string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| image?: string; | ||
| /** 작품 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url?: string; | ||
| /** 외부 출처 — bandcamp · spotify · imdb 등. */ | ||
| sameAs?: string[]; | ||
| }; | ||
| type JsonLdRating = { | ||
| value: number; | ||
| best?: number; | ||
| worst?: number; | ||
| }; | ||
| type JsonLdEventOffer = { | ||
| price: number; | ||
| currency: string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 티켓 오픈 시각. */ | ||
| validFrom: string; | ||
| name?: string; | ||
| }; | ||
| /** | ||
| * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와 | ||
| * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다. | ||
| */ | ||
| type JsonLdEvent = { | ||
| name: string; | ||
| /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */ | ||
| startDate: string; | ||
| /** ISO 8601 */ | ||
| endDate?: string; | ||
| venue: { | ||
| name: string; | ||
| address: string; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| /** Path(`/...`) 또는 절대 URL 목록. */ | ||
| images?: string[]; | ||
| description?: string; | ||
| offers?: JsonLdEventOffer[]; | ||
| /** 주최자명. 미지정 시 `venue.name` 으로 대체. */ | ||
| organizer?: string; | ||
| }; | ||
| /** | ||
| * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다. | ||
| * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다. | ||
| */ | ||
| type JsonLd = { | ||
| type: 'WebSite'; | ||
| } | ||
| /** `input.article` 에서 파생 — article 없으면 무시. */ | { | ||
| type: 'Article'; | ||
| } | { | ||
| type: 'Review'; | ||
| itemReviewed: JsonLdWork; | ||
| rating?: JsonLdRating; | ||
| } | { | ||
| type: 'CreativeWork'; | ||
| item: JsonLdWork; | ||
| } | ||
| /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | { | ||
| type: 'EventPage'; | ||
| event: JsonLdEvent; | ||
| } | { | ||
| type: 'Breadcrumb'; | ||
| items: { | ||
| name: string; | ||
| path: string; | ||
| }[]; | ||
| }; | ||
| type PageSeoInput = { | ||
| title: string; | ||
| description: string; | ||
| path: string; | ||
| lang?: Lang; | ||
| /** Alternate language versions for this page. Emits hreflang link tags. */ | ||
| alternates?: { | ||
| lang: Lang; | ||
| path: string; | ||
| }[]; | ||
| /** When set, og:type switches to `article` and article:* tags are emitted. */ | ||
| article?: ArticleMeta; | ||
| /** Override the default OG/Twitter share image for this page. */ | ||
| image?: SeoImage; | ||
| /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */ | ||
| jsonLd?: JsonLd[]; | ||
| /** | ||
| * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks` | ||
| * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시. | ||
| */ | ||
| appLinks?: { | ||
| iosUrl?: string; | ||
| androidUrl?: string; | ||
| }; | ||
| }; | ||
| type SiteConfig = { | ||
| /** Display name appended to every page title. */ | ||
| name: string; | ||
| /** Origin used to resolve absolute URLs. Trailing slash is normalized. */ | ||
| baseUrl: string; | ||
| /** Default OG/Twitter image used when a page does not override it. */ | ||
| defaultImage?: SeoImage; | ||
| /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */ | ||
| logoPath?: string; | ||
| /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */ | ||
| locales?: Partial<Record<Lang, string>>; | ||
| /** Site-wide keywords merged into every page's `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** | ||
| * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter | ||
| * image URLs (paths, not external `http` URLs). Bump it when an image is replaced | ||
| * in place under the same filename so social scrapers (Threads/Slack/iMessage 등) | ||
| * treat it as a new URL instead of serving their stale cache. | ||
| */ | ||
| assetVersion?: string; | ||
| /** Publishing organization — emitted as the `Organization` JSON-LD node. */ | ||
| publisher?: { | ||
| name: string; | ||
| url?: string; | ||
| logoPath?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */ | ||
| editor?: { | ||
| name: string; | ||
| url?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** | ||
| * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit. | ||
| * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다 | ||
| * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다. | ||
| */ | ||
| appLinks?: { | ||
| ios?: { | ||
| appStoreId: string; | ||
| appName: string; | ||
| }; | ||
| android?: { | ||
| packageName: string; | ||
| appName?: string; | ||
| }; | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/react/index.d.ts | ||
| declare function useDebouncedCallback<T extends (...args: any[]) => void>(fn: T, delay: number): (...args: Parameters<T>) => void; | ||
| //# sourceMappingURL=index.d.ts.map | ||
| type HeadMeta = { | ||
| title: string; | ||
| } | { | ||
| charSet: string; | ||
| } | { | ||
| name: string; | ||
| content: string; | ||
| } | { | ||
| property: string; | ||
| content: string; | ||
| } | { | ||
| httpEquiv: string; | ||
| content: string; | ||
| }; | ||
| type HeadLink = { | ||
| rel: string; | ||
| href: string; | ||
| hreflang?: string; | ||
| type?: string; | ||
| sizes?: string; | ||
| }; | ||
| type HeadScript = { | ||
| children?: string; | ||
| src?: string; | ||
| type?: string; | ||
| async?: boolean; | ||
| }; | ||
| type SeoHead = { | ||
| meta: HeadMeta[]; | ||
| links: HeadLink[]; | ||
| scripts: HeadScript[]; | ||
| }; | ||
| /** `PageSeoInput` + `robots` override. 미지정 시 엔진 기본값 `'index, follow'`. */ | ||
| type SeoHeadInput = PageSeoInput & { | ||
| robots?: string; | ||
| }; | ||
| /** | ||
| * `SiteConfig` 를 한 번 바인딩해, 라우트에서 단일 declarative 입력으로 호출하는 `seoHead(input)` 를 만든다. | ||
| * | ||
| * // app 진입 1회 | ||
| * export const seoHead = createSeoHead({ ...SITE, baseUrl }) | ||
| * | ||
| * // 라우트 | ||
| * head: ({ match }) => seoHead({ path: '/about', lang: match.context.lang, title, description }) | ||
| * head: () => seoHead({ path: '/resume', lang: 'ko', ...copy, robots: 'noindex, nofollow' }) | ||
| * | ||
| * 반환값(`{ meta, links, scripts }`)을 라우터가 `<head>` 로 직렬화한다. | ||
| */ | ||
| declare function createSeoHead(site: SiteConfig): (input: SeoHeadInput) => SeoHead; | ||
| //#endregion | ||
| export { useDebouncedCallback }; | ||
| export { type ArticleMeta, HeadLink, HeadMeta, HeadScript, type JsonLd, type JsonLdEvent, type JsonLdEventOffer, type JsonLdWork, type Lang, type PageSeoInput, SeoHead, SeoHeadInput, type SeoImage, type SiteConfig, createSeoHead, useDebouncedCallback }; | ||
| //# sourceMappingURL=index.d.cts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/src/react/index.ts"],"sourcesContent":[],"mappings":";iBAGgB,6DACV,6BAEO,WAAW;AAHxB"} | ||
| {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/metadata/core.ts","../../src/react/index.ts"],"sourcesContent":[],"mappings":"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"} |
+232
-3
@@ -0,7 +1,236 @@ | ||
| //#region src/metadata/core.d.ts | ||
| type Lang = 'ko' | 'en'; | ||
| type SeoImage = { | ||
| /** Absolute URL or path starting with `/`. */ | ||
| path: string; | ||
| width?: number; | ||
| height?: number; | ||
| type?: string; | ||
| alt?: string; | ||
| }; | ||
| type ArticleMeta = { | ||
| /** ISO 8601 timestamp. */ | ||
| publishedTime: string; | ||
| /** ISO 8601 timestamp. */ | ||
| modifiedTime?: string; | ||
| author?: string; | ||
| section?: string; | ||
| tags?: string[]; | ||
| }; | ||
| /** | ||
| * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다. | ||
| * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등). | ||
| */ | ||
| type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events'; | ||
| /** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */ | ||
| type JsonLdWork = { | ||
| kind: CreativeWorkKind; | ||
| name: string; | ||
| /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */ | ||
| by?: string; | ||
| /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */ | ||
| year?: number | string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| image?: string; | ||
| /** 작품 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url?: string; | ||
| /** 외부 출처 — bandcamp · spotify · imdb 등. */ | ||
| sameAs?: string[]; | ||
| }; | ||
| type JsonLdRating = { | ||
| value: number; | ||
| best?: number; | ||
| worst?: number; | ||
| }; | ||
| type JsonLdEventOffer = { | ||
| price: number; | ||
| currency: string; | ||
| /** Path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 티켓 오픈 시각. */ | ||
| validFrom: string; | ||
| name?: string; | ||
| }; | ||
| /** | ||
| * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와 | ||
| * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다. | ||
| */ | ||
| type JsonLdEvent = { | ||
| name: string; | ||
| /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */ | ||
| url: string; | ||
| /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */ | ||
| startDate: string; | ||
| /** ISO 8601 */ | ||
| endDate?: string; | ||
| venue: { | ||
| name: string; | ||
| address: string; | ||
| latitude: number; | ||
| longitude: number; | ||
| }; | ||
| /** Path(`/...`) 또는 절대 URL 목록. */ | ||
| images?: string[]; | ||
| description?: string; | ||
| offers?: JsonLdEventOffer[]; | ||
| /** 주최자명. 미지정 시 `venue.name` 으로 대체. */ | ||
| organizer?: string; | ||
| }; | ||
| /** | ||
| * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다. | ||
| * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다. | ||
| */ | ||
| type JsonLd = { | ||
| type: 'WebSite'; | ||
| } | ||
| /** `input.article` 에서 파생 — article 없으면 무시. */ | { | ||
| type: 'Article'; | ||
| } | { | ||
| type: 'Review'; | ||
| itemReviewed: JsonLdWork; | ||
| rating?: JsonLdRating; | ||
| } | { | ||
| type: 'CreativeWork'; | ||
| item: JsonLdWork; | ||
| } | ||
| /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | { | ||
| type: 'EventPage'; | ||
| event: JsonLdEvent; | ||
| } | { | ||
| type: 'Breadcrumb'; | ||
| items: { | ||
| name: string; | ||
| path: string; | ||
| }[]; | ||
| }; | ||
| type PageSeoInput = { | ||
| title: string; | ||
| description: string; | ||
| path: string; | ||
| lang?: Lang; | ||
| /** Alternate language versions for this page. Emits hreflang link tags. */ | ||
| alternates?: { | ||
| lang: Lang; | ||
| path: string; | ||
| }[]; | ||
| /** When set, og:type switches to `article` and article:* tags are emitted. */ | ||
| article?: ArticleMeta; | ||
| /** Override the default OG/Twitter share image for this page. */ | ||
| image?: SeoImage; | ||
| /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */ | ||
| jsonLd?: JsonLd[]; | ||
| /** | ||
| * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks` | ||
| * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시. | ||
| */ | ||
| appLinks?: { | ||
| iosUrl?: string; | ||
| androidUrl?: string; | ||
| }; | ||
| }; | ||
| type SiteConfig = { | ||
| /** Display name appended to every page title. */ | ||
| name: string; | ||
| /** Origin used to resolve absolute URLs. Trailing slash is normalized. */ | ||
| baseUrl: string; | ||
| /** Default OG/Twitter image used when a page does not override it. */ | ||
| defaultImage?: SeoImage; | ||
| /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */ | ||
| logoPath?: string; | ||
| /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */ | ||
| locales?: Partial<Record<Lang, string>>; | ||
| /** Site-wide keywords merged into every page's `keywords` meta. */ | ||
| keywords?: string[]; | ||
| /** | ||
| * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter | ||
| * image URLs (paths, not external `http` URLs). Bump it when an image is replaced | ||
| * in place under the same filename so social scrapers (Threads/Slack/iMessage 등) | ||
| * treat it as a new URL instead of serving their stale cache. | ||
| */ | ||
| assetVersion?: string; | ||
| /** Publishing organization — emitted as the `Organization` JSON-LD node. */ | ||
| publisher?: { | ||
| name: string; | ||
| url?: string; | ||
| logoPath?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */ | ||
| editor?: { | ||
| name: string; | ||
| url?: string; | ||
| sameAs?: string[]; | ||
| }; | ||
| /** | ||
| * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit. | ||
| * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다 | ||
| * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다. | ||
| */ | ||
| appLinks?: { | ||
| ios?: { | ||
| appStoreId: string; | ||
| appName: string; | ||
| }; | ||
| android?: { | ||
| packageName: string; | ||
| appName?: string; | ||
| }; | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/react/index.d.ts | ||
| declare function useDebouncedCallback<T extends (...args: any[]) => void>(fn: T, delay: number): (...args: Parameters<T>) => void; | ||
| //# sourceMappingURL=index.d.ts.map | ||
| type HeadMeta = { | ||
| title: string; | ||
| } | { | ||
| charSet: string; | ||
| } | { | ||
| name: string; | ||
| content: string; | ||
| } | { | ||
| property: string; | ||
| content: string; | ||
| } | { | ||
| httpEquiv: string; | ||
| content: string; | ||
| }; | ||
| type HeadLink = { | ||
| rel: string; | ||
| href: string; | ||
| hreflang?: string; | ||
| type?: string; | ||
| sizes?: string; | ||
| }; | ||
| type HeadScript = { | ||
| children?: string; | ||
| src?: string; | ||
| type?: string; | ||
| async?: boolean; | ||
| }; | ||
| type SeoHead = { | ||
| meta: HeadMeta[]; | ||
| links: HeadLink[]; | ||
| scripts: HeadScript[]; | ||
| }; | ||
| /** `PageSeoInput` + `robots` override. 미지정 시 엔진 기본값 `'index, follow'`. */ | ||
| type SeoHeadInput = PageSeoInput & { | ||
| robots?: string; | ||
| }; | ||
| /** | ||
| * `SiteConfig` 를 한 번 바인딩해, 라우트에서 단일 declarative 입력으로 호출하는 `seoHead(input)` 를 만든다. | ||
| * | ||
| * // app 진입 1회 | ||
| * export const seoHead = createSeoHead({ ...SITE, baseUrl }) | ||
| * | ||
| * // 라우트 | ||
| * head: ({ match }) => seoHead({ path: '/about', lang: match.context.lang, title, description }) | ||
| * head: () => seoHead({ path: '/resume', lang: 'ko', ...copy, robots: 'noindex, nofollow' }) | ||
| * | ||
| * 반환값(`{ meta, links, scripts }`)을 라우터가 `<head>` 로 직렬화한다. | ||
| */ | ||
| declare function createSeoHead(site: SiteConfig): (input: SeoHeadInput) => SeoHead; | ||
| //#endregion | ||
| export { useDebouncedCallback }; | ||
| export { type ArticleMeta, HeadLink, HeadMeta, HeadScript, type JsonLd, type JsonLdEvent, type JsonLdEventOffer, type JsonLdWork, type Lang, type PageSeoInput, SeoHead, SeoHeadInput, type SeoImage, type SiteConfig, createSeoHead, useDebouncedCallback }; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/src/react/index.ts"],"sourcesContent":[],"mappings":";iBAGgB,6DACV,6BAEO,WAAW;AAHxB"} | ||
| {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/metadata/core.ts","../../src/react/index.ts"],"sourcesContent":[],"mappings":"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"} |
@@ -1,2 +0,2 @@ | ||
| import{useCallback as e,useEffect as t,useRef as n}from"react";function r(r,i){let a=n(r),o=n(null);return t(()=>{a.current=r}),e((...e)=>{o.current&&clearTimeout(o.current),o.current=setTimeout(()=>a.current(...e),i)},[i])}export{r as useDebouncedCallback}; | ||
| import{useCallback as e,useEffect as t,useRef as n}from"react";const r={ko:`ko_KR`,en:`en_US`},i=`https://schema.org`;function a(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function o(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function s(e,t){return t.startsWith(`http`)?t:o(e,t)}function c(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function l(e,t){return s(e,t)}function u(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?o(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function d(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function f(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function p(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function m(e,t){let n=e.name,r=e.image?s(t,e.image):void 0,i=e.url?l(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),c=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,u=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:c,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:c,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:c,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:c,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:u,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:u,image:r,url:i,sameAs:a,datePublished:o}}}function h(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:m(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function g(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:l(t,e.path)}))}}function _(e,t){return{"@type":`MusicEvent`,name:e.name,url:l(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>s(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:s(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function v(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let a=t.baseUrl.replace(/\/$/,``),o=`${a}/#org`,s=`${a}/#editor`,c=`${a}/#website`,l=[];for(let i of r)switch(i.type){case`WebSite`:l.push(f(t,n,c,o));break;case`Article`:{let r=p(e,t,n,o,s);r&&l.push(r);break}case`Review`:l.push(h(i.itemReviewed,i.rating,e,t,n,a,o,s));break;case`CreativeWork`:l.push(m(i.item,a));break;case`EventPage`:l.push(_(i.event,a));break;case`Breadcrumb`:l.push(g(i.items,a));break}if(l.length===0)return[];let v=[],y=u(t,o);y&&v.push(y);let b=d(t,s);b&&v.push(b);let x={"@context":i,"@graph":[...v,...l]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(x)}]}function y(e,t){let n=e.lang??`ko`,i=RegExp(`\\b${a(t.name)}\\b`).test(e.title),u=i?e.title:`${e.title} | ${t.name}`,d=l(t.baseUrl,e.path),f=e.alternates??[],p={...r,...t.locales},m=[{rel:`canonical`,href:d}];if(f.length>0){m.push({rel:`alternate`,hreflang:n,href:d});for(let e of f)m.push({rel:`alternate`,hreflang:e.lang,href:l(t.baseUrl,e.path)});let r=f.find(e=>e.lang===`ko`)?.path??e.path;m.push({rel:`alternate`,hreflang:`x-default`,href:l(t.baseUrl,r)})}let h=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:u},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:d},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:p[n]}],g=[...new Set([...e.keywords??[],...t.keywords??[]])];g.length>0&&h.push({name:`keywords`,content:g.join(`, `)});let _=e.image??t.defaultImage,y=_?c(s(t.baseUrl,_.path),t.assetVersion,_.path.startsWith(`http`)):void 0;_&&y&&(h.push({property:`og:image`,content:y}),_.type&&h.push({property:`og:image:type`,content:_.type}),_.width&&h.push({property:`og:image:width`,content:String(_.width)}),_.height&&h.push({property:`og:image:height`,content:String(_.height)}),_.alt&&h.push({property:`og:image:alt`,content:_.alt}),h.push({name:`twitter:image`,content:y}),_.alt&&h.push({name:`twitter:image:alt`,content:_.alt})),t.logoPath&&h.push({property:`og:logo`,content:o(t.baseUrl,t.logoPath)});let b=!!_&&!!_.width&&!!_.height&&_.width>=_.height*1.5,x=b?`summary_large_image`:`summary`;h.push({name:`twitter:card`,content:x},{name:`twitter:title`,content:u},{name:`twitter:description`,content:e.description});for(let e of f)e.lang!==n&&h.push({property:`og:locale:alternate`,content:p[e.lang]});if(e.article){h.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&h.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&h.push({property:`article:author`,content:e.article.author}),e.article.section&&h.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])h.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(h.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),h.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&h.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(h.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&h.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&h.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let S=v(e,t,{url:d,fullTitle:u,imageUrl:y,lang:n});return{title:u,htmlLang:n,meta:h,link:m,script:S}}function b(r,i){let a=n(r),o=n(null);return t(()=>{a.current=r}),e((...e)=>{o.current&&clearTimeout(o.current),o.current=setTimeout(()=>a.current(...e),i)},[i])}function x(e){return t=>{let{robots:n,...r}=t,i=y(r,e),a=[{title:i.title}];for(let e of i.meta)if(e.name){let t=e.name===`robots`&&n?n:e.content;a.push({name:e.name,content:t})}else e.property&&a.push({property:e.property,content:e.content});let o=i.link.map(e=>({rel:e.rel,href:e.href,...e.hreflang?{hreflang:e.hreflang}:{}})),s=i.script.map(e=>({type:e.type,children:e.innerHTML}));return{meta:a,links:o,scripts:s}}}export{x as createSeoHead,b as useDebouncedCallback}; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","names":["fn: T","delay: number"],"sources":["../../src/react/index.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\n// biome-ignore lint/suspicious/noExplicitAny: generic callback signature\nexport function useDebouncedCallback<T extends (...args: any[]) => void>(\n fn: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const fnRef = useRef(fn);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n fnRef.current = fn;\n });\n\n return useCallback(\n (...args: Parameters<T>) => {\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => fnRef.current(...args), delay);\n },\n [delay]\n );\n}\n"],"mappings":"+DAGA,SAAgB,EACdA,EACAC,EACkC,CAElC,IADM,EAAQ,EAAO,EAAG,CAClB,EAAW,EAA6C,KAAK,CAMnE,MAJA,GAAU,IAAM,CACd,EAAM,QAAU,CACjB,EAAC,CAEK,EACL,CAAC,GAAG,IAAwB,CAE1B,AADI,EAAS,SAAS,aAAa,EAAS,QAAQ,CACpD,EAAS,QAAU,WAAW,IAAM,EAAM,QAAQ,GAAG,EAAK,CAAE,EAAM,AACnE,EACD,CAAC,CAAM,EACR,AACF"} | ||
| {"version":3,"file":"index.js","names":["DEFAULT_LOCALES: Record<Lang, string>","s: string","base: string","path: string","value: string","url: string","version: string | undefined","external: boolean","site: SiteConfig","orgId: string","editorId: string","ctx: JsonLdContext","websiteId: string","input: PageSeoInput","work: JsonLdWork","itemReviewed: JsonLdWork","rating: JsonLdRating | undefined","items: { name: string; path: string }[]","event: JsonLdEvent","pageNodes: Thing[]","baseNodes: Thing[]","lang: Lang","link: SeoLink[]","meta: SeoMeta[]","fn: T","delay: number","site: SiteConfig","meta: HeadMeta[]","links: HeadLink[]","scripts: HeadScript[]"],"sources":["../../src/metadata/core.ts","../../src/react/index.ts"],"sourcesContent":["import type {\n Article,\n Book,\n BreadcrumbList,\n Event,\n Graph,\n Movie,\n MusicAlbum,\n MusicEvent,\n MusicRecording,\n Organization,\n Person,\n Review,\n Thing,\n WebSite,\n} from 'schema-dts';\n\nexport type Lang = 'ko' | 'en';\n\nexport type SeoImage = {\n /** Absolute URL or path starting with `/`. */\n path: string;\n width?: number;\n height?: number;\n type?: string;\n alt?: string;\n};\n\nexport type ArticleMeta = {\n /** ISO 8601 timestamp. */\n publishedTime: string;\n /** ISO 8601 timestamp. */\n modifiedTime?: string;\n author?: string;\n section?: string;\n tags?: string[];\n};\n\n/**\n * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.\n * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).\n */\nexport type CreativeWorkKind =\n | 'albums'\n | 'tracks'\n | 'concerts'\n | 'films'\n | 'books'\n | 'events';\n\n/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */\nexport type JsonLdWork = {\n kind: CreativeWorkKind;\n name: string;\n /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */\n by?: string;\n /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */\n year?: number | string;\n /** Path(`/...`) 또는 절대 URL. */\n image?: string;\n /** 작품 페이지 path(`/...`) 또는 절대 URL. */\n url?: string;\n /** 외부 출처 — bandcamp · spotify · imdb 등. */\n sameAs?: string[];\n};\n\nexport type JsonLdRating = { value: number; best?: number; worst?: number };\n\nexport type JsonLdEventOffer = {\n price: number;\n currency: string;\n /** Path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 티켓 오픈 시각. */\n validFrom: string;\n name?: string;\n};\n\n/**\n * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와\n * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.\n */\nexport type JsonLdEvent = {\n name: string;\n /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */\n startDate: string;\n /** ISO 8601 */\n endDate?: string;\n venue: {\n name: string;\n address: string;\n latitude: number;\n longitude: number;\n };\n /** Path(`/...`) 또는 절대 URL 목록. */\n images?: string[];\n description?: string;\n offers?: JsonLdEventOffer[];\n /** 주최자명. 미지정 시 `venue.name` 으로 대체. */\n organizer?: string;\n};\n\n/**\n * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.\n * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.\n */\nexport type JsonLd =\n | { type: 'WebSite' }\n /** `input.article` 에서 파생 — article 없으면 무시. */\n | { type: 'Article' }\n | { type: 'Review'; itemReviewed: JsonLdWork; rating?: JsonLdRating }\n | { type: 'CreativeWork'; item: JsonLdWork }\n /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */\n | { type: 'EventPage'; event: JsonLdEvent }\n | { type: 'Breadcrumb'; items: { name: string; path: string }[] };\n\nexport type PageSeoInput = {\n title: string;\n description: string;\n path: string;\n lang?: Lang;\n /** Alternate language versions for this page. Emits hreflang link tags. */\n alternates?: { lang: Lang; path: string }[];\n /** When set, og:type switches to `article` and article:* tags are emitted. */\n article?: ArticleMeta;\n /** Override the default OG/Twitter share image for this page. */\n image?: SeoImage;\n /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */\n keywords?: string[];\n /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */\n jsonLd?: JsonLd[];\n /**\n * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`\n * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.\n */\n appLinks?: { iosUrl?: string; androidUrl?: string };\n};\n\nexport type SiteConfig = {\n /** Display name appended to every page title. */\n name: string;\n /** Origin used to resolve absolute URLs. Trailing slash is normalized. */\n baseUrl: string;\n /** Default OG/Twitter image used when a page does not override it. */\n defaultImage?: SeoImage;\n /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */\n logoPath?: string;\n /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */\n locales?: Partial<Record<Lang, string>>;\n /** Site-wide keywords merged into every page's `keywords` meta. */\n keywords?: string[];\n /**\n * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter\n * image URLs (paths, not external `http` URLs). Bump it when an image is replaced\n * in place under the same filename so social scrapers (Threads/Slack/iMessage 등)\n * treat it as a new URL instead of serving their stale cache.\n */\n assetVersion?: string;\n /** Publishing organization — emitted as the `Organization` JSON-LD node. */\n publisher?: {\n name: string;\n url?: string;\n logoPath?: string;\n sameAs?: string[];\n };\n /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */\n editor?: { name: string; url?: string; sameAs?: string[] };\n /**\n * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.\n * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다\n * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.\n */\n appLinks?: {\n ios?: { appStoreId: string; appName: string };\n android?: { packageName: string; appName?: string };\n };\n};\n\nexport type SeoMeta = { name?: string; property?: string; content: string };\nexport type SeoLink = { rel: string; href: string; hreflang?: string };\nexport type SeoScript = { type: 'application/ld+json'; innerHTML: string };\n\nexport type SeoTags = {\n title: string;\n htmlLang: Lang;\n meta: SeoMeta[];\n link: SeoLink[];\n /** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */\n script: SeoScript[];\n};\n\nconst DEFAULT_LOCALES: Record<Lang, string> = {\n ko: 'ko_KR',\n en: 'en_US',\n};\n\nconst SCHEMA_CONTEXT = 'https://schema.org';\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction joinUrl(base: string, path: string): string {\n const b = base.replace(/\\/$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${b}${p}`;\n}\n\n/** Resolve a value that may be a path(`/...`) or an already-absolute URL. */\nfunction absoluteUrl(base: string, value: string): string {\n return value.startsWith('http') ? value : joinUrl(base, value);\n}\n\n/**\n * Append `?v=<version>` (or `&v=`) for cache-busting. No-op when version is empty.\n * `external` skips the param entirely — we don't control third-party CDN URLs\n * (signed poster URLs could break) and only want to bust our own re-uploaded assets.\n */\nfunction withAssetVersion(\n url: string,\n version: string | undefined,\n external: boolean\n): string {\n if (!version || external) return url;\n return `${url}${url.includes('?') ? '&' : '?'}v=${encodeURIComponent(version)}`;\n}\n\n/**\n * Absolute *page* (route) URL.\n *\n * 과거 Next.js (`trailingSlash: true` + directory-index 호스팅) 시절엔 모든 page URL 에\n * `/` 를 강제로 붙여 canonical 과 sitemap 을 호스트 redirect 와 정합시켰지만, TSS +\n * Cloudflare Workers SSR 로 이관 후엔 라우트가 slash 없이 서빙된다 (`/event/foo` 는 200,\n * `/event/foo/` 는 의도된 canonical 이 아님). 따라서 `absoluteUrl` 결과를 그대로 둔다.\n */\nfunction pageUrl(base: string, value: string): string {\n return absoluteUrl(base, value);\n}\n\ntype JsonLdContext = {\n url: string;\n fullTitle: string;\n imageUrl?: string;\n lang: Lang;\n};\n\nfunction orgNode(site: SiteConfig, orgId: string): Organization | null {\n const p = site.publisher;\n if (!p) return null;\n return {\n '@type': 'Organization',\n '@id': orgId,\n name: p.name,\n url: p.url ?? site.baseUrl.replace(/\\/$/, ''),\n logo: p.logoPath ? joinUrl(site.baseUrl, p.logoPath) : undefined,\n sameAs: p.sameAs,\n } satisfies Organization;\n}\n\nfunction personNode(site: SiteConfig, editorId: string): Person | null {\n const e = site.editor;\n if (!e) return null;\n return {\n '@type': 'Person',\n '@id': editorId,\n name: e.name,\n url: e.url,\n sameAs: e.sameAs,\n } satisfies Person;\n}\n\nfunction websiteNode(\n site: SiteConfig,\n ctx: JsonLdContext,\n websiteId: string,\n orgId: string\n): WebSite {\n return {\n '@type': 'WebSite',\n '@id': websiteId,\n name: site.name,\n url: site.baseUrl.replace(/\\/$/, ''),\n inLanguage: ctx.lang,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies WebSite;\n}\n\nfunction articleNode(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n orgId: string,\n editorId: string\n): Article | null {\n const a = input.article;\n if (!a) return null;\n return {\n '@type': 'BlogPosting',\n headline: input.title,\n description: input.description,\n url: ctx.url,\n mainEntityOfPage: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a.publishedTime,\n dateModified: a.modifiedTime,\n image: ctx.imageUrl,\n articleSection: a.section,\n keywords: a.tags,\n author: site.editor\n ? { '@id': editorId }\n : a.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies Article;\n}\n\n/**\n * `JsonLdWork` → schema.org 작품 노드. kind 에 따라 @type 과 creator 프로퍼티를 매핑한다.\n * creator(`by`)는 문자열이 아니라 `MusicGroup`/`Person` 객체로 감싸야 schema-dts 가 받는다.\n */\nfunction workNode(work: JsonLdWork, base: string): Thing {\n const name = work.name;\n const image = work.image ? absoluteUrl(base, work.image) : undefined;\n const url = work.url ? pageUrl(base, work.url) : undefined;\n const sameAs = work.sameAs?.length ? work.sameAs : undefined;\n const year = work.year != null ? String(work.year) : undefined;\n const musicGroup = work.by\n ? ({ '@type': 'MusicGroup', name: work.by } as const)\n : undefined;\n const person = work.by\n ? ({ '@type': 'Person', name: work.by } as const)\n : undefined;\n\n switch (work.kind) {\n case 'albums':\n return {\n '@type': 'MusicAlbum',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies MusicAlbum;\n case 'tracks':\n return {\n '@type': 'MusicRecording',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n } satisfies MusicRecording;\n case 'concerts':\n return {\n '@type': 'MusicEvent',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies MusicEvent;\n case 'events':\n return {\n '@type': 'Event',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies Event;\n case 'films':\n return {\n '@type': 'Movie',\n name,\n director: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Movie;\n case 'books':\n return {\n '@type': 'Book',\n name,\n author: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Book;\n }\n}\n\nfunction reviewNode(\n itemReviewed: JsonLdWork,\n rating: JsonLdRating | undefined,\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n base: string,\n orgId: string,\n editorId: string\n): Review {\n const a = input.article;\n return {\n '@type': 'Review',\n name: ctx.fullTitle,\n reviewBody: input.description,\n url: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a?.publishedTime,\n author: site.editor\n ? { '@id': editorId }\n : a?.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n itemReviewed: workNode(itemReviewed, base),\n reviewRating: rating\n ? {\n '@type': 'Rating',\n ratingValue: rating.value,\n bestRating: rating.best,\n worstRating: rating.worst,\n }\n : undefined,\n } satisfies Review;\n}\n\nfunction breadcrumbNode(\n items: { name: string; path: string }[],\n base: string\n): BreadcrumbList {\n return {\n '@type': 'BreadcrumbList',\n itemListElement: items.map((it, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: it.name,\n item: pageUrl(base, it.path),\n })),\n } satisfies BreadcrumbList;\n}\n\n/**\n * `JsonLdEvent` → 풀 `MusicEvent` 노드. venue(Place+GeoCoordinates) · offers(Offer) · organizer\n * 까지 채워 Google Event 리치 결과 자격을 충족한다. `@graph` 노드라 `@context` 는 붙이지 않는다.\n */\nfunction eventNode(event: JsonLdEvent, base: string): MusicEvent {\n return {\n '@type': 'MusicEvent',\n name: event.name,\n url: pageUrl(base, event.url),\n startDate: event.startDate,\n endDate: event.endDate,\n eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',\n eventStatus: 'https://schema.org/EventScheduled',\n location: {\n '@type': 'Place',\n name: event.venue.name,\n address: event.venue.address,\n geo: {\n '@type': 'GeoCoordinates',\n latitude: event.venue.latitude,\n longitude: event.venue.longitude,\n },\n },\n image: event.images?.length\n ? event.images.map((img) => absoluteUrl(base, img))\n : undefined,\n description: event.description,\n offers: event.offers?.length\n ? event.offers.map((offer) => ({\n '@type': 'Offer',\n availability: 'https://schema.org/InStock',\n price: offer.price,\n priceCurrency: offer.currency,\n url: absoluteUrl(base, offer.url),\n validFrom: offer.validFrom,\n name: offer.name,\n }))\n : undefined,\n organizer: {\n '@type': 'Organization',\n name: event.organizer ?? event.venue.name,\n },\n } satisfies MusicEvent;\n}\n\n/**\n * 페이지 디스크립터 → 단일 `@graph` JSON-LD `<script>`.\n * publisher/editor base 노드를 항상 prepend 해 페이지 단위로 `@id` 참조가 해소되게 한다.\n */\nfunction buildJsonLd(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext\n): SeoScript[] {\n const descriptors = input.jsonLd ?? [];\n if (descriptors.length === 0) return [];\n\n const base = site.baseUrl.replace(/\\/$/, '');\n const orgId = `${base}/#org`;\n const editorId = `${base}/#editor`;\n const websiteId = `${base}/#website`;\n\n const pageNodes: Thing[] = [];\n for (const d of descriptors) {\n switch (d.type) {\n case 'WebSite':\n pageNodes.push(websiteNode(site, ctx, websiteId, orgId));\n break;\n case 'Article': {\n const node = articleNode(input, site, ctx, orgId, editorId);\n if (node) pageNodes.push(node);\n break;\n }\n case 'Review':\n pageNodes.push(\n reviewNode(\n d.itemReviewed,\n d.rating,\n input,\n site,\n ctx,\n base,\n orgId,\n editorId\n )\n );\n break;\n case 'CreativeWork':\n pageNodes.push(workNode(d.item, base));\n break;\n case 'EventPage':\n pageNodes.push(eventNode(d.event, base));\n break;\n case 'Breadcrumb':\n pageNodes.push(breadcrumbNode(d.items, base));\n break;\n }\n }\n if (pageNodes.length === 0) return [];\n\n const baseNodes: Thing[] = [];\n const org = orgNode(site, orgId);\n if (org) baseNodes.push(org);\n const person = personNode(site, editorId);\n if (person) baseNodes.push(person);\n\n const graph = {\n '@context': SCHEMA_CONTEXT,\n '@graph': [...baseNodes, ...pageNodes],\n } satisfies Graph;\n\n return [{ type: 'application/ld+json', innerHTML: JSON.stringify(graph) }];\n}\n\nexport function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags {\n const lang: Lang = input.lang ?? 'ko';\n // 라우트 측에서 이미 브랜드 suffix(`… | COLDSURF 공연 정보`, `… — COLDSURF`, `Engineering — COLDSURF`)를\n // 직접 박은 경우, 여기서 다시 `| ${site.name}` 을 더하면 `... | COLDSURF 공연 정보 | COLDSURF` 처럼 두 번 노출된다.\n // title 안에 site.name 토큰이 이미 등장하면 그대로 둔다 — 라우트의 의도를 보존하면서 중복만 제거.\n const titleHasBrand = new RegExp(`\\\\b${escapeRegExp(site.name)}\\\\b`).test(\n input.title\n );\n const fullTitle = titleHasBrand\n ? input.title\n : `${input.title} | ${site.name}`;\n const url = pageUrl(site.baseUrl, input.path);\n const alternates = input.alternates ?? [];\n const locales = { ...DEFAULT_LOCALES, ...site.locales };\n\n const link: SeoLink[] = [{ rel: 'canonical', href: url }];\n if (alternates.length > 0) {\n link.push({ rel: 'alternate', hreflang: lang, href: url });\n for (const alt of alternates) {\n link.push({\n rel: 'alternate',\n hreflang: alt.lang,\n href: pageUrl(site.baseUrl, alt.path),\n });\n }\n // x-default points at the primary (Korean) language when present.\n const xDefault =\n alternates.find((a) => a.lang === 'ko')?.path ?? input.path;\n link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: pageUrl(site.baseUrl, xDefault),\n });\n }\n\n const meta: SeoMeta[] = [\n { name: 'description', content: input.description },\n { name: 'robots', content: 'index, follow' },\n { property: 'og:title', content: fullTitle },\n { property: 'og:description', content: input.description },\n { property: 'og:type', content: input.article ? 'article' : 'website' },\n { property: 'og:url', content: url },\n { property: 'og:site_name', content: site.name },\n { property: 'og:locale', content: locales[lang] },\n ];\n\n const keywords = [\n ...new Set([...(input.keywords ?? []), ...(site.keywords ?? [])]),\n ];\n if (keywords.length > 0) {\n meta.push({ name: 'keywords', content: keywords.join(', ') });\n }\n\n const image = input.image ?? site.defaultImage;\n // SeoImage.path 는 doc 상 \"절대 URL 또는 `/` 시작 path\" — venue 포스터처럼 외부 CDN 풀\n // URL 도 받기 위해 absoluteUrl(http 접두면 그대로) 로 통과시킨다.\n // 자체 도메인 자산만 `?v=` cache-bust — 외부 http URL 은 건드리지 않는다.\n const imageUrl = image\n ? withAssetVersion(\n absoluteUrl(site.baseUrl, image.path),\n site.assetVersion,\n image.path.startsWith('http')\n )\n : undefined;\n if (image && imageUrl) {\n meta.push({ property: 'og:image', content: imageUrl });\n if (image.type)\n meta.push({ property: 'og:image:type', content: image.type });\n if (image.width)\n meta.push({ property: 'og:image:width', content: String(image.width) });\n if (image.height)\n meta.push({ property: 'og:image:height', content: String(image.height) });\n if (image.alt) meta.push({ property: 'og:image:alt', content: image.alt });\n meta.push({ name: 'twitter:image', content: imageUrl });\n if (image.alt) meta.push({ name: 'twitter:image:alt', content: image.alt });\n }\n\n if (site.logoPath) {\n meta.push({\n property: 'og:logo',\n content: joinUrl(site.baseUrl, site.logoPath),\n });\n }\n\n // summary_large_image (1.91:1) 에서 잘리지 않을 만큼 가로로 긴 이미지일 때만 큰 카드로 노출.\n // 정사각형 이하 비율은 summary (작은 카드)로 떨어뜨려 안전하게 보여준다.\n const isWideImage =\n !!image &&\n !!image.width &&\n !!image.height &&\n image.width >= image.height * 1.5;\n const twitterCard = isWideImage ? 'summary_large_image' : 'summary';\n meta.push(\n { name: 'twitter:card', content: twitterCard },\n { name: 'twitter:title', content: fullTitle },\n { name: 'twitter:description', content: input.description }\n );\n\n for (const alt of alternates) {\n if (alt.lang !== lang) {\n meta.push({\n property: 'og:locale:alternate',\n content: locales[alt.lang],\n });\n }\n }\n\n if (input.article) {\n meta.push({\n property: 'article:published_time',\n content: input.article.publishedTime,\n });\n if (input.article.modifiedTime) {\n meta.push({\n property: 'article:modified_time',\n content: input.article.modifiedTime,\n });\n }\n if (input.article.author) {\n meta.push({ property: 'article:author', content: input.article.author });\n }\n if (input.article.section) {\n meta.push({\n property: 'article:section',\n content: input.article.section,\n });\n }\n for (const tag of input.article.tags ?? []) {\n meta.push({ property: 'article:tag', content: tag });\n }\n }\n\n // App Links (al:*) — 앱 아이덴티티는 site, 딥링크 URL 은 페이지에서. Twitter app 카드는 의도적으로\n // 쓰지 않는다(이미지 카드 우선) — al:* 는 카드 종류와 무관하게 네이티브 앱 오픈을 동작시킨다.\n if (site.appLinks?.ios) {\n meta.push({\n property: 'al:ios:app_store_id',\n content: site.appLinks.ios.appStoreId,\n });\n meta.push({\n property: 'al:ios:app_name',\n content: site.appLinks.ios.appName,\n });\n if (input.appLinks?.iosUrl) {\n meta.push({ property: 'al:ios:url', content: input.appLinks.iosUrl });\n }\n }\n if (site.appLinks?.android) {\n meta.push({\n property: 'al:android:package',\n content: site.appLinks.android.packageName,\n });\n if (site.appLinks.android.appName) {\n meta.push({\n property: 'al:android:app_name',\n content: site.appLinks.android.appName,\n });\n }\n if (input.appLinks?.androidUrl) {\n meta.push({\n property: 'al:android:url',\n content: input.appLinks.androidUrl,\n });\n }\n }\n\n const script = buildJsonLd(input, site, { url, fullTitle, imageUrl, lang });\n\n return { title: fullTitle, htmlLang: lang, meta, link, script };\n}\n\n/**\n * `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구\n * `NextMetadataGenerator` 의 \"생성자 1회 주입\" 사용감을 stateful 클래스 없이 순수 함수로 재현한다.\n *\n * const seo = createSeo(siteConfig)\n * seo.buildTags({ title, description, path, jsonLd: [...] })\n */\nexport function createSeo(site: SiteConfig) {\n return {\n buildTags: (input: PageSeoInput): SeoTags => buildSeoTags(input, site),\n };\n}\n","import { useCallback, useEffect, useRef } from 'react';\nimport {\n type PageSeoInput,\n type SeoLink,\n type SeoMeta,\n type SeoScript,\n type SiteConfig,\n buildSeoTags,\n} from '../metadata/core';\n\n// biome-ignore lint/suspicious/noExplicitAny: generic callback signature\nexport function useDebouncedCallback<T extends (...args: any[]) => void>(\n fn: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const fnRef = useRef(fn);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n fnRef.current = fn;\n });\n\n return useCallback(\n (...args: Parameters<T>) => {\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => fnRef.current(...args), delay);\n },\n [delay]\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SEO head adapter\n//\n// `buildSeoTags` (framework-agnostic engine, ./metadata) 의 `SeoTags` 출력을, head()/meta API 가\n// 평범한 태그 디스크립터를 받는 React 라우터(예: TanStack Router)의 `{ meta, links, scripts }` 모양으로\n// 매핑한다. React import 에 의존하지 않는 순수 변환 — 어떤 head-descriptor 라우터에도 쓸 수 있다.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type HeadMeta =\n | { title: string }\n | { charSet: string }\n | { name: string; content: string }\n | { property: string; content: string }\n | { httpEquiv: string; content: string };\n\nexport type HeadLink = {\n rel: string;\n href: string;\n hreflang?: string;\n type?: string;\n sizes?: string;\n};\n\nexport type HeadScript = {\n children?: string;\n src?: string;\n type?: string;\n async?: boolean;\n};\n\nexport type SeoHead = {\n meta: HeadMeta[];\n links: HeadLink[];\n scripts: HeadScript[];\n};\n\n/** `PageSeoInput` + `robots` override. 미지정 시 엔진 기본값 `'index, follow'`. */\nexport type SeoHeadInput = PageSeoInput & { robots?: string };\n\n/**\n * `SiteConfig` 를 한 번 바인딩해, 라우트에서 단일 declarative 입력으로 호출하는 `seoHead(input)` 를 만든다.\n *\n * // app 진입 1회\n * export const seoHead = createSeoHead({ ...SITE, baseUrl })\n *\n * // 라우트\n * head: ({ match }) => seoHead({ path: '/about', lang: match.context.lang, title, description })\n * head: () => seoHead({ path: '/resume', lang: 'ko', ...copy, robots: 'noindex, nofollow' })\n *\n * 반환값(`{ meta, links, scripts }`)을 라우터가 `<head>` 로 직렬화한다.\n */\nexport function createSeoHead(\n site: SiteConfig\n): (input: SeoHeadInput) => SeoHead {\n return (input) => {\n const { robots, ...page } = input;\n const tags = buildSeoTags(page, site);\n\n const meta: HeadMeta[] = [{ title: tags.title }];\n for (const m of tags.meta as SeoMeta[]) {\n if (m.name) {\n // robots override — noindex 라우트만 명시.\n const content = m.name === 'robots' && robots ? robots : m.content;\n meta.push({ name: m.name, content });\n } else if (m.property) {\n meta.push({ property: m.property, content: m.content });\n }\n // name 도 property 도 없는 SeoMeta 는 spec 위반 — skip.\n }\n\n const links: HeadLink[] = (tags.link as SeoLink[]).map((l) => ({\n rel: l.rel,\n href: l.href,\n ...(l.hreflang ? { hreflang: l.hreflang } : {}),\n }));\n\n const scripts: HeadScript[] = (tags.script as SeoScript[]).map((s) => ({\n type: s.type,\n children: s.innerHTML,\n }));\n\n return { meta, links, scripts };\n };\n}\n\n// app code 가 `seoHead` 입력을 조립할 때 필요한 metadata 타입 재노출 — `@coldsurf/shared-utils/react`\n// 한 곳에서 끝나게.\nexport type {\n ArticleMeta,\n JsonLd,\n JsonLdEvent,\n JsonLdEventOffer,\n JsonLdWork,\n Lang,\n PageSeoInput,\n SeoImage,\n SiteConfig,\n} from '../metadata/core';\n"],"mappings":"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"} |
+1
-1
@@ -8,3 +8,3 @@ { | ||
| }, | ||
| "version": "1.3.1", | ||
| "version": "1.4.0", | ||
| "license": "MIT", | ||
@@ -11,0 +11,0 @@ "bugs": { |
+2
-2
@@ -44,3 +44,3 @@ # @coldsurfers/shared-utils | ||
| - `decodeJwt` (`utils.jwt`) uses `jwt-decode` at runtime. | ||
| - `NextMetadataGenerator` (`utils.metadata`) uses `schema-dts` types for JSON-LD typing. | ||
| - `buildSeoTags` / `createSeo` (`metadata`) uses `schema-dts` types for JSON-LD typing. | ||
@@ -57,3 +57,3 @@ ## What to install by use case | ||
| ``` | ||
| - You use `NextMetadataGenerator` / `generateLdJson` with `schema-dts` types: | ||
| - You use `buildSeoTags` / `createSeo` / `createSeoHead` with `schema-dts` types: | ||
| ```bash | ||
@@ -60,0 +60,0 @@ pnpm add @coldsurfers/shared-utils zod schema-dts |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
192684
119.19%722
38.85%1
Infinity%