diff --git a/.jules/palette.md b/.jules/palette.md index 9a12c9de..020780f7 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -48,3 +48,7 @@ ## 2024-08-01 - 네이티브 브라우저 UI의 다크 모드 지원 강제 **학습:** CSS 미디어 쿼리(`@media (prefers-color-scheme: dark)`)를 통해 다크 모드를 지원하더라도, 브라우저의 네이티브 UI 요소(스크롤바, 기본 폼 컨트롤, 기본 백그라운드 등)는 테마 변경을 인식하지 못해 어두운 테마 환경에서 밝은 스크롤바가 표시되는 등 시각적 불일치를 초래합니다. **조치:** 항상 HTML 문서의 `` 영역에 `` 메타 태그를 명시적으로 추가하여 브라우저 수준에서 사용자의 시스템 테마(다크 모드 등)를 완전히 상속받아 일관성 있는 네이티브 UI를 렌더링하도록 보장하십시오. + +## 2024-05-25 - 디렉토리 목록 빈 폴더 시각화 향상 +**학습:** 디렉토리 목록에서 비어있는 디렉토리에 대해 일반 디렉토리와 동일한 닫힌 폴더 아이콘과 '디렉토리' 라벨을 제공하면, 사용자가 불필요하게 비어있는 폴더를 클릭하여 탐색하게 되는 UX 저하가 발생합니다. +**조치:** 디렉토리 트리를 렌더링할 때 하위 디렉토리가 비어있는지 확인(성능을 위해 `Files.newDirectoryStream` 사용)하고, 비어있는 경우 열린 폴더 아이콘(`📂`)과 명확한 라벨('빈 디렉토리')을 제공하여 사용자에게 즉각적인 시각적 피드백을 제공하십시오. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index b4558624..cf3aa49f 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -307,6 +307,9 @@ fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array padding: 0.5rem; opacity: 0.7; font-style: italic; + display: flex; + align-items: flex-start; + gap: 0.5rem; } """ @@ -360,9 +363,21 @@ ${cssContent} } catch (e: Exception) { } if (!isSymbolicLink) { + var isEmptyDir = false + if (isLinkedDirectory) { + try { + val stream = Files.newDirectoryStream(it.toPath()) + try { + isEmptyDir = !stream.iterator().hasNext() + } finally { + stream.close() + } + } catch (e: Exception) { + } + } val encodedHref = if (isLinkedDirectory) { "./${fileName.urlEncodePath()}/" } else { "./${fileName.urlEncodePath()}" } - val ariaLabel = "${fileName} ${if (isLinkedDirectory) { "디렉토리" } else { "파일" }}".escapeHtml() - val icon = if (isLinkedDirectory) { "📁" } else { "📄" } + val ariaLabel = "${fileName} ${if (isLinkedDirectory) { if (isEmptyDir) "빈 디렉토리" else "디렉토리" } else { "파일" }}".escapeHtml() + val icon = if (isLinkedDirectory) { if (isEmptyDir) "📂" else "📁" } else { "📄" } l.append("""
  • ${fileName.escapeHtml()}
  • """) l.append('\n') } @@ -370,7 +385,7 @@ ${cssContent} } if(l.isEmpty()){ - l.append("""
  • 이 디렉토리는 비어 있습니다.
  • """) + l.append("""
  • 이 디렉토리는 비어 있습니다.
  • """) l.append('\n') } diff --git a/src/test/kotlin/html4tree/MainTest.kt b/src/test/kotlin/html4tree/MainTest.kt index 13494714..b761e7bd 100644 --- a/src/test/kotlin/html4tree/MainTest.kt +++ b/src/test/kotlin/html4tree/MainTest.kt @@ -94,6 +94,7 @@ class MainTest { val htmlContent = indexFile.readText() assertTrue(htmlContent.contains("")) assertTrue(htmlContent.contains("이 디렉토리는 비어 있습니다.")) + assertTrue(htmlContent.contains("📂")) assertTrue(htmlContent.contains("role=\"list\"")) } @@ -298,6 +299,9 @@ class MainTest { fun testProcessDir() { val subdir = File(tempDir, "subdir") subdir.mkdir() + File(subdir, "child.txt").createNewFile() + val emptydir = File(tempDir, "emptydir") + emptydir.mkdir() File(tempDir, "file1.txt").createNewFile() File(tempDir, "test.ignore").createNewFile() File(tempDir, ".html4ignore").writeText("*.ignore") @@ -320,9 +324,13 @@ class MainTest { assertTrue(htmlContent.contains("title=\"file1.txt 파일\"")) assertTrue(htmlContent.contains("aria-label=\"subdir 디렉토리\"")) assertTrue(htmlContent.contains("title=\"subdir 디렉토리\"")) + assertTrue(htmlContent.contains("aria-label=\"emptydir 빈 디렉토리\"")) + assertTrue(htmlContent.contains("title=\"emptydir 빈 디렉토리\"")) assertTrue(htmlContent.contains("file1.txt")) assertTrue(htmlContent.contains("subdir/")) + assertTrue(htmlContent.contains("emptydir/")) assertTrue(htmlContent.contains("📁")) + assertTrue(htmlContent.contains("📂")) assertFalse(htmlContent.contains("test.ignore")) assertTrue(htmlContent.contains("Content-Security-Policy")) val hashMatch = Regex("""style-src '(sha256-[A-Za-z0-9+/=]+)'""").find(htmlContent)?.groupValues?.get(1)