PageSourceSearch

https://xgm.guru/public/mjs/79df25d1/components/resource/recommender.js

js xgm.guru collected 2026-09-24 10:33:44 UTC 6,845 bytes, 168 lines download raw bytes

1import {globalProgress, L} from '../../ui/utils.js'
2import {firemsg} from '../../ui/firemsg.js'
3import {Modal} from '../../ui/modal.js'
4
5/** @type {HTMLFormElement|null} form */
6const form = document.getElementById('recommender-interest-control-form')
7if (form) {
8    /** @type {HTMLFormElement|null} fromLoader */
9    const formLoader = form.querySelector('.as-loader')
10    /** @type {HTMLFormElement|null} formNoLoader */
11    const formNoLoader = form.querySelector('.as-noloader')
12    /** @type {HTMLFormElement|null} formArea */
13    const formArea = form.querySelector('.data')
14    /** @type {HTMLFormElement|null} formArea */
15    const subDescription = form.querySelector('.sub-description')
16
17    /**
18     * @param {string|null} [recommenderData] json
19     * @returns {Promise<void>}
20     */
21    const loadData = (recommenderData) => {
22        formLoader.style.display = 'block'
23        formNoLoader.style.display = 'none'
24        return fetch('/interest/list/decode-from-item', {
25            method: 'POST',
26            body: recommenderData,
27            headers: {'Content-Type': 'application/json'}
28        })
29            .then((response) => {
30                response.json().then(data => { 
31                    if(data.html) {
32                        formArea.innerHTML = data.html
33                    }
34                    if(!data.interests || !data.interests.length) {
35                        firemsg(L('Ресурс не является персональной рекомендацией', 'The resource is not a personal recommendation'))
36                        Modal.close(null)
37                    }
38                })
39            })
40            .catch(r => {
41                formArea.innerHTML = r.statusText || 'Error '+r.status
42            })
43            .finally(() => {
44                formLoader.style.display = 'none'
45                formNoLoader.style.display = 'block'
46            })
47    }
48    document.body.addEventListener('click', e => {
49        const button = e.target.closest('[data-interests-control]')
50        if (!button) return
51        formArea.innerHTML = ''
52        const resource = button.closest('.as-resource')
53        button.remove()
54        const recommenderDataInput = resource.querySelector('.as-resource-recommender-data');
55        if(!recommenderDataInput || !recommenderDataInput.value || recommenderDataInput.value.indexOf('"interests":[]') != -1) {
56            firemsg(L('Ресурс не является персональной рекомендацией', 'The resource is not a personal recommendation'))
57            return
58        }
59        loadData(recommenderDataInput.value)
60
61        Modal.open(async () => {
62            return form
63        }, null)
64    })
65    document.body.addEventListener('click', e => {
66        const button = e.target.closest('[data-interest-adjust]')
67        if (!button) return
68        form.querySelectorAll('.interest-control-item-actions-btn').forEach(v => { v.disabled = true })
69        const item = button.closest('.resource-recommender-interest-control-item')
70        const weightEl = item.querySelector('.interest-control-item-weight')
71        const valueEl = item.querySelector('.interest-control-item-weight-value')
72        const interest = JSON.parse(item.dataset.interest);
73        interest.w = parseFloat(valueEl.innerHTML)/100.0;
74
75        fetch('/interest/adjust/'+button.dataset.interestAdjust, {
76            method: 'POST',
77            body: JSON.stringify(interest),
78            headers: {'Content-Type': 'application/json'}
79        })
80            .then((response) => {
81                form.querySelectorAll('.interest-control-item-actions-btn').forEach(v => { v.disabled = false })
82                response.json().then(data => { 
83                    if(data.interest) {
84                        if(subDescription) {
85                            subDescription.style.display = 'block'
86                        }
87                        
88                        valueEl.innerHTML = (data.interest.w * 100.0).toFixed(2)
89                        if(data.interest.w > 0.5) {
90                            weightEl.classList.add('interest-control-item-we
90ight-recommended')
91                        } else {
92                            weightEl.classList.remove('interest-control-item-weight-recommended')
93                        }
94                        form.querySelector('.interest-control-summary').style.display = 'none'
95                        item.dataset.interest = JSON.stringify(data.interest)
96                    }
97                })
98            })
99        
100    })
101}
102
103
104/**
105 * @event CustomEvent#onboarding.recommender.select
106 */
107/** @type {HTMLFormElement|null} onboardingForm */
108const onboardingForm = document.getElementById('recommender-interest-onboarding-form')
109if(onboardingForm) {
110    /** @type {HTMLFormElement|null} fromLoader */
111    const formLoader = onboardingForm.querySelector('.as-loader')
112    /** @type {HTMLFormElement|null} formArea */
113    const formArea = onboardingForm.querySelector('.data')
114
115    /**
116     * @returns {Promise<void>}
117     */
118    const loadData = () => {
119        formLoader.style.display = 'block'
120        return fetch('/interest/onboarding/load?old='+window.xgm.onboarding['recommender.old'])
121            .then((response) => {
122                response.json().then(data => { 
123                    if(data.html) {
124                        formArea.innerHTML = data.html
125                    }
126                    Modal.open(async () => {
127                        return onboardingForm
128                    }, null)
129                })
130            })
131            .catch(r => {
132                formArea.innerHTML = r.statusText || 'Error '+r.status
133            })
134            .finally(() => {
135                formLoader.style.display = 'none'
136            })
137    }
138
139    if(window.xgm.onboarding['recommender'] == 'show_window') {
140        loadData()
141    }
142
143    document.body.addEventListener('click', e => {
144        const button = e.target.closest('[data-interest-helper]')
145        if (!button) return
146        e.preventDefault()
147        loadData()
148    })
149
150    document.body.addEventListener('click', e => {
151        const button = e.target.closest('[data-interest-helper-select]')
152        if (!button) return
153        e.preventDefault()
154        let target = button.dataset.interestHelperSelect
155        button.dispatchEvent(new CustomEvent("onboarding.recommender.select", { bubbles: true, detail: target }))
156        fetch('/interest/onboarding/save/'+target, {
157            method: 'POST'
158        }).then(() => {
159            Modal.close(null)
160            if(target != 'later') {
161                firemsg(L('Спасибо!', 'Thank you!'))
162            }
163            if(target != 'nochange' && target != 'later') {
164                setTimeout(() => document.location.reload(), 750)   
165            }         
166        })
167    })
168}

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.