PageSourceSearch

https://probiwaem.ru/static/shorts/short.js?v=0.31.25

js probiwaem.ru collected 2026-09-24 15:11:38 UTC 9,565 bytes, 220 lines download raw bytes

1/* ============================================================================
2 * shorts/short.js — per-short controller.
3 *
4 * Wraps existing window.Investigation class в context'е /shorts feed slot.
5 * Один Short instance = один <article class="pb-short"> = одно расследование.
6 *
7 * Lifecycle:
8 *   ctor(article, data, options) — mount + ready (НЕ start play)
9 *   .play()                       — start Investigation
10 *   .pause()                      — pause Investigation + clear progress-bar
11 *   .resume()                     — resume Investigation
12 *   .destroy()                    — close Investigation, remove DOM contents
13 *                                   (stage заполняется заново когда Embla
14 *                                    re-virtualize'нет slot)
15 *
16 * Hooks:
17 *   onActivate(article, short)    — feed.js вызовет когда article попал в фокус
18 *   onDeactivate(article, short)  — feed.js вызовет когда article уехал
19 *   onComplete(short)             — внутри Investigation после summary card
20 *
21 * Progress-bar binding (Phase 1 simple):
22 *   - feed.js передаёт ссылку на progressEl (element с 5 .pb-bar children)
23 *   - Short.onSceneChange(idx, name) обновляет classes is-past/is-current/is-future
24 *   - CSS transition на .pb-bar.is-current > i scaleX(0)→1 синхронен со scene timing
25 * ========================================================================== */
26(function (global) {
27    "use strict";
28
29    // Scene durations (must match investigation.js timings — Scene 1: 1500,
30    // Scene 2: 1800, Scene 3: 1500, Scene 4: 1800, Scene 5: 2400+1800 hold).
31    var SCENE_DURATIONS = {
32        intake:  1500,
33        reviews: 1800,
34        seller:  1500,
35        price:   1800,
36        verdict: 4200, // includes hold + summary
37    };
38    var SCENE_INDEX = { intake: 0, reviews: 1, seller: 2, price: 3, verdict: 4 };
39
40    function Short(article, data, options) {
41        this.article = article;
42        this.data = data || {};
43        this.opts = options || {};
44        this.progressEl = options && options.progressEl || null;
45        this.inv = null;
46        this.state = "idle"; // idle, ready, playing, paused, complete, destroyed
47        this.completed = false;
48    }
49
50    Short.prototype._mount = function () {
51        if (this.inv) return;
52        if (this.state === "destroyed") return;
53        if (typeof global.Investigation !== "function") {
54            console.warn("[short] Investigation class unavailable");
55            return;
56        }
57        var stage = this.article.querySelector(".pb-short-stage");
58        if (!stage) return;
59
60        // v0.25.23: selective cleanup — preview-img остаётся в DOM (для fade-out
61        // когда Investigation смонтируется). Раньше textContent="" удалял всё
62        // включая preview, что ломало pre-show image feature.
63        try {
64            var nodes = stage.childNodes;
65            for (var ci = nodes.length - 1; ci >= 0; ci--) {
66                var n = nodes[ci];
67                if (n.nodeType !== 1 || !n.classList ||
68                    !n.classList.contains("pb-short-preview-img")) {
69                    stage.removeChild(n);
70                }
71            }
72        } catch (_) {}
73
74        var self = this;
75        // v0.25.3 FIX: было embedded:false → Investigation создавала <dialog>
76        // ПОВЕРХ feed (пользователь видел dialog, не feed). Теперь embedded:true →
77        // Investigation монтируется INLINE в stage без своего dialog.
78        // Существующие embedded-стили (phone-frame compact look) переопределяем
79        // через CSS в feed.css — внутри .pb-short-stage сцены full-bleed.
80        this.inv = new global.Investigation({
81            data: this.data,
82            mountPoint: stage,
83            embedded: true,
84            mute: this.opts.mute !== false,
85            loop: false,
86            autoLoop: false,
87            onSceneChange: function (idx, name) { self._onSceneChange(idx, name); },
88            onComplete: function (info) { self._onComplete(info); },
89        });
90        try { stage.classList.add("pb-short-stage-mounted"); } catch (_) {}
91        this.state = "ready";
92    };
93
94    Short.prototype.play = function () {
95        if (this.state === "destroyed") return;
96        this._mount();
97        if (!this.inv) return;
98        try {
99            this.inv.play();
100            this.state = "playing";
101            this._setProgressForScene(0, "intake");
102            // v0.25.23: signal CSS to fade-out preview-img (Investigation готов)
103            try { this.article.classList.add("is-investigation-mounted"); } catch (_) {}
104        } catch (e) {
105            console.warn("[short] play failed", e);
106        }
107    };
108
109    Short.prototype.pause = function () {
110        if (!this.inv || this.state === "destroyed") return;
111        try { this.inv.pause(); this.state = "paused"; } catch (_) {}
112    };
113
114    Short.prototype.resume = function () {
115        if (!this.inv || this.state === "destroyed") return;
116        try { this.inv.resume(); this.state = "playing"; } catch (_) {}
117    };
118
119    Short.prototype.destroy = function () {
120        if (this.state === "destroyed") return;
121        if (this.inv) {
122            try { this.inv.close(); } catch (_) {}
123            this.inv = null;
124        }
125        try {
126            var stage = this.article.querySelector(".pb-short-stage");
127            if (stage) {
128                // v0.25.23: selective cleanup — preview-img остаётся для re-mount
129                var nodes = stage.childNodes;
130                for (var di = nodes.length - 1; di >= 0; di--) {
131                    var nn = nodes[di];
132                    if (nn.nodeType !== 1 || !nn.classList ||
133                        !nn.classList.contains("pb-short-preview-img")) {
134                        stage.removeChild(nn);
135                    }
136                }
137            }
138            this.article.classList.remove("is-investigation-mounted");
139        } catch (_) {}
140        this.state = "destroyed";
141    };
142
143    Short.prototype._onSceneChange = function (idx, name) {
144        this._setProgressForScene(idx, name);
145        if (typeof this.opts.onSceneChange === "function") {
146            try { this.opts.onSceneChange(idx, name); } catch (_) {}
147        }
148    };
149
150    Short.prototype._onComplete = function (info) {
151        this.completed = true;
152        this.state = "complete";
153        // Заполнить все progress-bar segments как is-
153past.
154        if (this.progressEl) {
155            var bars = this.progressEl.querySelectorAll(".pb-bar");
156            for (var i = 0; i < bars.length; i++) {
157                bars[i].classList.remove("is-current", "is-future");
158                bars[i].classList.add("is-past");
159            }
160        }
161        if (typeof this.opts.onComplete === "function") {
162            try { this.opts.onComplete(info, this); } catch (_) {}
163        }
164    };
165
166    Short.prototype._setProgressForScene = function (idx, name) {
167        if (!this.progressEl) return;
168        var bars = this.progressEl.querySelectorAll(".pb-bar");
169        for (var i = 0; i < bars.length; i++) {
170            var bar = bars[i];
171            bar.classList.remove("is-past", "is-current", "is-future");
172            if (i < idx) bar.classList.add("is-past");
173            else if (i === idx) bar.classList.add("is-current");
174            else bar.classList.add("is-future");
175            // Для current bar — set explicit transition-duration matching scene
176            if (i === idx) {
177                var dur = SCENE_DURATIONS[name] || 1500;
178                var fill = bar.querySelector("i");
179                if (fill) {
180                    fill.style.transition = "none";
181                    fill.style.transform = "scaleX(0)";
182                    // force reflow
183                    void fill.offsetWidth;
184                    fill.style.transition = "transform " + dur + "ms linear";
185                    fill.style.transform = "scaleX(1)";
186                }
187            } else {
188                var fill2 = bar.querySelector("i");
189                if (fill2) {
190                    fill2.style.transition = "";
191                    fill2.style.transform = i < idx ? "scaleX(1)" : "scaleX(0)";
192                }
193            }
194        }
195    };
196
197    /**
198     * setUserPaused(bool) — toggles the .is-user-paused class on the article
199     * element so feed.css can render the floating pause-indicator overlay.
200     * Does NOT affect Investigation playback (that's done via .pause/.resume).
201     * Safe to call repeatedly with the same value.
202     */
203    Short.prototype.setUserPaused = function (val) {
204        if (!this.article) return;
205        try { this.article.classList.toggle("is-user-paused", !!val); } catch (_) {}
206    };
207
208    Short.prototype.resetProgress = function () {
209        if (!this.progressEl) return;
210        var bars = this.progressEl.querySelectorAll(".pb-bar");
211        for (var i = 0; i < bars.length; i++) {
212            bars[i].classList.remove("is-past", "is-current");
213            bars[i].classList.add("is-future");
214            var fill = bars[i].querySelector("i");
215            if (fill) { fill.style.transition = ""; fill.style.transform = "scaleX(0)"; }
216        }
217    };
218
219    global.PbShort = Short;
220})(typeof window !== "undefined" ? window : this);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.