@@ -7,13 +7,36 @@ public struct PostMetadata: Sendable {
77 public let tags : [ String ]
88 public let image : String ?
99 public let description : String ?
10-
11- public init ( title: String , date: Date , tags: [ String ] , image: String ? = nil , description: String ? = nil ) {
10+ /// 프로젝트 스크린 갤러리용 이미지 URL 배열 (캐러셀 표시, 플랫폼별 없을 때 fallback)
11+ public let screens : [ String ]
12+ /// 플랫폼별 스크린: screenshot/ios, screenshot/ipad, screenshot/macos 폴더 기준
13+ public let screensIos : [ String ]
14+ public let screensIpad : [ String ]
15+ public let screensMacos : [ String ]
16+ /// 프로젝트 지원 플랫폼 (iOS, iPad, macOS) - 목업 탭 표시용
17+ public let platforms : [ String ]
18+
19+ public init ( title: String , date: Date , tags: [ String ] , image: String ? = nil , description: String ? = nil , screens: [ String ] = [ ] , screensIos: [ String ] = [ ] , screensIpad: [ String ] = [ ] , screensMacos: [ String ] = [ ] , platforms: [ String ] = [ " iOS " ] ) {
1220 self . title = title
1321 self . date = date
1422 self . tags = tags
1523 self . image = image
1624 self . description = description
25+ self . screens = screens
26+ self . screensIos = screensIos
27+ self . screensIpad = screensIpad
28+ self . screensMacos = screensMacos
29+ self . platforms = platforms. isEmpty ? [ " iOS " ] : platforms
30+ }
31+
32+ /// 플랫폼 키(ios/ipad/mac)에 해당하는 스크린 목록. 없으면 screens 사용
33+ public func screens( forPlatformKey key: String ) -> [ String ] {
34+ switch key {
35+ case " ios " : return screensIos. isEmpty ? screens : screensIos
36+ case " ipad " : return screensIpad. isEmpty ? screens : screensIpad
37+ case " mac " : return screensMacos. isEmpty ? screens : screensMacos
38+ default : return screens
39+ }
1740 }
1841}
1942
@@ -32,7 +55,7 @@ public struct Post: Sendable {
3255
3356 public static func from( file: String , content: String ) throws -> Post {
3457 // 새로운 Markdown Parser 사용
35- var parser = MarkdownParser ( )
58+ let parser = MarkdownParser ( )
3659 let markdown = parser. parse ( content)
3760
3861 // slug 생성
@@ -46,8 +69,13 @@ public struct Post: Sendable {
4669 title: markdown. metadata [ " title " ] ?? " Untitled " ,
4770 date: parseDate ( from: markdown. metadata [ " date " ] ) ?? Date ( ) ,
4871 tags: parseTags ( from: markdown. metadata [ " tags " ] ) ,
49- image: parseImage ( from: markdown. metadata [ " image " ] , slug: slug) ,
50- description: markdown. metadata [ " description " ]
72+ image: parseImage ( from: markdown. metadata [ " image " ] ) ,
73+ description: markdown. metadata [ " description " ] ,
74+ screens: parseScreens ( from: markdown. metadata [ " screens " ] ) ,
75+ screensIos: parseScreens ( from: markdown. metadata [ " screens_ios " ] ) ,
76+ screensIpad: parseScreens ( from: markdown. metadata [ " screens_ipad " ] ) ,
77+ screensMacos: parseScreens ( from: markdown. metadata [ " screens_macos " ] ) ,
78+ platforms: parsePlatforms ( from: markdown. metadata [ " platforms " ] )
5179 )
5280
5381 return Post (
@@ -94,24 +122,38 @@ public struct Post: Sendable {
94122 return cleanedValue. components ( separatedBy: " , " ) . map { $0. trimmingCharacters ( in: . whitespaces) }
95123 }
96124
97- // 이미지 파싱 헬퍼 함수
98- private static func parseImage( from imageString: String ? , slug : String ) -> String ? {
125+ // 이미지 파싱: Contents 프론트매터에 적은 경로를 그대로 사용 (Public 기준 루트 상대 경로)
126+ private static func parseImage( from imageString: String ? ) -> String ? {
99127 guard let imageString = imageString else { return nil }
100128
101- let trimmedImage = imageString. trimmingCharacters ( in: . whitespacesAndNewlines)
129+ let trimmed = imageString. trimmingCharacters ( in: . whitespacesAndNewlines)
130+ guard !trimmed. isEmpty else { return nil }
102131
103- // 외부 URL인 경우 (https:// 또는 http://로 시작)
104- if trimmedImage . hasPrefix ( " https:// " ) || trimmedImage . hasPrefix ( " http:// " ) {
105- return trimmedImage
132+ // 외부 URL 또는 이미 / 로 시작하는 절대 경로 → 그대로 사용
133+ if trimmed . hasPrefix ( " https:// " ) || trimmed . hasPrefix ( " http:// " ) || trimmed . hasPrefix ( " / " ) {
134+ return trimmed
106135 }
107-
108- // 로컬 경로인 경우 /thumbnail/ 경로로 변환
109- if trimmedImage. hasPrefix ( " / " ) {
110- // 이미 절대 경로인 경우 그대로 사용
111- return trimmedImage
112- } else {
113- // 상대 경로인 경우 /thumbnail/ 경로로 변환
114- return " /thumbnail/ \( trimmedImage) "
136+ // 상대 경로 → Public 루트 기준으로 / 경로 (예: thumbnail/foo.svg → /thumbnail/foo.svg, app/daysquare/Icon.svg → /app/daysquare/Icon.svg)
137+ return " / " + trimmed
138+ }
139+
140+ /// screens: "url1, url2, url3" 형태 파싱, 각 경로는 parseImage 규칙으로 정규화
141+ private static func parseScreens( from screensString: String ? ) -> [ String ] {
142+ guard let s = screensString else { return [ ] }
143+ let trimmed = s. trimmingCharacters ( in: . whitespacesAndNewlines)
144+ guard !trimmed. isEmpty else { return [ ] }
145+ return trimmed. components ( separatedBy: " , " ) . compactMap { part in
146+ let p = part. trimmingCharacters ( in: . whitespaces)
147+ return parseImage ( from: p. isEmpty ? nil : p)
115148 }
116149 }
150+
151+ /// platforms: "iOS, iPad, macOS" 형태 파싱
152+ private static func parsePlatforms( from platformsString: String ? ) -> [ String ] {
153+ guard let s = platformsString else { return [ " iOS " ] }
154+ let trimmed = s. trimmingCharacters ( in: . whitespacesAndNewlines)
155+ guard !trimmed. isEmpty else { return [ " iOS " ] }
156+ let list = trimmed. components ( separatedBy: " , " ) . map { $0. trimmingCharacters ( in: . whitespaces) } . filter { !$0. isEmpty }
157+ return list. isEmpty ? [ " iOS " ] : list
158+ }
117159}
0 commit comments