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("