PageSourceSearch

https://www.newcivilengineer.com/wp-content/plugins/mbm-miso/Extensions/NCE/assets/miso-ask.js?ver=2.2.3

js newcivilengineer.com collected 2026-09-24 17:40:09 UTC 11,178 bytes, 235 lines download raw bytes

1/** NCE **/
2jQuery(document).ready(function ($) {
3    (window.misocmd || (window.misocmd = [])).push(async () => {
4        var brandCode = miso_script_vars.brandCode;
5        const TYPES = [
6            {
7                type: 'News',
8                alternatives: ['Breaking', 'Digital edition', 'Event', 'Exclude from homepages', 'ICE', 'ICE What\'s on', 'Multimedia', 'NCE100', 'News', 'Webinar']
9            },
10            {
11                type: 'Comment',
12                alternatives: ['Comment', 'Company profile', 'Industry View', 'Listicle', 'Your View']
13            },
14            {
15                type: 'In Depth',
16                alternatives: ['Analysis', 'Explainer', 'Feature', 'Inside Track']
17            },
18            {
19                type: 'Interview',
20                alternatives: ['Interview', 'Podcast']
21            },
22            {
23                type: 'Sponsored',
24                alternatives: ['Tech Showcase']
25            }
26        ];
27
28        const TYPE_INDEX = TYPES.reduce((map, { type, alternatives }) => {
29            map.set(type.toLowerCase(), type);
30            for (const alt of alternatives || []) {
31                map.set(alt.toLowerCase(), type);
32            }
33            return map;
34        }, new Map());
35        function getType(source) {
36            
37            const custom_attributes = source.custom_attributes || {};
38            //console.log(custom_attributes);
39            //const type = custom_attributes.type && custom_attributes.type[0] || undefined;
40            let typeValue = custom_attributes.type;
41            // Normalize typeValue to array
42            const typeArray = Array.isArray(typeValue) ? typeValue : (typeof typeValue === 'string' ? [typeValue] : []);
43            const type = typeArray[0]; // Use the first value, if available
44            return type && TYPE_INDEX.get(type.toLowerCase()) || 'Other';
45        }
46        function onCitationLink({ setAttribute, setTooltipHtml, escapeHtml }, { source, index }) {
47            if (source) {
48                // determine source article type by some logic
49                const type = getType(source);
50                if (type) {
51                    setAttribute('data-type', type);
52                    setTooltipHtml(`<span class="title">${escapeHtml(source.title)}</span><span class="type">${type}</span>`);
53                } else {
54                    setTooltipHtml(`<span class="title">${escapeHtml(source.title)}</span>`);
55                }
56            }
57        }
58        function escapeHtml(text) {
59            text = `${text}`;
60            return text && text
61                .replaceAll('&', '&amp;')
62                .replaceAll('<', '&lt;')
63                .replaceAll('>', '&gt;')
64                .replaceAll('"', '&quot;')
65                .replaceAll("'", '&#039;');
66        }
67        function renderArticleInfoBlock(layout, article) {
68            const { className } = layout;
69            const { title, snippet, description, created_at, updated_at, published_at } = article;
70            const date = published_at || created_at || updated_at;
71            const type = getType(article);
72            let content = '';
73            if (title) {
74                content += `<div class="${className}__item-title">${escapeHtml(title)}</div>`;
75            }
76            if (snippet) {
77                content += `<div class="${className}__item-snippet">${snippet}</div>`;
78            } else if (description) {
79                content += `<div class="${className}__item-desc">${escapeHtml(description)}</div>`;
80            }
81            content += `<div class="${className}__item-date-type">`;
82            if (date) {
83                content += `<div class="${className}__item-date">${new Date(date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })}</div>`;
84            }
85            if (type) {
86                content += `<div class="${className}__item-type" data-type="${type}">${type}</div>`;
87            }
88            content += `</div>`;
89            return `<div class="${className}__item-info-container">${content}</div>`;
90        }
91        function renderArticleIndexBlock(layout, article, { index }) {
92            const { className } = layout;
93            const i = index + 1;
94            const type = getType(article);
95            return `<div class="${className}__item-index-container"><span class="${className}__item-index miso-citation-index" data-index="${i}" data-type="${type}"></span></div>`;
96        }
97        function getElements(workflow) {
98            const parentQuestionId = workflow.parentQuestionId;
99            const comboElement = document.querySelector('miso-ask-combo');
100            const containerSelector = parentQuestionId ? `miso-ask[parent-question-id="${parentQuestionId}"]` : `miso-ask`;
101            // insert type definitions block if not already present
102            if (!comboElement.querySelector(`${containerSelector} .miso-type-defs`)) {
103                const questionElement = comboElement.querySelector(`${containerSelector} miso-question`);
104                questionElement.insertAdjacentHTML('afterend', `<div class="miso-type-defs"><ul></ul></div>`);
105            }
106            const typeDefsListElement = comboElement.querySelector(`${containerSelector} .miso-type-defs ul`);
107            return { comboElement, typeDefsListElement };
108        }
109        MisoClient.on('create', (client) => {
110            const context = client.ui.asks;
111            context.useLayouts({
112                answer: {
113                    onCitationLink,
114                },
115                sources: ['list', {
116                    templates: {
117                        infoBlock: renderArticleInfoBlock,
118                        indexBlock: renderArticleIndexBlock,
119                    },
120                }],
121            });
122            context.useApi({
123                source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'custom_attributes.type'],
124                "_pipeline_config": {
125                    "version": "v3",
126                },
127            });
128            context.on('loading', (event) => {
129                const { typeDefsListElement } = getElements(event.workflow);
130                // clean up type definitions block
131                typeDefsListElement.innerHTML = '';
132            });
133            context.on('data', (event) => {
134                const data = event.value;
135                if (!data) {
136                    return;
137                }
138                const sources = data.sources;
139                if (!sources || sources.length === 0) {
140                    return;
141                }
142                const { typeDefsListElement } = getElements(event.workflow);
143                // insert type definitions block if not already present
144                for (const type of sources.map(getType)) {
145                    // add to <ul> if not already present
146                    if (!type || typeDefsListElement.querySelector(`li[data-type="${type}"]`)) {
147                        continue;
148                    }
149                    typeDefsListElement.insertAdjacentHTML('beforeend', `<li data-type="${type}">${type}</li>`);
150                }
151            });
152            context.on('ready', (event) => {
153                const workflow = event.workflow;
154                const parentQuestionId = workflow.parentQuestionId;
155                const comboElement = document.querySelector('miso-ask-combo');
156                const containerSelector = parentQuestionId ? `miso-ask[parent-question-id="${parentQuestionId}"]` : `miso-ask`;
157                if (comboElement.querySelector(`${containerSelector} .miso-notice`)) {
158                    return;
159                }
160                const feedbackElement = comboElement.querySelector(`${containerSelector} miso-feedback`);
161                feedbackElement.insertAdjacentHTML('afterend', `
162			          <hr>
163			          <div class="miso-notice">
164			            <div class="miso-notice-icon"><i>!</i></div>
165			            <div class="miso-notice-content">As a note of caution, while the Artificial Intelligence engine is constantly learning, we cannot guarantee these Ask NCE AI-generated answers will always be without error. Please use your own judgment on the suitability of the answer and review the articles the answer is based on. Any action taken based on the answer is at your own risk.</div>
166			          </div>
167			        `);
168            });
169        });
170
171        // --- START: MutationObserver to remove reasoning/thought boxes ---
172        const observer = new MutationObserver((mutations) => {
173            mutations.forEach(() => {
174                document.querySelectorAll('.miso-ask-combo__reasoning-container')
175                    .forEach(el => el.remove());
176            });
177        });
178        observer.observe(document.body, { childList: true, subtree: true });
179        // --- END: MutationObserver ---
180
181        var brand_name = miso_script_vars.brandName;
182        var miso_user_id = miso_script_vars.miso_user_id;
183        var miso_user_hash = miso_script_vars.user_hash;
184        let b_code = brandCode.toLowerCase();
185        var fq_text = 'brand:"' + brand_name + '"';
186
187        if (miso_script_vars.hasOwnProperty('ex_categories') && miso_script_vars.ex_categories != "") {
188            var str_cats = miso_script_vars.ex_categories;
189            var c_arr = str_cats.split(',');
190            var str = '';
191            for (var i = 0; i < c_arr.length; i++) {
192                str += '"' + c_arr[i] + '"';
193                if (i < c_arr.length - 1) {
194                    str += ' or ';
195                }
196            }
197            fq_text += ' AND (NOT(categories:(' + str + ')))';
198        }
199
200        if (miso_script_vars.hasOwnProperty('custom_attributes') && miso_script_vars.custom_attributes != "") {
201            var custom_attributes = formatKeyValuePairs(miso_script_vars, 'custom_attributes');
202            fq_text += ' AND (NOT(' + custom_attributes + '))';
203        }
204
205
206        // Function to get key-value pairs from an object 
207        function formatKeyValuePairs(data, groupKey) {
208            // Get the specific group object based on the provided key
209            const groupObj = data[groupKey];
210
211            if (!groupObj || typeof groupObj !== 'object') {
212                return '';
213            }
214
215            let formattedPairs = [];
216
217            for (const [key, value] of Object.entries(groupObj)) {
218                formattedPairs.push(`${groupKey}.${key}:(${value.map(v => `"${v}"`).join(' or ')})`);
219            }
220
221            return formattedPairs.join(' or ');
222        }
223
224        const combo = MisoClient.ui.combo.ask;
225        combo.config({
226            api: {
227                fq: fq_text,
228                user_id: miso_user_id,
229                user_hash: miso_user_hash,
230                source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'custom_attributes.type'],
231            },
232        });
233        combo.start();
234    });
vendor: 3 bytes, line 235
235});

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.