sr-npm 3.1.25 → 3.1.26
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/backend/fetchPositionsFromSRAPI.js +85 -144
- package/package.json +1 -1
- package/pages/careersMultiBoxesPage.js +17 -11
|
@@ -119,8 +119,10 @@ async function htmlRichContentConverter(sections,richContentConverterToken) {
|
|
|
119
119
|
const richContentObject = {}
|
|
120
120
|
for (const [sectionTitle, sectionData] of Object.entries(sections)) {
|
|
121
121
|
if (sectionData.text) {
|
|
122
|
+
// Strip span tags but keep their content , since <span> tags paragraphs are deleted by the converter
|
|
123
|
+
const cleanedHtml = sectionData.text.replace(/<\/?span[^>]*>/gi, '');
|
|
122
124
|
const raw = JSON.stringify({
|
|
123
|
-
content:
|
|
125
|
+
content: cleanedHtml,
|
|
124
126
|
});
|
|
125
127
|
const requestOptions = {
|
|
126
128
|
method: 'post',
|
|
@@ -137,7 +139,10 @@ async function htmlRichContentConverter(sections,richContentConverterToken) {
|
|
|
137
139
|
);
|
|
138
140
|
if (response.ok) {
|
|
139
141
|
const data = await response.json();
|
|
140
|
-
|
|
142
|
+
// Fix list items with nested paragraphs (causes line breaks after bold text)
|
|
143
|
+
const flattenedContent = flattenListItems(data.richContent.richContent);
|
|
144
|
+
// const richContentWithSpacing=addSpacingToRichContent(cleanedHtml,flattenedContent);
|
|
145
|
+
const richContentWithSpacing=addEmptyParagraphsBetweenConsecutive(cleanedHtml,flattenedContent);
|
|
141
146
|
richContentObject[sectionTitle] = richContentWithSpacing
|
|
142
147
|
}
|
|
143
148
|
else {
|
|
@@ -148,148 +153,6 @@ async function htmlRichContentConverter(sections,richContentConverterToken) {
|
|
|
148
153
|
return richContentObject;
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
//Adds empty paragraph nodes between sections in rich content
|
|
152
|
-
// to create visual spacing that the Wix RICOS converter strips out
|
|
153
|
-
function addSpacingToRichContent(html, richContent) {
|
|
154
|
-
if (!richContent || !richContent.nodes) {
|
|
155
|
-
return richContent;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Extract paragraph texts from HTML that end with  
|
|
159
|
-
const htmlParagraphsWithSpace = [];
|
|
160
|
-
// Extract paragraphs with <br> tags
|
|
161
|
-
const htmlParagraphsWithBr = new Map(); // text -> array of parts split by <br>
|
|
162
|
-
|
|
163
|
-
const pTagRegex = /<p>(.*?)<\/p>/gi;
|
|
164
|
-
let match;
|
|
165
|
-
|
|
166
|
-
while ((match = pTagRegex.exec(html)) !== null) {
|
|
167
|
-
const content = match[1];
|
|
168
|
-
|
|
169
|
-
// Check if this paragraph ends with   (before closing tags)
|
|
170
|
-
if (content.includes(' ')) {
|
|
171
|
-
const textOnly = content.replace(/<[^>]+>/g, '').trim();
|
|
172
|
-
htmlParagraphsWithSpace.push(textOnly);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Check if this paragraph contains <br> tags
|
|
176
|
-
if (content.includes('<br>') || content.includes('<br/>') || content.includes('<br />')) {
|
|
177
|
-
// Split by <br> tags and extract text parts
|
|
178
|
-
const parts = content.split(/<br\s*\/?>/i).map(part =>
|
|
179
|
-
part.replace(/<[^>]+>/g, '').trim()
|
|
180
|
-
).filter(part => part.length > 0);
|
|
181
|
-
|
|
182
|
-
if (parts.length > 1) {
|
|
183
|
-
// Store the parts for this paragraph
|
|
184
|
-
const fullText = content.replace(/<[^>]+>/g, '').replace(/\s+/g, '').trim();
|
|
185
|
-
htmlParagraphsWithBr.set(fullText, parts);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const nodes = richContent.nodes;
|
|
191
|
-
const newNodes = [];
|
|
192
|
-
let nodeIdCounter = 0;
|
|
193
|
-
|
|
194
|
-
// Check if a paragraph is bold (has BOLD decoration)
|
|
195
|
-
const isBoldParagraph = (node) => {
|
|
196
|
-
if (node.type !== 'PARAGRAPH') return false;
|
|
197
|
-
const decorations = node.nodes?.[0]?.textData?.decorations || [];
|
|
198
|
-
return decorations.some(d => d.type === 'BOLD');
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// Check if a paragraph node's text matches one with   in HTML
|
|
202
|
-
const needsSpacingAfter = (node) => {
|
|
203
|
-
if (node.type !== 'PARAGRAPH') return false;
|
|
204
|
-
|
|
205
|
-
// Add spacing after bold paragraphs
|
|
206
|
-
if (isBoldParagraph(node)) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const text = node.nodes?.[0]?.textData?.text || '';
|
|
211
|
-
const trimmedText = text.trim();
|
|
212
|
-
|
|
213
|
-
// Check if this text matches any HTML paragraph that had  
|
|
214
|
-
return htmlParagraphsWithSpace.some(htmlText => {
|
|
215
|
-
const cleanHtml = htmlText.replace(/ /g, ' ').trim();
|
|
216
|
-
return trimmedText.includes(cleanHtml) || cleanHtml.includes(trimmedText);
|
|
217
|
-
});
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// Check if a paragraph contains text that should be split by <br>
|
|
221
|
-
const shouldSplitByBr = (node) => {
|
|
222
|
-
if (node.type !== 'PARAGRAPH') return null;
|
|
223
|
-
|
|
224
|
-
const text = node.nodes?.[0]?.textData?.text || '';
|
|
225
|
-
const normalizedText = text.replace(/\s+/g, '').trim();
|
|
226
|
-
|
|
227
|
-
// Find matching HTML paragraph with <br>
|
|
228
|
-
for (const [htmlText, parts] of htmlParagraphsWithBr.entries()) {
|
|
229
|
-
if (normalizedText.includes(htmlText) || htmlText.includes(normalizedText)) {
|
|
230
|
-
return parts;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return null;
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Add spacing after bulleted lists
|
|
237
|
-
const isListEnd = (currentNode, nextNode) => {
|
|
238
|
-
return currentNode.type === 'BULLETED_LIST' &&
|
|
239
|
-
nextNode &&
|
|
240
|
-
nextNode.type === 'PARAGRAPH';
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
244
|
-
const currentNode = nodes[i];
|
|
245
|
-
const nextNode = nodes[i + 1];
|
|
246
|
-
|
|
247
|
-
// Check if this paragraph should be split by <br>
|
|
248
|
-
const brParts = shouldSplitByBr(currentNode);
|
|
249
|
-
if (brParts && brParts.length > 1) {
|
|
250
|
-
// Split into multiple paragraphs and add spacing between them
|
|
251
|
-
const decorations = currentNode.nodes?.[0]?.textData?.decorations || [];
|
|
252
|
-
|
|
253
|
-
brParts.forEach((part, idx) => {
|
|
254
|
-
newNodes.push({
|
|
255
|
-
...currentNode,
|
|
256
|
-
id: `${currentNode.id}_split_${idx}`,
|
|
257
|
-
nodes: [{
|
|
258
|
-
type: "TEXT",
|
|
259
|
-
id: "",
|
|
260
|
-
nodes: [],
|
|
261
|
-
textData: {
|
|
262
|
-
text: part,
|
|
263
|
-
decorations: decorations
|
|
264
|
-
}
|
|
265
|
-
}]
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Add empty paragraph after each split part except the last
|
|
269
|
-
if (idx < brParts.length - 1) {
|
|
270
|
-
newNodes.push(createEmptyParagraph(`empty_br_${nodeIdCounter++}`));
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// Add spacing after the split paragraphs if there's a next node
|
|
275
|
-
if (nextNode) {
|
|
276
|
-
newNodes.push(createEmptyParagraph(`empty_${nodeIdCounter++}`));
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
newNodes.push(currentNode);
|
|
280
|
-
|
|
281
|
-
// Add empty paragraph ONLY after paragraphs with   or after lists
|
|
282
|
-
if ((needsSpacingAfter(currentNode) || isListEnd(currentNode, nextNode)) && nextNode) {
|
|
283
|
-
newNodes.push(createEmptyParagraph(`empty_${nodeIdCounter++}`));
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
...richContent,
|
|
290
|
-
nodes: newNodes
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
156
|
|
|
294
157
|
function createEmptyParagraph(id) {
|
|
295
158
|
return {
|
|
@@ -314,6 +177,84 @@ function createEmptyParagraph(id) {
|
|
|
314
177
|
};
|
|
315
178
|
}
|
|
316
179
|
|
|
180
|
+
// Flattens LIST_ITEM nodes by removing nested PARAGRAPH wrappers
|
|
181
|
+
// Fixes Wix converter bug where bold text + regular text in <li> creates line breaks
|
|
182
|
+
function flattenListItems(richContent) {
|
|
183
|
+
if (!richContent || !richContent.nodes) return richContent;
|
|
184
|
+
|
|
185
|
+
const processNode = (node) => {
|
|
186
|
+
if (node.type === 'BULLETED_LIST' || node.type === 'ORDERED_LIST') {
|
|
187
|
+
return {
|
|
188
|
+
...node,
|
|
189
|
+
nodes: node.nodes.map(listItem => {
|
|
190
|
+
if (listItem.type !== 'LIST_ITEM') return listItem;
|
|
191
|
+
|
|
192
|
+
// Flatten: extract TEXT nodes from nested PARAGRAPHs
|
|
193
|
+
const flattenedChildren = [];
|
|
194
|
+
for (const child of listItem.nodes) {
|
|
195
|
+
if (child.type === 'PARAGRAPH' && child.nodes) {
|
|
196
|
+
// Pull TEXT nodes out of the PARAGRAPH
|
|
197
|
+
flattenedChildren.push(...child.nodes);
|
|
198
|
+
} else {
|
|
199
|
+
flattenedChildren.push(child);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { ...listItem, nodes: flattenedChildren };
|
|
204
|
+
})
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return node;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
...richContent,
|
|
212
|
+
nodes: richContent.nodes.map(processNode)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Adds empty paragraph nodes between consecutive paragraphs and before lists
|
|
217
|
+
function addEmptyParagraphsBetweenConsecutive(html, richContent) {
|
|
218
|
+
if (!richContent || !richContent.nodes) return richContent;
|
|
219
|
+
const hasConsecutiveParagraphs = /<\/p>\s*<p/i.test(html);
|
|
220
|
+
const hasParagraphBeforeList = /<\/p>\s*<ul/i.test(html);
|
|
221
|
+
const hasParagraphAfterList = /<\/ul>\s*<p/i.test(html);
|
|
222
|
+
|
|
223
|
+
if (!hasConsecutiveParagraphs && !hasParagraphBeforeList && !hasParagraphAfterList) return richContent;
|
|
224
|
+
|
|
225
|
+
const nodes = richContent.nodes;
|
|
226
|
+
const newNodes = [];
|
|
227
|
+
let nodeIdCounter = 0;
|
|
228
|
+
|
|
229
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
230
|
+
const currentNode = nodes[i];
|
|
231
|
+
const nextNode = nodes[i + 1];
|
|
232
|
+
|
|
233
|
+
newNodes.push(currentNode);
|
|
234
|
+
|
|
235
|
+
if (currentNode.type === 'PARAGRAPH' && nextNode) {
|
|
236
|
+
// Add empty paragraph between consecutive paragraphs
|
|
237
|
+
if (hasConsecutiveParagraphs && nextNode.type === 'PARAGRAPH') {
|
|
238
|
+
newNodes.push(createEmptyParagraph(`empty_consecutive_${nodeIdCounter++}`));
|
|
239
|
+
}
|
|
240
|
+
// Add empty paragraph before list
|
|
241
|
+
if (hasParagraphBeforeList && nextNode.type === 'BULLETED_LIST') {
|
|
242
|
+
newNodes.push(createEmptyParagraph(`empty_before_list_${nodeIdCounter++}`));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Add empty paragraph after list
|
|
247
|
+
if (hasParagraphAfterList && currentNode.type === 'BULLETED_LIST' && nextNode && nextNode.type === 'PARAGRAPH') {
|
|
248
|
+
newNodes.push(createEmptyParagraph(`empty_after_list_${nodeIdCounter++}`));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
...richContent,
|
|
254
|
+
nodes: newNodes
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
317
258
|
|
|
318
259
|
|
|
319
260
|
|
package/package.json
CHANGED
|
@@ -30,6 +30,7 @@ const countsByFieldId = new Map(); // fieldId -> {valueId: count} map of counts
|
|
|
30
30
|
let allfields=[] // all fields in the database
|
|
31
31
|
let alljobs=[] // all jobs in the database
|
|
32
32
|
let allvaluesobjects=[] // all values in the database
|
|
33
|
+
let cities=[] // all cities in the database
|
|
33
34
|
let valueToJobs={} // valueId -> array of jobIds
|
|
34
35
|
let currentJobs=[] // current jobs that are displayed in the jobs repeater
|
|
35
36
|
let allsecondarySearchJobs=[] // secondary search results that are displayed in the jobs repeater
|
|
@@ -83,10 +84,10 @@ async function handleBackAndForth(_$w){
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
async function clearAll(_$w,urlOnChange=false) {
|
|
86
|
-
if(selectedByField.size>0 || _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SECONDARY_SEARCH_INPUT).value || _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PRIMARY_SEARCH_INPUT).value) {
|
|
87
87
|
|
|
88
88
|
for(const field of allfields) {
|
|
89
89
|
_$w(`#${FiltersIds[field.title]}CheckBox`).selectedIndices = [];
|
|
90
|
+
_$w(`#${FiltersIds[field.title]}input`).value='';
|
|
90
91
|
}
|
|
91
92
|
selectedByField.clear();
|
|
92
93
|
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.SECONDARY_SEARCH_INPUT).value='';
|
|
@@ -102,7 +103,6 @@ async function clearAll(_$w,urlOnChange=false) {
|
|
|
102
103
|
await updateJobsAndNumbersAndFilters(_$w,true);
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function handleFilterInMobile(_$w) {
|
|
@@ -364,7 +364,7 @@ async function loadJobsRepeater(_$w) {
|
|
|
364
364
|
async function loadFilters(_$w) {
|
|
365
365
|
try {
|
|
366
366
|
// 1) Load all categories (fields)
|
|
367
|
-
|
|
367
|
+
cities = await getAllRecords(COLLECTIONS.CITIES);
|
|
368
368
|
for(const city of cities) {
|
|
369
369
|
valueToJobs[city._id] = city.jobIds;
|
|
370
370
|
}
|
|
@@ -480,8 +480,13 @@ function getValueFromValueId(valueIds, value) {
|
|
|
480
480
|
{
|
|
481
481
|
const selectedFieldId=Array.from( selectedByField.keys() )[0]
|
|
482
482
|
if(selectedFieldId===fieldId) {
|
|
483
|
+
if(selectedFieldId==="Location") {
|
|
484
|
+
countsMap = new Map(cities.map(city=>[city._id, city.count]));
|
|
485
|
+
}
|
|
486
|
+
else{
|
|
483
487
|
const relevantFields=allvaluesobjects.filter(val=>val.customField===selectedFieldId)
|
|
484
488
|
countsMap = new Map(relevantFields.map(val=>[val.valueId, val.count]));
|
|
489
|
+
}
|
|
485
490
|
considerAllJobs=false;
|
|
486
491
|
}
|
|
487
492
|
}
|
|
@@ -518,11 +523,7 @@ function getValueFromValueId(valueIds, value) {
|
|
|
518
523
|
|
|
519
524
|
// Sort alphabetically by label
|
|
520
525
|
filtered.sort((a, b) => (a.label || '').localeCompare(b.label || ''));
|
|
521
|
-
// Preserve currently selected values that are still visible
|
|
522
|
-
// let prevSelected=[]
|
|
523
|
-
// clearAll? prevSelected=[]:prevSelected= _$w(`#${FiltersIds[fieldTitle]}CheckBox`).value;
|
|
524
526
|
const visibleSet = new Set(filtered.map(o => o.value));
|
|
525
|
-
//const preserved = prevSelected.filter(v => visibleSet.has(v));
|
|
526
527
|
if(filtered.length===0) {
|
|
527
528
|
_$w(`#${FiltersIds[fieldTitle]}MultiBox`).changeState(`${FiltersIds[fieldTitle]}NoResults`);
|
|
528
529
|
}
|
|
@@ -591,11 +592,14 @@ function getValueFromValueId(valueIds, value) {
|
|
|
591
592
|
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationTotalCountText).text = secondarySearchIsFilled? Math.ceil(currentSecondarySearchJobs.length/pagination.pageSize).toString():Math.ceil(currentJobs.length/pagination.pageSize).toString();
|
|
592
593
|
if(jobsFirstPage.length===0) {
|
|
593
594
|
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("noJobs");
|
|
595
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = "0";
|
|
596
|
+
pagination.currentPage=0;
|
|
594
597
|
}
|
|
595
598
|
else{
|
|
596
599
|
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("searchResult");
|
|
600
|
+
pagination.currentPage=1;
|
|
597
601
|
}
|
|
598
|
-
|
|
602
|
+
|
|
599
603
|
handlePaginationButtons(_$w);
|
|
600
604
|
}
|
|
601
605
|
|
|
@@ -603,7 +607,7 @@ function handlePaginationButtons(_$w)
|
|
|
603
607
|
{
|
|
604
608
|
handlePageUrlParam();
|
|
605
609
|
|
|
606
|
-
pagination.currentPage===1? _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_PREVIOUS).disable():_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_PREVIOUS).enable();
|
|
610
|
+
pagination.currentPage===1 || pagination.currentPage===0? _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_PREVIOUS).disable():_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_PREVIOUS).enable();
|
|
607
611
|
if(secondarySearchIsFilled) {
|
|
608
612
|
if(currentSecondarySearchJobs.length===0) {
|
|
609
613
|
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.PAGE_BUTTON_NEXT).disable();
|
|
@@ -622,9 +626,8 @@ function handlePaginationButtons(_$w)
|
|
|
622
626
|
|
|
623
627
|
function handlePageUrlParam() {
|
|
624
628
|
ActivateURLOnchange=false;
|
|
625
|
-
if(pagination.currentPage==1)
|
|
629
|
+
if(pagination.currentPage==1 || pagination.currentPage==0)
|
|
626
630
|
{
|
|
627
|
-
|
|
628
631
|
queryParams.remove(["page"]);
|
|
629
632
|
}
|
|
630
633
|
else{
|
|
@@ -704,6 +707,8 @@ async function secondarySearch(_$w,query) {
|
|
|
704
707
|
|
|
705
708
|
if(jobsFirstPage.length===0) {
|
|
706
709
|
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("noJobs");
|
|
710
|
+
_$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.paginationCurrentText).text = "0";
|
|
711
|
+
pagination.currentPage=0;
|
|
707
712
|
}
|
|
708
713
|
else{
|
|
709
714
|
await _$w(CAREERS_MULTI_BOXES_PAGE_CONSTS.JOBS_MULTI_STATE_BOX).changeState("searchResult");
|
|
@@ -730,6 +735,7 @@ async function secondarySearch(_$w,query) {
|
|
|
730
735
|
}
|
|
731
736
|
|
|
732
737
|
|
|
738
|
+
|
|
733
739
|
module.exports = {
|
|
734
740
|
careersMultiBoxesPageOnReady,
|
|
735
741
|
secondarySearch
|