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.
@@ -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: sectionData.text,
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
- const richContentWithSpacing=addSpacingToRichContent(sectionData.text,data.richContent.richContent);
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 &#xa0;
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 &#xa0; (before closing tags)
170
- if (content.includes('&#xa0;')) {
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 &#xa0; 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 &#xa0;
214
- return htmlParagraphsWithSpace.some(htmlText => {
215
- const cleanHtml = htmlText.replace(/&#xa0;/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 &#xa0; 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sr-npm",
3
- "version": "3.1.25",
3
+ "version": "3.1.26",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- const cities = await getAllRecords(COLLECTIONS.CITIES);
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
- pagination.currentPage=1;
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