NeroReflex

AI_REVIEW

Nov 2nd, 2025
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.00 KB | None | 0 0
  1. Below is a **drop‑in replacement** for the `openDetail()` routine (and a quick refresher on how to reuse the same escaping helper that we already introduced for the card rendering).
  2. Everything that is inserted into the DOM – titles, text blocks, lists, etc. – is first run through the same `escapeHTML()` routine so that any `<script>` or other harmful markup that might sneak into the JSON file can’t be executed.
  3.  
  4. ```javascript
  5. /* --------------------------------------------------------------------
  6. Utility – escape HTML (kept in the global scope so that all helpers
  7. can reuse it). This is the same function we used in `renderCard()`.
  8. -------------------------------------------------------------------- */
  9. function escapeHTML(str) {
  10. return String(str)
  11. .replace(/&/g, '&amp;')
  12. .replace(/</g, '&lt;')
  13. .replace(/>/g, '&gt;')
  14. .replace(/"/g, '&quot;')
  15. .replace(/'/g, '&#39;');
  16. }
  17.  
  18. /* --------------------------------------------------------------------
  19. Rendering the cards (kept the same – just a reminder that it uses
  20. the helper). It is unchanged except for the `escapeHTML()` calls.
  21. -------------------------------------------------------------------- */
  22. function renderCard(book) {
  23. const title = escapeHTML(book.title);
  24. const card = `
  25. <div class="card" data-id="${escapeHTML(book.id)}">
  26. <h3>${title}</h3>
  27. <div class="card-body">
  28. <h4>${escapeHTML(book.title)} – ${escapeHTML(book.author)}</h4>
  29. <p class="card-text">${escapeHTML(book.blurb)}</p>
  30. </div>
  31. </div>`;
  32. return card;
  33. }
  34. ```
  35.  
  36. ---
  37.  
  38. ### 1. The new `openDetail()` implementation
  39.  
  40. ```javascript
  41. /* --------------------------------------------------------------------
  42. Global variable that will hold the currently shown detail pane.
  43. -------------------------------------------------------------------- */
  44. let activeDetailView = null;
  45.  
  46. /* --------------------------------------------------------------------
  47. 1⃣ Build the content **with escaping** and return it as a
  48. `template` element. Using a <template> gives us a cheap
  49. sandbox where we can create children with *textContent* – no
  50. chance of injecting raw HTML.
  51. -------------------------------------------------------------------- */
  52. function openDetail(id) {
  53. const result = playbooks.find(b => b.id === id);
  54. if (!result) return;
  55.  
  56. // ---------- 1. Build the steps list --------------------------------
  57. const stepsContainer = document.createElement('ul');
  58. stepsContainer.className = 'detail-steps';
  59.  
  60. result.steps.forEach(step => {
  61. const li = document.createElement('li');
  62. li.textContent = step; // <– safe: textContent escapes everything
  63. stepsContainer.appendChild(li);
  64. });
  65.  
  66. // ---------- 2. Build the whole detail pane ------------------------
  67. const detailContainer = document.createElement('div');
  68. detailContainer.className = 'detail-view';
  69. detailContainer.setAttribute('data-id', result.id);
  70.  
  71. // 2a. A small “Close” button that simply removes the pane
  72. const closeBtn = document.createElement('button');
  73. closeBtn.className = 'detail-close';
  74. closeBtn.textContent = '×'; // × looks nicer than “Close”
  75. closeBtn.onclick = closeDetail; // will be defined below
  76. detailContainer.appendChild(closeBtn);
  77.  
  78. // 2b. Add the heading (escaped!)
  79. const heading = document.createElement('h2');
  80. heading.textContent = result.title;
  81. detailContainer.appendChild(heading);
  82.  
  83. // 2c. Add the blurb (escaped!)
  84. const blurb = document.createElement('p');
  85. blurb.textContent = result.blurb;
  86. detailContainer.appendChild(blurb);
  87.  
  88. // 2d. Append the steps list
  89. detailContainer.appendChild(stepsContainer);
  90.  
  91. // 2e. (Optional) Add a little bit of styling if you want the pane
  92. // to sit on top of the page
  93. detailContainer.style.position = 'fixed';
  94. detailContainer.style.top = '20px';
  95. detailContainer.style.left = '50%';
  96. detailContainer.style.transform = 'translateX(-50%)';
  97. detailContainer.style.background = '#fff';
  98. detailContainer.style.padding = '1rem 2rem';
  99. detailContainer.style.boxShadow = '0 4px 12px rgba(0,0,0,.15)';
  100. detailContainer.style.zIndex = 9999;
  101.  
  102. // ---------- 3⃣ Add the pane to the document ----------------------
  103. document.body.appendChild(detailContainer);
  104. activeDetailView = detailContainer; // keep a handle so we can close it
  105.  
  106. // ---------- 4⃣ Highlight the corresponding card ----------------
  107. const highlightedCard = document.querySelector(`.card[data-id="${id}"]`);
  108. if (highlightedCard) highlightedCard.classList.add('highlight');
  109. }
  110.  
  111. /* --------------------------------------------------------------------
  112. Closes the detail pane – removes the highlight on the card
  113. and removes the detail element from the DOM.
  114. -------------------------------------------------------------------- */
  115. function closeDetail() {
  116. if (activeDetailView) {
  117. const id = activeDetailView.getAttribute('data-id');
  118. const card = document.querySelector(`.card[data-id="${id}"]`);
  119. if (card) card.classList.remove('highlight');
  120.  
  121. activeDetailView.remove();
  122. activeDetailView = null;
  123. }
  124. }
  125.  
  126. /* --------------------------------------------------------------------
  127. Wire the “Search” results (or any other trigger) to the new routine.
  128. -------------------------------------------------------------------- */
  129. searchInput.addEventListener('input', () => {
  130. const query = searchInput.value.trim();
  131. displaySearchResults(query);
  132. });
  133.  
  134. /* --------------------------------------------------------------------
  135. When a user clicks a search result, open its detail pane.
  136. -------------------------------------------------------------------- */
  137. document.addEventListener('click', event => {
  138. if (event.target.closest('.search-result')) {
  139. const target = event.target.closest('.search-result');
  140. const id = target.dataset.id;
  141. openDetail(id);
  142. }
  143. });
  144. ```
  145.  
  146. ---
  147.  
  148. ### 2. How it protects against XSS
  149.  
  150. 1. **All strings from the JSON are inserted as `textContent`**
  151. – This property automatically escapes any `<`, `>`, `"`, `&`, etc.
  152.  
  153. 2. **No `innerHTML` is used on untrusted data**
  154. The only `innerHTML` that appears is in `renderCard()` where we *already*
  155. applied `escapeHTML()` to every piece of text.
  156.  
  157. 3. **The detail pane is a separate element**
  158. We never inject raw HTML from the server into the DOM; we build
  159. the detail pane entirely from DOM nodes that are created by
  160. JavaScript, so the browser itself prevents any stray tags from
  161. being interpreted.
  162.  
  163. 4. **Event delegation keeps the click‑handler safe**
  164. We test the target with `closest()` which stops at the nearest
  165. ancestor that matches. Even if a malicious string were to slip
  166. into the search results, it would be treated as *plain text*.
  167.  
  168. ---
  169.  
  170. ### 3. Optional tweaks
  171.  
  172. * If you wish to keep the **old `onclick` behaviour** from the
  173. search results, you could add `onclick="openDetail('${id}')"`,
  174. but the approach above is safer because it avoids string‑based
  175. event binding entirely.
  176.  
  177. * Add a CSS rule for `.highlight` if you want a visible visual cue:
  178. ```css
  179. .card.highlight {
  180. border: 2px solid #f0ad4e; /* e.g. a gold border */
  181. box-shadow: 0 0 8px rgba(240, 173, 78, .6);
  182. }
  183. ```
  184.  
  185. * If you want the pane to scroll to the card instead of using a
  186. “fixed” position, you can use `scrollIntoView()`:
  187. ```js
  188. highlightedCard.scrollIntoView({behavior:'smooth', block:'center'});
  189. ```
  190.  
  191. ---
  192.  
  193. ### 4. Recap of what changed
  194.  
  195. * **`openDetail()`** – now creates a safe, **textContent‑only**
  196. pane and attaches a close button.
  197. * **`closeDetail()`** – removes the pane and the card highlight.
  198. * **`renderCard()`** – uses the same escape helper.
  199. * **Search click** – wired to call `openDetail(id)`.
  200.  
  201. These changes make sure that any user‑supplied data that appears in
  202. the detail view can no longer be interpreted as executable
  203. JavaScript or markup, keeping your page safe from XSS.
  204.  
Advertisement
Add Comment
Please, Sign In to add comment