ndmhung6 commited on
Commit
49797f0
·
1 Parent(s): 48872eb

feat: make major sidebar navigation sections collapsible and persist their state in localStorage

Browse files
Files changed (2) hide show
  1. static/theme.css +25 -2
  2. templates/layout.html +184 -129
static/theme.css CHANGED
@@ -181,13 +181,36 @@ body {
181
  transform: rotate(-90deg);
182
  }
183
 
184
- .nav-section-label {
185
  font-size: var(--font-size-xs);
186
  text-transform: uppercase;
187
  color: var(--color-label-muted);
188
  font-weight: var(--font-weight-semi);
189
  letter-spacing: 0.08em;
190
- padding: var(--spacing-md) 0.9rem 0.4rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  }
192
 
193
  .nav-link.disabled {
 
181
  transform: rotate(-90deg);
182
  }
183
 
184
+ .nav-section-header {
185
  font-size: var(--font-size-xs);
186
  text-transform: uppercase;
187
  color: var(--color-label-muted);
188
  font-weight: var(--font-weight-semi);
189
  letter-spacing: 0.08em;
190
+ padding: var(--spacing-sm) 0.9rem;
191
+ display: flex;
192
+ justify-content: space-between;
193
+ align-items: center;
194
+ cursor: pointer;
195
+ border-radius: var(--radius-sm);
196
+ transition: background 0.15s, color 0.15s;
197
+ margin-top: var(--spacing-md);
198
+ margin-bottom: 0.2rem;
199
+ }
200
+
201
+ .nav-section-header:hover {
202
+ background: var(--color-bg-input);
203
+ color: var(--color-text-heading);
204
+ }
205
+
206
+ .nav-section-header .nav-chevron {
207
+ font-size: 0.65rem;
208
+ color: var(--color-text-disabled);
209
+ transition: transform 0.2s ease;
210
+ }
211
+
212
+ .nav-section-header.collapsed .nav-chevron {
213
+ transform: rotate(-90deg);
214
  }
215
 
216
  .nav-link.disabled {
templates/layout.html CHANGED
@@ -26,154 +26,174 @@
26
  </a>
27
 
28
  <!-- 🎙️ ÂM THANH & LỒNG TIẾNG -->
29
- <div class="nav-section-label">ÂM THANH & LỒNG TIẾNG</div>
30
- <a class="nav-link nav-link-collapsible {% if request.path.startswith('/tts') %}active{% endif %}"
31
- href="/tts/vieneu"
32
- data-bs-toggle="collapse"
33
- data-bs-target="#tts-sub"
34
- aria-expanded="{{ 'true' if request.path.startswith('/tts') else 'false' }}"
35
- onclick="return handleNavCollapse(event, this)">
36
- <span><i class="fas fa-microphone-alt"></i> Text to Speech</span>
37
- <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/tts') %}collapsed{% endif %}"></i>
38
- </a>
39
- <div class="collapse nav-sub {% if request.path.startswith('/tts') %}show{% endif %}" id="tts-sub">
40
- <a class="nav-link nav-link-sub {% if request.path == '/tts/vieneu' %}active{% endif %}" href="/tts/vieneu">
41
- <i class="fas fa-dragon"></i> VieNeu-TTS
42
  </a>
43
- <a class="nav-link nav-link-sub {% if request.path == '/tts/gtts' %}active{% endif %}" href="/tts/gtts">
44
- <i class="fab fa-google"></i> Google TTS
 
 
 
 
 
 
 
 
 
 
 
45
  </a>
46
- <a class="nav-link nav-link-sub {% if request.path == '/tts/edge' %}active{% endif %}" href="/tts/edge">
47
- <i class="fab fa-microsoft"></i> Edge TTS
48
  </a>
49
  </div>
50
- <a class="nav-link {% if request.path.startswith('/audio-story-studio') %}active{% endif %}" href="/audio-story-studio">
51
- <i class="fas fa-podcast"></i> AI Audio Story Studio
52
- </a>
53
- <a class="nav-link {% if request.path.startswith('/podcast-studio') %}active{% endif %}" href="/podcast-studio">
54
- <i class="fas fa-microphone"></i> AI Podcast Studio
55
- </a>
56
 
57
  <!-- 🎬 HÌNH ẢNH & VIDEO AI -->
58
- <div class="nav-section-label">HÌNH ẢNH & VIDEO AI</div>
59
- <a class="nav-link nav-link-collapsible {% if request.path.startswith('/image-studio') %}active{% endif %}"
60
- href="/image-studio/remove-bg"
61
- data-bs-toggle="collapse"
62
- data-bs-target="#image-sub"
63
- aria-expanded="{{ 'true' if request.path.startswith('/image-studio') else 'false' }}"
64
- onclick="return handleNavCollapse(event, this)">
65
- <span><i class="fas fa-image"></i> Image Studio</span>
66
- <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/image-studio') %}collapsed{% endif %}"></i>
67
- </a>
68
- <div class="collapse nav-sub {% if request.path.startswith('/image-studio') %}show{% endif %}" id="image-sub">
69
- <a class="nav-link nav-link-sub {% if request.path == '/image-studio/remove-bg' %}active{% endif %}" href="/image-studio/remove-bg">
70
- <i class="fas fa-cut"></i> Xóa nền
71
- </a>
72
- <a class="nav-link nav-link-sub {% if request.path == '/image-studio/upscale' %}active{% endif %}" href="/image-studio/upscale">
73
- <i class="fas fa-expand-arrows-alt"></i> Nâng cấp ảnh
74
- </a>
75
  </div>
76
- <a class="nav-link {% if request.path.startswith('/comic-creator') %}active{% endif %}" href="/comic-creator">
77
- <i class="fas fa-book-open"></i> AI Comic Creator
78
- </a>
79
- <a class="nav-link {% if request.path.startswith('/video-ai') %}active{% endif %}" href="/video-ai">
80
- <i class="fas fa-video"></i> Video AI
81
- </a>
82
- <a class="nav-link {% if request.path.startswith('/shorts-generator') %}active{% endif %}" href="/shorts-generator">
83
- <i class="fas fa-mobile-alt"></i> AI Shorts Generator
84
- </a>
85
-
86
- <!-- ✍️ TRỢ LÝ NỘI DUNG -->
87
- <div class="nav-section-label">TRỢ LÝ NỘI DUNG</div>
88
- <a class="nav-link nav-link-collapsible {% if request.path.startswith('/prompt-generator') %}active{% endif %}"
89
- href="/prompt-generator/code"
90
- data-bs-toggle="collapse"
91
- data-bs-target="#prompt-sub"
92
- aria-expanded="{{ 'true' if request.path.startswith('/prompt-generator') else 'false' }}"
93
- onclick="return handleNavCollapse(event, this)">
94
- <span><i class="fas fa-magic"></i> AI Prompt Studio</span>
95
- <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/prompt-generator') %}collapsed{% endif %}"></i>
96
- </a>
97
- <div class="collapse nav-sub {% if request.path.startswith('/prompt-generator') %}show{% endif %}" id="prompt-sub">
98
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/code' %}active{% endif %}" href="/prompt-generator/code">
99
- <i class="fas fa-code"></i> Lập trình (Code)
100
- </a>
101
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/video' %}active{% endif %}" href="/prompt-generator/video">
102
- <i class="fas fa-video"></i> Ảnh / Video AI
103
- </a>
104
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/music' %}active{% endif %}" href="/prompt-generator/music">
105
- <i class="fas fa-music"></i> Nhạc (Suno/Udio)
106
- </a>
107
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/story' %}active{% endif %}" href="/prompt-generator/story">
108
- <i class="fas fa-book"></i> Kịch bản truyện
109
  </a>
110
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/video-script' %}active{% endif %}" href="/prompt-generator/video-script">
111
- <i class="fas fa-film"></i> Kịch bản Video
 
 
 
 
 
 
 
 
112
  </a>
113
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/audio-story' %}active{% endif %}" href="/prompt-generator/audio-story">
114
- <i class="fas fa-headphones"></i> Truyện Audio
115
  </a>
116
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/marketing' %}active{% endif %}" href="/prompt-generator/marketing">
117
- <i class="fas fa-ad"></i> Marketing / Content
118
  </a>
119
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/education' %}active{% endif %}" href="/prompt-generator/education">
120
- <i class="fas fa-graduation-cap"></i> Học tập / Ngoại ngữ
121
- </a>
122
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/data' %}active{% endif %}" href="/prompt-generator/data">
123
- <i class="fas fa-chart-line"></i> Phân tích dữ liệu
124
- </a>
125
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/office' %}active{% endif %}" href="/prompt-generator/office">
126
- <i class="fas fa-briefcase"></i> Trợ lý văn phòng
127
- </a>
128
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/brainstorm' %}active{% endif %}" href="/prompt-generator/brainstorm">
129
- <i class="fas fa-lightbulb"></i> Lên ý tưởng
 
 
 
 
 
130
  </a>
131
- <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/enhancer' %}active{% endif %}" href="/prompt-generator/enhancer">
132
- <i class="fas fa-wand-magic-sparkles"></i> Tối ưu Prompt thô
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  </a>
134
  </div>
135
- <a class="nav-link {% if request.path.startswith('/omnichannel-writer') %}active{% endif %}" href="/omnichannel-writer">
136
- <i class="fas fa-paper-plane"></i> AI Omnichannel Writer
137
- </a>
138
 
139
  <!-- 💼 DỊCH THUẬT & TÀI LIỆU -->
140
- <div class="nav-section-label">DỊCH THUẬT & TÀI LIỆU</div>
141
- <a class="nav-link nav-link-collapsible {% if request.path.startswith('/translator') %}active{% endif %}"
142
- href="/translator/text"
143
- data-bs-toggle="collapse"
144
- data-bs-target="#translator-sub"
145
- aria-expanded="{{ 'true' if request.path.startswith('/translator') else 'false' }}"
146
- onclick="return handleNavCollapse(event, this)">
147
- <span><i class="fas fa-language"></i> AI Translator</span>
148
- <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/translator') %}collapsed{% endif %}"></i>
149
- </a>
150
- <div class="collapse nav-sub {% if request.path.startswith('/translator') %}show{% endif %}" id="translator-sub">
151
- <a class="nav-link nav-link-sub {% if request.path == '/translator/text' %}active{% endif %}" href="/translator/text">
152
- <i class="fas fa-font"></i> Văn bản
153
- </a>
154
- <a class="nav-link nav-link-sub {% if request.path == '/translator/file' %}active{% endif %}" href="/translator/file">
155
- <i class="fas fa-file-alt"></i> Tài liệu
156
- </a>
157
- <a class="nav-link nav-link-sub {% if request.path == '/translator/image' %}active{% endif %}" href="/translator/image">
158
- <i class="fas fa-image"></i> Dịch ảnh
159
- </a>
160
  </div>
161
- <a class="nav-link nav-link-collapsible {% if request.path.startswith('/pdf') %}active{% endif %}"
162
- href="/pdf/summarize"
163
- data-bs-toggle="collapse"
164
- data-bs-target="#pdf-sub"
165
- aria-expanded="{{ 'true' if request.path.startswith('/pdf') else 'false' }}"
166
- onclick="return handleNavCollapse(event, this)">
167
- <span><i class="fas fa-file-pdf"></i> Smart PDF</span>
168
- <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/pdf') %}collapsed{% endif %}"></i>
169
- </a>
170
- <div class="collapse nav-sub {% if request.path.startswith('/pdf') %}show{% endif %}" id="pdf-sub">
171
- <a class="nav-link nav-link-sub {% if request.path == '/pdf/summarize' %}active{% endif %}" href="/pdf/summarize">
172
- <i class="fas fa-align-left"></i> Tóm tắt
173
  </a>
174
- <a class="nav-link nav-link-sub {% if request.path == '/pdf/extract' %}active{% endif %}" href="/pdf/extract">
175
- <i class="fas fa-table"></i> Trích xuất dữ liệu
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  </a>
 
 
 
 
 
 
 
 
177
  </div>
178
  </nav>
179
  </div>
@@ -237,15 +257,50 @@
237
  }
238
  });
239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(el => {
241
  const targetId = el.getAttribute('data-bs-target');
242
  const target = document.querySelector(targetId);
243
  if (!target) return;
 
244
  target.addEventListener('show.bs.collapse', () => {
 
 
245
  el.querySelector('.nav-chevron')?.classList.remove('collapsed');
 
 
 
246
  });
247
  target.addEventListener('hide.bs.collapse', () => {
 
 
248
  el.querySelector('.nav-chevron')?.classList.add('collapsed');
 
 
 
249
  });
250
  });
251
  </script>
 
26
  </a>
27
 
28
  <!-- 🎙️ ÂM THANH & LỒNG TIẾNG -->
29
+ <div class="nav-section-header" data-bs-toggle="collapse" data-bs-target="#audio-group" aria-expanded="true">
30
+ <span>ÂM THANH & LỒNG TIẾNG</span>
31
+ <i class="fas fa-chevron-down nav-chevron"></i>
32
+ </div>
33
+ <div class="collapse show nav-sub-group" id="audio-group">
34
+ <a class="nav-link nav-link-collapsible {% if request.path.startswith('/tts') %}active{% endif %}"
35
+ href="/tts/vieneu"
36
+ data-bs-toggle="collapse"
37
+ data-bs-target="#tts-sub"
38
+ aria-expanded="{{ 'true' if request.path.startswith('/tts') else 'false' }}"
39
+ onclick="return handleNavCollapse(event, this)">
40
+ <span><i class="fas fa-microphone-alt"></i> Text to Speech</span>
41
+ <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/tts') %}collapsed{% endif %}"></i>
42
  </a>
43
+ <div class="collapse nav-sub {% if request.path.startswith('/tts') %}show{% endif %}" id="tts-sub">
44
+ <a class="nav-link nav-link-sub {% if request.path == '/tts/vieneu' %}active{% endif %}" href="/tts/vieneu">
45
+ <i class="fas fa-dragon"></i> VieNeu-TTS
46
+ </a>
47
+ <a class="nav-link nav-link-sub {% if request.path == '/tts/gtts' %}active{% endif %}" href="/tts/gtts">
48
+ <i class="fab fa-google"></i> Google TTS
49
+ </a>
50
+ <a class="nav-link nav-link-sub {% if request.path == '/tts/edge' %}active{% endif %}" href="/tts/edge">
51
+ <i class="fab fa-microsoft"></i> Edge TTS
52
+ </a>
53
+ </div>
54
+ <a class="nav-link {% if request.path.startswith('/audio-story-studio') %}active{% endif %}" href="/audio-story-studio">
55
+ <i class="fas fa-podcast"></i> AI Audio Story Studio
56
  </a>
57
+ <a class="nav-link {% if request.path.startswith('/podcast-studio') %}active{% endif %}" href="/podcast-studio">
58
+ <i class="fas fa-microphone"></i> AI Podcast Studio
59
  </a>
60
  </div>
 
 
 
 
 
 
61
 
62
  <!-- 🎬 HÌNH ẢNH & VIDEO AI -->
63
+ <div class="nav-section-header" data-bs-toggle="collapse" data-bs-target="#video-group" aria-expanded="true">
64
+ <span>HÌNH ẢNH & VIDEO AI</span>
65
+ <i class="fas fa-chevron-down nav-chevron"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  </div>
67
+ <div class="collapse show nav-sub-group" id="video-group">
68
+ <a class="nav-link nav-link-collapsible {% if request.path.startswith('/image-studio') %}active{% endif %}"
69
+ href="/image-studio/remove-bg"
70
+ data-bs-toggle="collapse"
71
+ data-bs-target="#image-sub"
72
+ aria-expanded="{{ 'true' if request.path.startswith('/image-studio') else 'false' }}"
73
+ onclick="return handleNavCollapse(event, this)">
74
+ <span><i class="fas fa-image"></i> Image Studio</span>
75
+ <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/image-studio') %}collapsed{% endif %}"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  </a>
77
+ <div class="collapse nav-sub {% if request.path.startswith('/image-studio') %}show{% endif %}" id="image-sub">
78
+ <a class="nav-link nav-link-sub {% if request.path == '/image-studio/remove-bg' %}active{% endif %}" href="/image-studio/remove-bg">
79
+ <i class="fas fa-cut"></i> Xóa nền
80
+ </a>
81
+ <a class="nav-link nav-link-sub {% if request.path == '/image-studio/upscale' %}active{% endif %}" href="/image-studio/upscale">
82
+ <i class="fas fa-expand-arrows-alt"></i> Nâng cấp ảnh
83
+ </a>
84
+ </div>
85
+ <a class="nav-link {% if request.path.startswith('/comic-creator') %}active{% endif %}" href="/comic-creator">
86
+ <i class="fas fa-book-open"></i> AI Comic Creator
87
  </a>
88
+ <a class="nav-link {% if request.path.startswith('/video-ai') %}active{% endif %}" href="/video-ai">
89
+ <i class="fas fa-video"></i> Video AI
90
  </a>
91
+ <a class="nav-link {% if request.path.startswith('/shorts-generator') %}active{% endif %}" href="/shorts-generator">
92
+ <i class="fas fa-mobile-alt"></i> AI Shorts Generator
93
  </a>
94
+ </div>
95
+
96
+ <!-- ✍️ TRỢ LÝ NỘI DUNG -->
97
+ <div class="nav-section-header" data-bs-toggle="collapse" data-bs-target="#content-group" aria-expanded="true">
98
+ <span>TRỢ LÝ NỘI DUNG</span>
99
+ <i class="fas fa-chevron-down nav-chevron"></i>
100
+ </div>
101
+ <div class="collapse show nav-sub-group" id="content-group">
102
+ <a class="nav-link nav-link-collapsible {% if request.path.startswith('/prompt-generator') %}active{% endif %}"
103
+ href="/prompt-generator/code"
104
+ data-bs-toggle="collapse"
105
+ data-bs-target="#prompt-sub"
106
+ aria-expanded="{{ 'true' if request.path.startswith('/prompt-generator') else 'false' }}"
107
+ onclick="return handleNavCollapse(event, this)">
108
+ <span><i class="fas fa-magic"></i> AI Prompt Studio</span>
109
+ <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/prompt-generator') %}collapsed{% endif %}"></i>
110
  </a>
111
+ <div class="collapse nav-sub {% if request.path.startswith('/prompt-generator') %}show{% endif %}" id="prompt-sub">
112
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/code' %}active{% endif %}" href="/prompt-generator/code">
113
+ <i class="fas fa-code"></i> Lập trình (Code)
114
+ </a>
115
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/video' %}active{% endif %}" href="/prompt-generator/video">
116
+ <i class="fas fa-video"></i> Ảnh / Video AI
117
+ </a>
118
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/music' %}active{% endif %}" href="/prompt-generator/music">
119
+ <i class="fas fa-music"></i> Nhạc (Suno/Udio)
120
+ </a>
121
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/story' %}active{% endif %}" href="/prompt-generator/story">
122
+ <i class="fas fa-book"></i> Kịch bản truyện
123
+ </a>
124
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/video-script' %}active{% endif %}" href="/prompt-generator/video-script">
125
+ <i class="fas fa-film"></i> Kịch bản Video
126
+ </a>
127
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/audio-story' %}active{% endif %}" href="/prompt-generator/audio-story">
128
+ <i class="fas fa-headphones"></i> Truyện Audio
129
+ </a>
130
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/marketing' %}active{% endif %}" href="/prompt-generator/marketing">
131
+ <i class="fas fa-ad"></i> Marketing / Content
132
+ </a>
133
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/education' %}active{% endif %}" href="/prompt-generator/education">
134
+ <i class="fas fa-graduation-cap"></i> Học tập / Ngoại ngữ
135
+ </a>
136
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/data' %}active{% endif %}" href="/prompt-generator/data">
137
+ <i class="fas fa-chart-line"></i> Phân tích dữ liệu
138
+ </a>
139
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/office' %}active{% endif %}" href="/prompt-generator/office">
140
+ <i class="fas fa-briefcase"></i> Trợ lý văn phòng
141
+ </a>
142
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/brainstorm' %}active{% endif %}" href="/prompt-generator/brainstorm">
143
+ <i class="fas fa-lightbulb"></i> Lên ý tưởng
144
+ </a>
145
+ <a class="nav-link nav-link-sub {% if request.path == '/prompt-generator/enhancer' %}active{% endif %}" href="/prompt-generator/enhancer">
146
+ <i class="fas fa-wand-magic-sparkles"></i> Tối ưu Prompt thô
147
+ </a>
148
+ </div>
149
+ <a class="nav-link {% if request.path.startswith('/omnichannel-writer') %}active{% endif %}" href="/omnichannel-writer">
150
+ <i class="fas fa-paper-plane"></i> AI Omnichannel Writer
151
  </a>
152
  </div>
 
 
 
153
 
154
  <!-- 💼 DỊCH THUẬT & TÀI LIỆU -->
155
+ <div class="nav-section-header" data-bs-toggle="collapse" data-bs-target="#docs-group" aria-expanded="true">
156
+ <span>DỊCH THUẬT & TÀI LIỆU</span>
157
+ <i class="fas fa-chevron-down nav-chevron"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  </div>
159
+ <div class="collapse show nav-sub-group" id="docs-group">
160
+ <a class="nav-link nav-link-collapsible {% if request.path.startswith('/translator') %}active{% endif %}"
161
+ href="/translator/text"
162
+ data-bs-toggle="collapse"
163
+ data-bs-target="#translator-sub"
164
+ aria-expanded="{{ 'true' if request.path.startswith('/translator') else 'false' }}"
165
+ onclick="return handleNavCollapse(event, this)">
166
+ <span><i class="fas fa-language"></i> AI Translator</span>
167
+ <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/translator') %}collapsed{% endif %}"></i>
 
 
 
168
  </a>
169
+ <div class="collapse nav-sub {% if request.path.startswith('/translator') %}show{% endif %}" id="translator-sub">
170
+ <a class="nav-link nav-link-sub {% if request.path == '/translator/text' %}active{% endif %}" href="/translator/text">
171
+ <i class="fas fa-font"></i> Văn bản
172
+ </a>
173
+ <a class="nav-link nav-link-sub {% if request.path == '/translator/file' %}active{% endif %}" href="/translator/file">
174
+ <i class="fas fa-file-alt"></i> Tài liệu
175
+ </a>
176
+ <a class="nav-link nav-link-sub {% if request.path == '/translator/image' %}active{% endif %}" href="/translator/image">
177
+ <i class="fas fa-image"></i> Dịch ảnh
178
+ </a>
179
+ </div>
180
+ <a class="nav-link nav-link-collapsible {% if request.path.startswith('/pdf') %}active{% endif %}"
181
+ href="/pdf/summarize"
182
+ data-bs-toggle="collapse"
183
+ data-bs-target="#pdf-sub"
184
+ aria-expanded="{{ 'true' if request.path.startswith('/pdf') else 'false' }}"
185
+ onclick="return handleNavCollapse(event, this)">
186
+ <span><i class="fas fa-file-pdf"></i> Smart PDF</span>
187
+ <i class="fas fa-chevron-down nav-chevron {% if not request.path.startswith('/pdf') %}collapsed{% endif %}"></i>
188
  </a>
189
+ <div class="collapse nav-sub {% if request.path.startswith('/pdf') %}show{% endif %}" id="pdf-sub">
190
+ <a class="nav-link nav-link-sub {% if request.path == '/pdf/summarize' %}active{% endif %}" href="/pdf/summarize">
191
+ <i class="fas fa-align-left"></i> Tóm tắt
192
+ </a>
193
+ <a class="nav-link nav-link-sub {% if request.path == '/pdf/extract' %}active{% endif %}" href="/pdf/extract">
194
+ <i class="fas fa-table"></i> Trích xuất dữ liệu
195
+ </a>
196
+ </div>
197
  </div>
198
  </nav>
199
  </div>
 
257
  }
258
  });
259
 
260
+ // Restore collapsible states from localStorage on load
261
+ document.querySelectorAll('.sidebar .collapse').forEach(target => {
262
+ const id = target.getAttribute('id');
263
+ const state = localStorage.getItem('nav-group-' + id);
264
+ const trigger = document.querySelector(`[data-bs-target="#${id}"]`);
265
+
266
+ if (state === 'collapsed') {
267
+ target.classList.remove('show');
268
+ if (trigger) {
269
+ trigger.classList.add('collapsed');
270
+ trigger.setAttribute('aria-expanded', 'false');
271
+ trigger.querySelector('.nav-chevron')?.classList.add('collapsed');
272
+ }
273
+ } else if (state === 'expanded') {
274
+ target.classList.add('show');
275
+ if (trigger) {
276
+ trigger.classList.remove('collapsed');
277
+ trigger.setAttribute('aria-expanded', 'true');
278
+ trigger.querySelector('.nav-chevron')?.classList.remove('collapsed');
279
+ }
280
+ }
281
+ });
282
+
283
+ // Listen to collapse show/hide events to save states to localStorage
284
  document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(el => {
285
  const targetId = el.getAttribute('data-bs-target');
286
  const target = document.querySelector(targetId);
287
  if (!target) return;
288
+
289
  target.addEventListener('show.bs.collapse', () => {
290
+ el.classList.remove('collapsed');
291
+ el.setAttribute('aria-expanded', 'true');
292
  el.querySelector('.nav-chevron')?.classList.remove('collapsed');
293
+
294
+ const id = target.getAttribute('id');
295
+ localStorage.setItem('nav-group-' + id, 'expanded');
296
  });
297
  target.addEventListener('hide.bs.collapse', () => {
298
+ el.classList.add('collapsed');
299
+ el.setAttribute('aria-expanded', 'false');
300
  el.querySelector('.nav-chevron')?.classList.add('collapsed');
301
+
302
+ const id = target.getAttribute('id');
303
+ localStorage.setItem('nav-group-' + id, 'collapsed');
304
  });
305
  });
306
  </script>