@@ -93,21 +93,53 @@ var App = (function() {
9393
9494 function renderHome ( container , data ) {
9595 if ( ! container ) return ;
96+
97+ function timeAgo ( dateStr ) {
98+ if ( ! dateStr ) return '' ;
99+ try {
100+ var now = new Date ( ) ;
101+ var then = new Date ( dateStr ) ;
102+ var diff = Math . floor ( ( now - then ) / ( 1000 * 60 * 60 * 24 ) ) ;
103+
104+ if ( diff === 0 ) return 'сегодня' ;
105+ if ( diff === 1 ) return 'вчера' ;
106+ if ( diff < 4 ) return diff + ' дня назад' ;
107+ if ( diff < 7 ) return diff + ' дней назад' ;
108+ if ( diff < 30 ) return Math . floor ( diff / 7 ) + ' нед. назад' ;
109+ return dateStr ;
110+ } catch ( e ) {
111+ return dateStr ;
112+ }
113+ }
114+
96115 var titles = ( data && data . titles ) ? data . titles : [ ] ;
97116 if ( titles . length === 0 ) {
98117 container . innerHTML = '<p class="text-muted">Пока нет добавленных тайтлов.</p>' ;
99118 return ;
100119 }
120+
101121 var html = '' ;
102122 titles . forEach ( function ( t , i ) {
103123 var translated = t . translatedChapters || 0 ;
104124 var total = t . totalChapters || 0 ;
105125 var pct = total > 0 ? Math . min ( 100 , Math . round ( ( translated / total ) * 100 ) ) : 0 ;
106- var lastCh = t . chapters && t . chapters . length ? t . chapters . reduce ( function ( a , c ) {
107- var d = c . datePublished || '' ;
108- return d > ( a . datePublished || '' ) ? c : a ;
109- } , t . chapters [ 0 ] ) : null ;
110- var lastDate = lastCh ? ( lastCh . datePublished || t . lastChapterDate || '' ) : ( t . lastChapterDate || '' ) ;
126+
127+ var lastCh = null ;
128+ var lastDate = '' ;
129+ var lastNum = '' ;
130+
131+ if ( t . chapters && t . chapters . length ) {
132+ var sortedChapters = t . chapters . slice ( ) . sort ( function ( a , b ) {
133+ return ( b . id || 0 ) - ( a . id || 0 ) ;
134+ } ) ;
135+
136+ if ( sortedChapters . length > 0 ) {
137+ lastCh = sortedChapters [ 0 ] ;
138+ lastDate = lastCh . datePublished || t . lastChapterDate || '' ;
139+ lastNum = lastCh . number != null ? lastCh . number : lastCh . id ;
140+ }
141+ }
142+
111143 var coverSrc = ( t . cover || '' ) . replace ( / ^ \/ / , '' ) ;
112144
113145 html += '<a href="' + getTitleUrl ( t . id ) + '" class="title-card fade-in" style="animation-delay:' + ( i * 0.05 ) + 's">' ;
@@ -117,13 +149,21 @@ var App = (function() {
117149 if ( t . titleEn && t . title !== t . titleEn ) html += '<p class="title-card-en">' + escapeHtml ( t . titleEn ) + '</p>' ;
118150 html += '<div class="progress-wrap"><div class="progress-bar"><div class="progress-fill" style="width:' + pct + '%"></div></div>' ;
119151 html += '<p class="progress-text">' + translated + ' / ' + total + ' chapters translated</p></div>' ;
152+
153+ if ( lastDate && lastNum ) {
154+ var displayDate = timeAgo ( lastDate ) ;
155+ html += '<p class="title-card-last-update">📖 Обновлено: гл. ' + lastNum + ' (' + escapeHtml ( displayDate ) + ')</p>' ;
156+ } else if ( lastDate ) {
157+ var displayDate = timeAgo ( lastDate ) ;
158+ html += '<p class="title-card-last-update">📖 Обновлено: ' + escapeHtml ( displayDate ) + '</p>' ;
159+ }
160+
120161 if ( t . genre && t . genre . length ) {
121162 html += '<div class="genres">' ;
122163 t . genre . slice ( 0 , 4 ) . forEach ( function ( g ) { html += '<span class="genre-tag">' + escapeHtml ( g ) + '</span>' ; } ) ;
123164 html += '</div>' ;
124165 }
125166 html += '<span class="status-badge ' + statusClass ( t . status ) + '">' + ( t . status === 'completed' ? 'Completed' : t . status === 'hiatus' || t . status === 'on hiatus' ? 'On Hiatus' : 'Ongoing' ) + '</span>' ;
126- if ( lastDate ) html += '<p class="title-card-date">Last chapter: ' + escapeHtml ( lastDate ) + '</p>' ;
127167 html += '</div></a>' ;
128168 } ) ;
129169 container . innerHTML = html ;
@@ -138,10 +178,8 @@ var App = (function() {
138178 }
139179
140180 function renderTitlePage ( root , title ) {
141- // Ключ для localStorage (уникальный для каждого тайтла)
142181 var SORT_KEY = 'bb_sort_' + title . id ;
143182
144- // Загружаем состояние сортировки из localStorage
145183 var sortDescending = false ;
146184 try {
147185 var saved = localStorage . getItem ( SORT_KEY ) ;
@@ -150,16 +188,13 @@ var App = (function() {
150188 }
151189 } catch ( e ) { }
152190
153- // Функция для рендеринга списка глав с учетом сортировки
154191 function renderChapters ( lang ) {
155192 var chapters = ( lang === 'ru' ) ? byLang . ru : byLang . en ;
156193 var container = document . getElementById ( 'chapters-' + lang ) ;
157194 if ( ! container ) return ;
158195
159- // Копируем массив, чтобы не менять оригинал
160196 var sorted = chapters . slice ( ) ;
161197
162- // Сортируем в зависимости от состояния
163198 sorted . sort ( function ( a , b ) {
164199 var na = a . number != null ? a . number : a . id ;
165200 var nb = b . number != null ? b . number : b . id ;
@@ -168,7 +203,6 @@ var App = (function() {
168203 return sortDescending ? nb - na : na - nb ;
169204 } ) ;
170205
171- // Рендерим список
172206 container . innerHTML = chapterListHtml ( sorted , title . id , lang ) ;
173207 }
174208
@@ -209,14 +243,12 @@ var App = (function() {
209243 html += '<span class="status-badge ' + statusClass ( title . status ) + '">' + ( title . status === 'completed' ? 'Completed' : title . status === 'hiatus' || title . status === 'on hiatus' ? 'On Hiatus' : 'Ongoing' ) + '</span>' ;
210244 html += '</div></div>' ;
211245
212- // Вкладки языков + кнопка сортировки
213246 html += '<div class="lang-tabs-wrapper" style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; margin-bottom:1rem;">' ;
214247 html += '<div class="lang-tabs" role="tablist" style="display:flex; gap:0; border-bottom:1px solid var(--color-border); flex-wrap:wrap;">' ;
215248 html += '<button type="button" class="lang-tab active" role="tab" aria-selected="true" data-lang="ru">RU · Перевод</button>' ;
216249 html += '<button type="button" class="lang-tab" role="tab" aria-selected="false" data-lang="en">EN · Оригинал</button>' ;
217250 html += '</div>' ;
218251
219- // Кнопка сортировки (всегда один текст)
220252 html += '<button type="button" id="sort-toggle" class="btn btn-sort" style="font-size:0.8125rem; padding:0.3rem 0.75rem; min-height:32px; border-color:var(--color-border); background:var(--color-bg-elevated); color:var(--color-text-secondary);">↕ Сортировать</button>' ;
221253 html += '</div>' ;
222254
@@ -236,7 +268,6 @@ var App = (function() {
236268 document . getElementById ( 'chapters-ru' ) . style . display = lang === 'ru' ? '' : 'none' ;
237269 document . getElementById ( 'chapters-en' ) . style . display = lang === 'en' ? '' : 'none' ;
238270
239- // При переключении вкладки применяем текущую сортировку
240271 renderChapters ( lang ) ;
241272 } ) ;
242273 } ) ;
@@ -247,21 +278,17 @@ var App = (function() {
247278 sortBtn . addEventListener ( 'click' , function ( ) {
248279 sortDescending = ! sortDescending ;
249280
250- // Сохраняем в localStorage
251281 try {
252282 localStorage . setItem ( SORT_KEY , String ( sortDescending ) ) ;
253283 } catch ( e ) { }
254284
255- // Определяем, какая вкладка сейчас активна
256285 var activeTab = root . querySelector ( '.lang-tab.active' ) ;
257286 var activeLang = activeTab ? activeTab . getAttribute ( 'data-lang' ) : 'ru' ;
258287
259- // Перерендериваем только активную вкладку
260288 renderChapters ( activeLang ) ;
261289 } ) ;
262290 }
263291
264- // После загрузки применяем сохраненную сортировку к активной вкладке
265292 var activeTab = root . querySelector ( '.lang-tab.active' ) ;
266293 var activeLang = activeTab ? activeTab . getAttribute ( 'data-lang' ) : 'ru' ;
267294 renderChapters ( activeLang ) ;
@@ -297,7 +324,6 @@ var App = (function() {
297324 return div . innerHTML ;
298325 }
299326
300- // Обновленная функция получения конкретной главы
301327 function getChapter ( data , titleId , chapterNum ) {
302328 var title = ( data && data . titles ) ? data . titles . find ( function ( t ) { return t . id === titleId ; } ) : null ;
303329 if ( ! title || ! title . chapters ) return { title : title , chapter : null } ;
0 commit comments