nodebb-plugin-ezoic-infinite 1.7.3 → 1.7.4
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.
- package/package.json +1 -1
- package/public/client.js +54 -50
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -60,19 +60,25 @@
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Table centrale
|
|
63
|
+
* Table centrale par kindClass :
|
|
64
64
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
65
|
+
* sel : sélecteur CSS complet de l'élément cible
|
|
66
|
+
* baseTag : tag CSS à préfixer dans les querySelector de recherche
|
|
67
|
+
* (déduit manuellement — évite le fragile split('[')[0])
|
|
68
|
+
* anchorAttr : attribut DOM STABLE qui identifie l'élément de façon unique
|
|
69
|
+
* et permanente, utilisé comme clé d'ancre du wrap.
|
|
70
|
+
* → data-pid pour posts (id message, immuable)
|
|
71
|
+
* → data-index pour topics (position dans la liste)
|
|
72
|
+
* → data-cid pour catégories (id catégorie, immuable)
|
|
73
|
+
* ordinalAttr: attribut utilisé pour calculer l'intervalle d'injection.
|
|
74
|
+
* Doit être un entier 0-based fourni par NodeBB.
|
|
75
|
+
* Pour posts ET topics : data-index (0-based, toujours présent).
|
|
76
|
+
* Pour catégories : pas d'infinite scroll → fallback positionnel.
|
|
71
77
|
*/
|
|
72
78
|
const KIND = {
|
|
73
|
-
'ezoic-ad-message': { sel: SEL.post, anchorAttr: 'data-pid' },
|
|
74
|
-
'ezoic-ad-between': { sel: SEL.topic, anchorAttr: 'data-index' },
|
|
75
|
-
'ezoic-ad-categories': { sel: SEL.category, anchorAttr: 'data-cid' },
|
|
79
|
+
'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
|
|
80
|
+
'ezoic-ad-between': { sel: SEL.topic, baseTag: 'li', anchorAttr: 'data-index', ordinalAttr: 'data-index' },
|
|
81
|
+
'ezoic-ad-categories': { sel: SEL.category, baseTag: 'li', anchorAttr: 'data-cid', ordinalAttr: null },
|
|
76
82
|
};
|
|
77
83
|
|
|
78
84
|
// ── État ───────────────────────────────────────────────────────────────────
|
|
@@ -307,46 +313,33 @@
|
|
|
307
313
|
// ── Prune ──────────────────────────────────────────────────────────────────
|
|
308
314
|
|
|
309
315
|
/**
|
|
310
|
-
* Supprime les wraps dont l'élément-ancre n'est plus dans le DOM.
|
|
316
|
+
* Supprime les wraps VIDES dont l'élément-ancre n'est plus dans le DOM.
|
|
311
317
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*/
|
|
320
|
-
/**
|
|
321
|
-
* Supprime les wraps dont l'élément-ancre n'est plus dans le DOM.
|
|
322
|
-
*
|
|
323
|
-
* Règle stricte : on ne supprime JAMAIS un wrap rempli (filled).
|
|
324
|
-
* - Il peut contenir un player wyvern actif → .remove() déclenche des
|
|
325
|
-
* erreurs async ("Cannot read 'paused'", "offsetWidth", "getChild"…).
|
|
326
|
-
* - Le post-ancre peut être temporairement virtualisé par NodeBB puis
|
|
327
|
-
* revenir — dans ce cas le wrap filled doit rester en place.
|
|
328
|
-
*
|
|
329
|
-
* Seuls les wraps VIDES dont l'ancre a disparu sont supprimés.
|
|
318
|
+
* Règles :
|
|
319
|
+
* 1. Jamais avant MIN_PRUNE_AGE_MS (DOM post-batch pas encore stabilisé).
|
|
320
|
+
* 2. Jamais un wrap rempli (player wyvern potentiellement actif).
|
|
321
|
+
* 3. L'ancre est retrouvée via KIND[klass].anchorAttr (stable par design) :
|
|
322
|
+
* ezoic-ad-message → [data-pid="123"]
|
|
323
|
+
* ezoic-ad-between → li[data-index="5"]
|
|
324
|
+
* ezoic-ad-categories → li[data-cid="7"]
|
|
330
325
|
*/
|
|
331
326
|
function pruneOrphans(klass) {
|
|
332
327
|
const meta = KIND[klass];
|
|
333
328
|
if (!meta) return;
|
|
334
329
|
|
|
335
|
-
const baseTag = meta.sel.split('[')[0];
|
|
336
|
-
|
|
337
330
|
document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(w => {
|
|
338
331
|
if (ts() - parseInt(w.getAttribute(A_CREATED) || '0', 10) < MIN_PRUNE_AGE_MS) return;
|
|
339
|
-
|
|
340
|
-
// Ne jamais retirer un wrap qui contient du contenu (player potentiellement actif)
|
|
341
|
-
if (isFilled(w)) return;
|
|
332
|
+
if (isFilled(w)) return; // jamais supprimer un wrap rempli (wyvern)
|
|
342
333
|
|
|
343
334
|
const key = w.getAttribute(A_ANCHOR) ?? '';
|
|
344
335
|
const sid = key.slice(klass.length + 1);
|
|
345
336
|
if (!sid) { mutate(() => dropWrap(w)); return; }
|
|
346
337
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
338
|
+
// Construire le sélecteur avec baseTag explicite depuis KIND
|
|
339
|
+
// baseTag='' pour posts → sélecteur : [data-pid="123"] (correct, sans tag ambigu)
|
|
340
|
+
// baseTag='li' pour topics/categories → li[data-index="5"], li[data-cid="7"]
|
|
341
|
+
const sel = `${meta.baseTag}[${meta.anchorAttr}="${sid.replace(/"/g, '\\"')}"]`;
|
|
342
|
+
const anchorEl = document.querySelector(sel);
|
|
350
343
|
if (!anchorEl || !anchorEl.isConnected) mutate(() => dropWrap(w));
|
|
351
344
|
});
|
|
352
345
|
}
|
|
@@ -388,24 +381,35 @@
|
|
|
388
381
|
// ── Injection ──────────────────────────────────────────────────────────────
|
|
389
382
|
|
|
390
383
|
/**
|
|
391
|
-
* Ordinal 0-based pour le calcul de l'intervalle.
|
|
392
|
-
*
|
|
393
|
-
*
|
|
384
|
+
* Ordinal 0-based d'un élément pour le calcul de l'intervalle d'injection.
|
|
385
|
+
*
|
|
386
|
+
* Utilise KIND[klass].ordinalAttr en priorité (data-index pour posts et topics,
|
|
387
|
+
* null pour catégories). Si absent, fallback positionnel dans le parent.
|
|
388
|
+
*
|
|
389
|
+
* Pour les posts : KIND.baseTag='' donc le fallback itère sur les enfants directs
|
|
390
|
+
* du parent en filtrant par sélecteur complet (pas juste le tag).
|
|
394
391
|
*/
|
|
395
392
|
function ordinal(klass, el) {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
//
|
|
393
|
+
const meta = KIND[klass];
|
|
394
|
+
|
|
395
|
+
// 1. Attribut ordinal explicite (data-index sur posts et topics)
|
|
396
|
+
if (meta?.ordinalAttr) {
|
|
397
|
+
const v = el.getAttribute(meta.ordinalAttr);
|
|
398
|
+
if (v !== null && v !== '' && !isNaN(v)) return parseInt(v, 10);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 2. Fallback positionnel — compte parmi les frères qui matchent le même sélecteur
|
|
399
402
|
try {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
let i = 0;
|
|
404
|
+
const siblings = el.parentElement?.children ?? [];
|
|
405
|
+
const fullSel = meta?.sel ?? '';
|
|
406
|
+
for (const s of siblings) {
|
|
407
|
+
if (s === el) return i;
|
|
408
|
+
// Compter uniquement les éléments du même type (pas les wraps ou autres)
|
|
409
|
+
if (!fullSel || s.matches?.(fullSel)) i++;
|
|
407
410
|
}
|
|
408
411
|
} catch (_) {}
|
|
412
|
+
|
|
409
413
|
return 0;
|
|
410
414
|
}
|
|
411
415
|
|