nodebb-plugin-recent-cards-2 3.3.20 → 3.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/static/lib/main.js +58 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-recent-cards-2",
3
- "version": "3.3.20",
3
+ "version": "3.3.21",
4
4
  "description": "Add lavender-style cards of recent topics to Persona's category homepage",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -177,16 +177,7 @@ $(document).ready(function () {
177
177
 
178
178
  // If showing all, and we have the original cards stashed, restore them
179
179
  if (showAll && pluginContainer.data('rc-original-cards')) {
180
- destroySlick(carouselContainer);
181
- carouselContainer.html(pluginContainer.data('rc-original-cards'));
182
- emptyState.addClass('d-none');
183
- carouselContainer.removeClass('d-none');
184
- if (isCarouselMode) {
185
- carouselContainer.addClass('overflow-hidden invisible');
186
- initSlick(carouselContainer);
187
- } else {
188
- carouselContainer.removeClass('carousel-mode invisible');
189
- }
180
+ swapCards(carouselContainer, emptyState, pluginContainer.data('rc-original-cards'), isCarouselMode);
190
181
  return;
191
182
  }
192
183
 
@@ -199,17 +190,14 @@ $(document).ready(function () {
199
190
  pluginContainer.data('rc-original-cards', carouselContainer.html());
200
191
  }
201
192
 
202
- // Show loading state
203
- destroySlick(carouselContainer);
204
- carouselContainer.addClass('overflow-hidden invisible');
205
-
206
- // Cancel any in-flight request to prevent race conditions
193
+ // Abort any in-flight request (safe to call on completed/null)
207
194
  const prevXhr = pluginContainer.data('rc-xhr');
208
- if (prevXhr && prevXhr.readyState !== 4) {
195
+ if (prevXhr) {
209
196
  prevXhr.abort();
197
+ pluginContainer.removeData('rc-xhr');
210
198
  }
211
199
 
212
- // Fetch filtered topics from server
200
+ // Keep old cards visible while loading — no blank flash
213
201
  const params = $.param({
214
202
  cids: selectedCids.join(','),
215
203
  sort: widgetCfg.sort || 'recent',
@@ -218,38 +206,51 @@ $(document).ready(function () {
218
206
  thumbnailStyle: widgetCfg.thumbnailStyle || 'background',
219
207
  });
220
208
 
221
- const xhr = $.getJSON(config.relative_path + '/plugins/nodebb-plugin-recent-cards/filter?' + params, function (data) {
222
- const parsed = $(data.html);
223
- const newCards = parsed.find('.recent-cards').html();
224
-
225
- if (!newCards || !newCards.trim()) {
226
- emptyState.removeClass('d-none');
227
- carouselContainer.addClass('d-none').removeClass('overflow-hidden invisible');
228
- return;
229
- }
209
+ const xhr = $.getJSON(config.relative_path + '/plugins/nodebb-plugin-recent-cards/filter?' + params)
210
+ .done(function (data) {
211
+ const parsed = $('<div>').html(data.html);
212
+ const newCards = parsed.find('.recent-cards').html();
213
+
214
+ if (!newCards || !newCards.trim()) {
215
+ destroySlick(carouselContainer);
216
+ emptyState.removeClass('d-none');
217
+ carouselContainer.addClass('d-none');
218
+ return;
219
+ }
220
+
221
+ // Translate, then do instant swap (destroy → replace → reinit in one tick)
222
+ require(['translator'], function (translator) {
223
+ translator.Translator.create().translate(newCards).then(function (translated) {
224
+ swapCards(carouselContainer, emptyState, translated, isCarouselMode);
225
+ }).catch(function () {
226
+ swapCards(carouselContainer, emptyState, newCards, isCarouselMode);
227
+ });
228
+ });
229
+ })
230
+ .fail(function (jqXHR, textStatus) {
231
+ if (textStatus === 'abort') return;
232
+ // On error, keep current cards as-is
233
+ })
234
+ .always(function () {
235
+ pluginContainer.removeData('rc-xhr');
236
+ });
230
237
 
231
- emptyState.addClass('d-none');
232
- carouselContainer.removeClass('d-none').html(newCards);
238
+ pluginContainer.data('rc-xhr', xhr);
239
+ }
233
240
 
234
- if (isCarouselMode) {
235
- carouselContainer.addClass('overflow-hidden invisible');
236
- initSlick(carouselContainer);
237
- } else {
238
- carouselContainer.removeClass('carousel-mode invisible overflow-hidden');
239
- }
240
- }).fail(function (jqXHR, textStatus) {
241
- if (textStatus === 'abort') return; // intentional cancel, ignore
242
- // On error, restore original cards
243
- if (pluginContainer.data('rc-original-cards')) {
244
- carouselContainer.html(pluginContainer.data('rc-original-cards'));
245
- }
246
- carouselContainer.removeClass('overflow-hidden invisible');
247
- if (isCarouselMode) {
248
- initSlick(carouselContainer);
249
- }
250
- });
241
+ function swapCards(carouselContainer, emptyState, html, isCarouselMode) {
242
+ // Instant swap: destroy → replace → reinit in one synchronous block (no blank flash)
243
+ destroySlick(carouselContainer);
244
+ emptyState.addClass('d-none');
245
+ carouselContainer.removeClass('d-none').html(html);
246
+ carouselContainer.find('.timeago').timeago();
251
247
 
252
- pluginContainer.data('rc-xhr', xhr);
248
+ if (isCarouselMode) {
249
+ carouselContainer.addClass('overflow-hidden invisible');
250
+ initSlick(carouselContainer);
251
+ } else {
252
+ carouselContainer.removeClass('carousel-mode invisible overflow-hidden');
253
+ }
253
254
  }
254
255
 
255
256
  function destroySlick(container) {
@@ -302,14 +303,18 @@ $(document).ready(function () {
302
303
  }
303
304
  });
304
305
 
305
- // Checkbox change
306
+ // Checkbox change (debounced — user may click multiple checkboxes rapidly)
307
+ let filterTimeout;
306
308
  pluginContainer.on('change.rcFilter', '.rc-filter-option input[type="checkbox"]', function () {
307
- const selected = [];
308
- pluginContainer.find('.rc-filter-option input:checked').each(function () {
309
- selected.push(parseInt($(this).attr('data-cid'), 10));
310
- });
311
- saveFilter(storageKey, selected, allCids);
312
- applyFilter(pluginContainer, selected, categories);
309
+ clearTimeout(filterTimeout);
310
+ filterTimeout = setTimeout(function () {
311
+ const selected = [];
312
+ pluginContainer.find('.rc-filter-option input:checked').each(function () {
313
+ selected.push(parseInt($(this).attr('data-cid'), 10));
314
+ });
315
+ saveFilter(storageKey, selected, allCids);
316
+ applyFilter(pluginContainer, selected, categories);
317
+ }, 300);
313
318
  });
314
319
 
315
320
  // Clear all (from dropdown header)