diff --git a/.jules/palette.md b/.jules/palette.md index 7b223901..9a12c9de 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -48,7 +48,3 @@ ## 2024-08-01 - 네이티브 브라우저 UI의 다크 모드 지원 강제 **학습:** CSS 미디어 쿼리(`@media (prefers-color-scheme: dark)`)를 통해 다크 모드를 지원하더라도, 브라우저의 네이티브 UI 요소(스크롤바, 기본 폼 컨트롤, 기본 백그라운드 등)는 테마 변경을 인식하지 못해 어두운 테마 환경에서 밝은 스크롤바가 표시되는 등 시각적 불일치를 초래합니다. **조치:** 항상 HTML 문서의 `` 영역에 `` 메타 태그를 명시적으로 추가하여 브라우저 수준에서 사용자의 시스템 테마(다크 모드 등)를 완전히 상속받아 일관성 있는 네이티브 UI를 렌더링하도록 보장하십시오. - -## 2024-07-13 - 빈 디렉토리 상태의 접근성(Accessibility) 개선 -**Learning:** 정적 파일 서버의 빈 디렉토리 상태는 스크린 리더 사용자에게 컨텐츠 누락으로 오해받을 수 있으며, 시각적으로도 일반 리스트 아이템과 정렬이 맞지 않는 문제가 있었습니다. -**Action:** 빈 상태를 나타내는 요소에 `role="status"`를 추가하여 스크린 리더가 명확하게 인지할 수 있도록 하고, 아이콘과 flex 레이아웃을 통해 다른 리스트 아이템과 일관된 시각적 흐름을 제공하도록 합니다. diff --git a/build.gradle b/build.gradle index bc408aad..33cc784c 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - testCompile 'junit:junit:4.13.2' + testCompile 'junit:junit:4.11' testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" compile "com.github.ajalt:clikt:2.7.1" } diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 29eef0c4..4dc5e492 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -94,7 +94,7 @@ internal fun crawl_directories( dirFiles?.forEach { // ⚡ Bolt Performance Optimization: Short-circuit OS stat calls (isDirectory/isSymbolicLink) // by checking cheap in-memory string exclusion rules first - if(!it.name.startsWith(".") && it.name !in exclude && isDirectory(it) && !isSymbolicLink(it)) { + if(!it.name.startsWithHiddenPrefix() && it.name !in exclude && isDirectory(it) && !isSymbolicLink(it)) { val childEntry = LinkedListEntry(it, currentLevel+1, readIdentity(it).key) ll.push(childEntry) } @@ -221,7 +221,7 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S // 보안 향상: .env, .git 등 민감한 정보가 포함될 수 있는 숨김 파일(.으로 시작하는 모든 항목)을 기본적으로 노출하지 않도록 제외 (정보 노출 방지) (dirFilesNames ?: curr_dir.list())?.forEach { - if (it.startsWith(".")) { + if (it.startsWithHiddenPrefix()) { files_to_exclude.add(it) } } @@ -304,9 +304,6 @@ fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array } } .empty-dir { - display: flex; - align-items: flex-start; - gap: 0.5rem; padding: 0.5rem; opacity: 0.7; font-style: italic; @@ -330,6 +327,8 @@ ${cssContent} + + ${curr_dir.getName().escapeHtml()} ${css} @@ -351,7 +350,7 @@ ${cssContent} val fileName = it.getName() // ⚡ Bolt Performance Optimization: Short-circuit string match before expensive OS filesystem calls // 🛡️ Sentinel: Ignore hidden files/directories to prevent sensitive data exposure - if (!fileName.startsWith(".") && fileName !in exclude) { + if (!fileName.startsWithHiddenPrefix() && fileName !in exclude) { var isLinkedDirectory = false var isSymbolicLink = false try { @@ -373,7 +372,7 @@ ${cssContent} } if(l.isEmpty()){ - l.append("""
  • 이 디렉토리는 비어 있습니다.
  • """) + l.append("""
  • 이 디렉토리는 비어 있습니다.
  • """) l.append('\n') } diff --git a/src/main/kotlin/html4tree/util.kt b/src/main/kotlin/html4tree/util.kt index fa25294c..90cdba06 100644 --- a/src/main/kotlin/html4tree/util.kt +++ b/src/main/kotlin/html4tree/util.kt @@ -6,6 +6,12 @@ data class Entry (val data: File, val level: Int, var next: Entry?, val fileKey: data class LinkedListEntry(val file: File, val level: Int, var fileKey: Any? = null) +fun String.startsWithHiddenPrefix(): Boolean { + if (this.isEmpty()) return false + val c = this[0] + return c == '.' || c == '。' || c == '﹒' || c == '.' || c == '。' || c == '․' || c == '‥' +} + class LinkedList { var first: Entry? = null var last: Entry? = null diff --git a/src/test/kotlin/html4tree/MainTest.kt b/src/test/kotlin/html4tree/MainTest.kt index 83739c9c..e5689c3d 100644 --- a/src/test/kotlin/html4tree/MainTest.kt +++ b/src/test/kotlin/html4tree/MainTest.kt @@ -93,8 +93,8 @@ class MainTest { assertTrue(indexFile.exists()) val htmlContent = indexFile.readText() assertTrue(htmlContent.contains("")) + assertTrue(htmlContent.contains("")) assertTrue(htmlContent.contains("이 디렉토리는 비어 있습니다.")) - assertTrue(htmlContent.contains("role=\"status\"")) assertTrue(htmlContent.contains("role=\"list\"")) } @@ -118,6 +118,12 @@ class MainTest { val normalFile = File(tempDir, "normal_file.txt") normalFile.createNewFile() + val unicodeHiddenFile = File(tempDir, "。unicode_hidden.txt") + unicodeHiddenFile.createNewFile() + + val unicodeHiddenDir = File(tempDir, ".unicode_hidden_dir") + unicodeHiddenDir.mkdir() + go(tempDir.absolutePath, -1) val indexFile = File(tempDir, "index.html") @@ -127,9 +133,12 @@ class MainTest { assertTrue(htmlContent.contains("normal_file.txt"), "normal_file.txt should be listed") assertFalse(htmlContent.contains(".hidden_file.txt"), ".hidden_file.txt should not be listed") assertFalse(htmlContent.contains(".hidden_dir"), ".hidden_dir should not be listed") + assertFalse(htmlContent.contains("。unicode_hidden.txt"), "。unicode_hidden.txt should not be listed") + assertFalse(htmlContent.contains(".unicode_hidden_dir"), ".unicode_hidden_dir should not be listed") val hiddenDirIndexFile = File(hiddenDir, "index.html") assertFalse(hiddenDirIndexFile.exists(), "Hidden directories should not be traversed to generate index.html") + assertFalse(File(unicodeHiddenDir, "index.html").exists(), "Unicode hidden directories should not be traversed to generate index.html") } @Test @@ -310,6 +319,7 @@ class MainTest { val htmlContent = indexFile.readText() assertTrue(htmlContent.contains("")) assertTrue(htmlContent.contains("")) + assertTrue(htmlContent.contains("")) assertTrue(htmlContent.contains("