diff --git a/.jules/palette.md b/.jules/palette.md index 9a12c9de..32d8ad5f 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-08-05 - 빈 디렉토리와 내용이 있는 디렉토리 시각적/시맨틱 구분 +**학습:** 디렉토리 목록에서 비어있는 디렉토리와 그렇지 않은 디렉토리가 시각적으로나 시맨틱하게 동일하게 표시되면, 사용자는 빈 디렉토리 내부를 불필요하게 탐색하게 되어 불편함을 느낍니다. +**조치:** 디렉토리 탐색 시 해당 디렉토리가 비어있는지 미리 확인하여, 비어있을 경우 닫힌 폴더 아이콘 대신 열린 폴더 아이콘(📂)을 사용하고 `aria-label`과 `title` 속성에 '빈 디렉토리'라고 명시함으로써 불필요한 내비게이션을 방지하고 접근성을 개선하십시오. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index b4558624..b2410592 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -240,7 +240,7 @@ fun write_index_file(curr_dir: File, content: String) { } } -fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array? = null){ +fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array? = null, newDirectoryStream: (java.nio.file.Path) -> java.nio.file.DirectoryStream = { Files.newDirectoryStream(it) }){ val exclude: Set = excludeSet ?: process_ignore_file(curr_dir) @@ -360,9 +360,21 @@ ${cssContent} } catch (e: Exception) { } if (!isSymbolicLink) { + var isEmptyDir = false + if (isLinkedDirectory) { + try { + val stream = 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') } diff --git a/src/test/kotlin/html4tree/AttrExceptionTest.kt b/src/test/kotlin/html4tree/AttrExceptionTest.kt index 98973eb1..04461674 100644 --- a/src/test/kotlin/html4tree/AttrExceptionTest.kt +++ b/src/test/kotlin/html4tree/AttrExceptionTest.kt @@ -21,4 +21,20 @@ class AttrExceptionTest { tempDir.deleteRecursively() } } + + @Test + fun testExceptionInNewDirectoryStream() { + val tempDir = Files.createTempDirectory("stream_test").toFile() + try { + val subDir = File(tempDir, "subdir") + subDir.mkdir() + process_dir(tempDir, setOf(), arrayOf(subDir)) { _ -> + throw java.io.IOException("Mock exception") + } + val indexHtml = File(tempDir, "index.html") + assertTrue(indexHtml.exists()) + } finally { + tempDir.deleteRecursively() + } + } } diff --git a/src/test/kotlin/html4tree/MainTest.kt b/src/test/kotlin/html4tree/MainTest.kt index 13494714..a548cafa 100644 --- a/src/test/kotlin/html4tree/MainTest.kt +++ b/src/test/kotlin/html4tree/MainTest.kt @@ -86,6 +86,18 @@ class MainTest { } } + @Test + fun testEmptyDirectoryIcon() { + val emptyDir = File(tempDir, "empty_subdir") + emptyDir.mkdir() + go(tempDir.absolutePath, 0) + val indexFile = File(tempDir, "index.html") + val htmlContent = indexFile.readText() + assertTrue(htmlContent.contains("📂")) + assertTrue(htmlContent.contains("aria-label=\"empty_subdir 빈 디렉토리\"")) + assertTrue(htmlContent.contains("title=\"empty_subdir 빈 디렉토리\"")) + } + @Test fun testGoEmptyDir() { go(tempDir.absolutePath, -1) @@ -298,6 +310,7 @@ class MainTest { fun testProcessDir() { val subdir = File(tempDir, "subdir") subdir.mkdir() + File(subdir, "some_file.txt").createNewFile() File(tempDir, "file1.txt").createNewFile() File(tempDir, "test.ignore").createNewFile() File(tempDir, ".html4ignore").writeText("*.ignore")