-
Notifications
You must be signed in to change notification settings - Fork 270
DOC-5922 Markdown checklist format #2351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c5783d9
cf443e9
6e9b129
dedcff9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| {{- $id := .Attributes.id | default "checklist" -}} | ||
| <pre class="checklist-source" data-checklist-id="{{ $id }}">{{ .Inner | htmlEscape | safeHTML }}</pre> | ||
| {{ .Page.Store.Set "hasChecklist" true }} | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,159 @@ | ||||||
| document.addEventListener('DOMContentLoaded', () => { | ||||||
| // Find all checklist code blocks | ||||||
| const checklists = document.querySelectorAll('pre.checklist-source'); | ||||||
| console.log('Found', checklists.length, 'checklist(s)'); | ||||||
|
|
||||||
| checklists.forEach(pre => { | ||||||
| const checklistId = pre.getAttribute('data-checklist-id'); | ||||||
| const markdownContent = pre.textContent; | ||||||
| console.log('Processing checklist:', checklistId); | ||||||
|
|
||||||
| // Parse markdown and create interactive checklist | ||||||
| createChecklistFromMarkdown(markdownContent, checklistId, pre); | ||||||
| }); | ||||||
| }); | ||||||
|
|
||||||
| function createChecklistFromMarkdown(markdown, formId, preElement) { | ||||||
| const lines = markdown.split('\n'); | ||||||
| const items = []; | ||||||
|
|
||||||
| // Parse checklist items from markdown | ||||||
| lines.forEach(line => { | ||||||
| const trimmed = line.trim(); | ||||||
| if (trimmed.match(/^- \[[\sx]\]/)) { | ||||||
| items.push(trimmed); | ||||||
| } | ||||||
| }); | ||||||
|
|
||||||
| if (items.length === 0) return; | ||||||
|
|
||||||
| // Create form | ||||||
| const form = document.createElement('form'); | ||||||
| form.id = formId; | ||||||
|
|
||||||
| const ul = document.createElement('ul'); | ||||||
| ul.style.listStyleType = 'none'; | ||||||
| ul.style.paddingLeft = '0px'; | ||||||
|
|
||||||
| // Parse each item | ||||||
| items.forEach(item => { | ||||||
| const li = document.createElement('li'); | ||||||
|
|
||||||
| // Create select dropdown | ||||||
| const select = document.createElement('select'); | ||||||
| select.onchange = () => clChange(formId); | ||||||
|
|
||||||
| const options = [ | ||||||
| { value: 'R', label: '❌' }, | ||||||
| { value: 'G', label: '✅' }, | ||||||
| { value: 'A', label: '🔍' }, | ||||||
| { value: 'X', label: '∅' } | ||||||
| ]; | ||||||
|
|
||||||
| options.forEach(opt => { | ||||||
| const option = document.createElement('option'); | ||||||
| option.value = opt.value; | ||||||
| option.innerHTML = opt.label; | ||||||
| select.appendChild(option); | ||||||
| }); | ||||||
|
|
||||||
| li.appendChild(select); | ||||||
|
|
||||||
| // Parse link and text from markdown | ||||||
| // Format: - [ ] [text](#anchor) or - [ ] text | ||||||
| const linkMatch = item.match(/\[([^\]]+)\]\(([^\)]+)\)/); | ||||||
| if (linkMatch) { | ||||||
| const a = document.createElement('a'); | ||||||
| a.href = linkMatch[2]; | ||||||
| a.textContent = linkMatch[1]; | ||||||
| li.appendChild(a); | ||||||
| } else { | ||||||
| // Just text after the checkbox | ||||||
| const text = item.replace(/^- \[[\sx]\]\s*/, ''); | ||||||
| li.appendChild(document.createTextNode(text)); | ||||||
| } | ||||||
|
|
||||||
| ul.appendChild(li); | ||||||
| }); | ||||||
|
|
||||||
| form.appendChild(ul); | ||||||
|
|
||||||
| // Add counters | ||||||
| const countersDiv = document.createElement('div'); | ||||||
| countersDiv.innerHTML = ` | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security control: Static Code Analysis Semgrep Pro Javascript.Browser.Security.Insecure-Document-Method.Insecure-Document-Method User controlled data in methods like Severity: HIGH Fix suggestion: This fix suggestion was generated by Jit. Please note that the suggestion might not always fit every use case. It is highly recommended that you check and review it before merging. Suggestion guidelines This remediation replaces the usage of insecure methods like 'innerHTML', 'outerHTML' or 'document.write' with a safer alternative, 'textContent'. The code will now use 'textContent' to safely set or update the content without putting your application at risk of XSS attacks.
Suggested change
Why should you fix this issue? Jit Bot commands and options (e.g., ignore issue)You can trigger Jit actions by commenting on this PR review:
|
||||||
| <label for="${formId}-gcount">✅ = </label> | ||||||
| <output name="gcount" id="${formId}-gcount">0</output>/<output id="${formId}-gtotal">0</output>, | ||||||
| <label for="${formId}-rcount">❌ = </label> | ||||||
| <output name="rcount" id="${formId}-rcount">0</output>/<output id="${formId}-rtotal">0</output>, | ||||||
| <label for="${formId}-acount">🔍 = </label> | ||||||
| <output name="acount" id="${formId}-acount">0</output>/<output id="${formId}-atotal">0</output> | ||||||
| <br/> | ||||||
| (<label for="${formId}-xcount">∅ = </label> | ||||||
| <output name="xcount" id="${formId}-xcount">0</output>) | ||||||
| `; | ||||||
| form.appendChild(countersDiv); | ||||||
|
|
||||||
| // Replace the entire <pre> element with the interactive form | ||||||
| preElement.replaceWith(form); | ||||||
|
|
||||||
| // Initialize | ||||||
| let itemString = localStorage.getItem(formId); | ||||||
| if (itemString) { | ||||||
| setCLItemsFromString(formId, itemString); | ||||||
| } else { | ||||||
| clChange(formId); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| function getStringFromCLItems(formId) { | ||||||
| let result = ""; | ||||||
| let form = document.getElementById(formId); | ||||||
| let listItems = form.getElementsByTagName("li"); | ||||||
|
|
||||||
| for (let elem of listItems) { | ||||||
| let menu = elem.getElementsByTagName("select")[0]; | ||||||
| result += menu.value; | ||||||
| } | ||||||
|
|
||||||
| return result; | ||||||
| } | ||||||
|
|
||||||
| function setCLItemsFromString(formId, clString) { | ||||||
| let counts = {R: 0, G: 0, A: 0, X:0}; | ||||||
|
|
||||||
| let form = document.getElementById(formId); | ||||||
| let listItems = form.getElementsByTagName("li"); | ||||||
|
|
||||||
| if (clString.length < listItems.length) { | ||||||
| clString = clString.padEnd(listItems.length, "R"); | ||||||
| } else if (clString.length > listItems.length) { | ||||||
| clString = clString.substring(0, listItems.length); | ||||||
| } | ||||||
|
|
||||||
| for (let i = 0; i < clString.length; i++) { | ||||||
| let char = clString.charAt(i); | ||||||
| counts[char]++; | ||||||
| let menu = listItems[i].getElementsByTagName("select")[0]; | ||||||
| menu.value = char; | ||||||
| } | ||||||
|
|
||||||
| form.elements["gcount"].value = counts["G"]; | ||||||
| form.elements["rcount"].value = counts["R"]; | ||||||
| form.elements["acount"].value = counts["A"]; | ||||||
| form.elements["xcount"].value = counts["X"]; | ||||||
|
|
||||||
| let numClItems = listItems.length - counts["X"]; | ||||||
|
|
||||||
| document.getElementById(formId + "-rtotal").textContent = numClItems; | ||||||
| document.getElementById(formId + "-gtotal").textContent = numClItems; | ||||||
| document.getElementById(formId + "-atotal").textContent = numClItems; | ||||||
|
|
||||||
| let itemChoices = getStringFromCLItems(formId); | ||||||
| localStorage.setItem(formId, itemChoices); | ||||||
| } | ||||||
|
|
||||||
| function clChange(formId) { | ||||||
| let itemChoices = getStringFromCLItems(formId); | ||||||
| setCLItemsFromString(formId, itemChoices); | ||||||
| } | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security control: Static Code Analysis Semgrep Pro
Javascript.Browser.Security.Insecure-Document-Method.Insecure-Document-Method
User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilitiesSeverity: HIGH
Learn more about this issue
Fix suggestion:
This fix suggestion was generated by Jit. Please note that the suggestion might not always fit every use case. It is highly recommended that you check and review it before merging.
Suggestion guidelines
This remediation replaces the usage of insecure methods like 'innerHTML', 'outerHTML' or 'document.write' with a safer alternative, 'textContent'. The code will now use 'textContent' to safely set or update the content without putting your application at risk of XSS attacks.
Why should you fix this issue?
This code introduces a vulnerability that could compromise the security of your production environment. In production, where reliability and security are paramount, even a small vulnerability can be exploited to cause significant damage, leading to unauthorized access or service disruption.
Jit Bot commands and options (e.g., ignore issue)
You can trigger Jit actions by commenting on this PR review:
#jit_ignore_fpIgnore and mark this specific single instance of finding as “False Positive”#jit_ignore_acceptIgnore and mark this specific single instance of finding as “Accept Risk”#jit_ignore_type_in_fileIgnore any finding of type "javascript.browser.security.insecure-document-method.insecure-document-method" in static/js/checklist.js; future occurrences will also be ignored.#jit_undo_ignoreUndo ignore command