feat: add functionality to parse checklists in text
This commit is contained in:
parent
2691a84610
commit
d2ed0fd673
|
@ -53,6 +53,7 @@ import hljs from 'highlight.js/lib/common'
|
|||
|
||||
import AttachmentModel from '../../models/attachment'
|
||||
import AttachmentService from '../../services/attachment'
|
||||
import {findCheckboxesInText} from '../../helpers/checklistFromText'
|
||||
|
||||
export default {
|
||||
name: 'editor',
|
||||
|
@ -303,36 +304,7 @@ export default {
|
|||
return str.substr(0, index) + replacement + str.substr(index + replacement.length)
|
||||
},
|
||||
findNthIndex(str, n) {
|
||||
|
||||
const searchLength = 6
|
||||
const listPrefixes = ['*', '-']
|
||||
|
||||
let inChecked, inUnchecked, startIndex = 0
|
||||
// We're building an array with all checkboxes, checked or unchecked.
|
||||
// I've found this to be the best way to always get the results I need.
|
||||
// The difficulty without an index is that we need to get all checkboxes, checked and unchecked
|
||||
// and calculate our index based off that to compare it and find the checkbox we need.
|
||||
let checkboxes = []
|
||||
|
||||
// Searching in two different loops for each search term since that is way easier and more predicatble
|
||||
// More "intelligent" solutions sometimes don't have all values or duplicates.
|
||||
// Because we're sorting and removing duplicates of them, we can safely put everything in one giant array.
|
||||
listPrefixes.forEach(pref => {
|
||||
while ((inChecked = str.indexOf(`${pref} [x]`, startIndex)) > -1) {
|
||||
checkboxes.push(inChecked)
|
||||
startIndex = startIndex + searchLength
|
||||
}
|
||||
|
||||
startIndex = 0
|
||||
while ((inUnchecked = str.indexOf(`${pref} [ ]`, startIndex)) > -1) {
|
||||
checkboxes.push(inUnchecked)
|
||||
startIndex = startIndex + searchLength
|
||||
}
|
||||
})
|
||||
|
||||
checkboxes.sort((a, b) => a - b)
|
||||
checkboxes = checkboxes.filter((v, i, s) => s.indexOf(v) === i && v > -1)
|
||||
|
||||
const checkboxes = findCheckboxesInText(str)
|
||||
return checkboxes[n]
|
||||
},
|
||||
renderPreview() {
|
||||
|
@ -435,7 +407,7 @@ export default {
|
|||
console.debug(index, this.text.substr(index, 9))
|
||||
|
||||
const listPrefix = this.text.substr(index, 1)
|
||||
|
||||
|
||||
if (checked) {
|
||||
this.text = this.replaceAt(this.text, index, `${listPrefix} [x] `)
|
||||
} else {
|
||||
|
@ -475,7 +447,7 @@ export default {
|
|||
input[type="checkbox"] {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
|
||||
&.has-checkbox {
|
||||
margin-left: -2em;
|
||||
list-style: none;
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import {findCheckboxesInText, getChecklistStatistics} from './checklistFromText'
|
||||
|
||||
describe('Find checklists in text', () => {
|
||||
it('should find no checkbox', () => {
|
||||
const text: string = 'Lorem Ipsum'
|
||||
const checkboxes = findCheckboxesInText(text)
|
||||
|
||||
expect(checkboxes).toHaveLength(0)
|
||||
})
|
||||
it('should find multiple checkboxes', () => {
|
||||
const text: string = `* [ ] Lorem Ipsum
|
||||
* [ ] Dolor sit amet
|
||||
|
||||
Here's some text in between
|
||||
|
||||
* [x] Dolor sit amet
|
||||
- [ ] Dolor sit amet`
|
||||
const checkboxes = findCheckboxesInText(text)
|
||||
|
||||
expect(checkboxes).toHaveLength(4)
|
||||
expect(checkboxes[0]).toBe(0)
|
||||
expect(checkboxes[1]).toBe(18)
|
||||
expect(checkboxes[2]).toBe(69)
|
||||
})
|
||||
it('should find one checkbox with *', () => {
|
||||
const text: string = '* [ ] Lorem Ipsum'
|
||||
const checkboxes = findCheckboxesInText(text)
|
||||
|
||||
expect(checkboxes).toHaveLength(1)
|
||||
expect(checkboxes[0]).toBe(0)
|
||||
})
|
||||
it('should find one checkbox with -', () => {
|
||||
const text: string = '- [ ] Lorem Ipsum'
|
||||
const checkboxes = findCheckboxesInText(text)
|
||||
|
||||
expect(checkboxes).toHaveLength(1)
|
||||
expect(checkboxes[0]).toBe(0)
|
||||
})
|
||||
it('should find one checked checkbox with *', () => {
|
||||
const text: string = '* [x] Lorem Ipsum'
|
||||
const checkboxes = findCheckboxesInText(text)
|
||||
|
||||
expect(checkboxes).toHaveLength(1)
|
||||
expect(checkboxes[0]).toBe(0)
|
||||
})
|
||||
it('should find one checked checkbox with -', () => {
|
||||
const text: string = '- [x] Lorem Ipsum'
|
||||
const checkboxes = findCheckboxesInText(text)
|
||||
|
||||
expect(checkboxes).toHaveLength(1)
|
||||
expect(checkboxes[0]).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Get Checklist Statistics in a Text', () => {
|
||||
it('should find no checkbox', () => {
|
||||
const text: string = 'Lorem Ipsum'
|
||||
const stats = getChecklistStatistics(text)
|
||||
|
||||
expect(stats.total).toBe(0)
|
||||
})
|
||||
it('should find one checkbox', () => {
|
||||
const text: string = '* [ ] Lorem Ipsum'
|
||||
const stats = getChecklistStatistics(text)
|
||||
|
||||
expect(stats.total).toBe(1)
|
||||
expect(stats.checked).toBe(0)
|
||||
})
|
||||
it('should find one checked checkbox', () => {
|
||||
const text: string = '* [x] Lorem Ipsum'
|
||||
const stats = getChecklistStatistics(text)
|
||||
|
||||
expect(stats.total).toBe(1)
|
||||
expect(stats.checked).toBe(1)
|
||||
})
|
||||
it('should find multiple mixed and matched', () => {
|
||||
const text: string = `* [ ] Lorem Ipsum
|
||||
* [ ] Dolor sit amet
|
||||
* [x] Dolor sit amet
|
||||
- [x] Dolor sit amet
|
||||
|
||||
Here's some text in between
|
||||
|
||||
* [x] Dolor sit amet
|
||||
- [ ] Dolor sit amet`
|
||||
const stats = getChecklistStatistics(text)
|
||||
|
||||
expect(stats.total).toBe(6)
|
||||
expect(stats.checked).toBe(3)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,62 @@
|
|||
const searchLength = 6
|
||||
const listPrefixes = ['*', '-']
|
||||
const unchecked = '[ ]'
|
||||
const checked = '[x]'
|
||||
|
||||
/**
|
||||
* Returns the indices where checkboxes start and end in the given text.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
export const findCheckboxesInText = (text: string): number[] => {
|
||||
let inChecked: number, inUnchecked: number, startIndex: number = 0
|
||||
// We're building an array with all checkboxes, checked or unchecked.
|
||||
// I've found this to be the best way to always get the results I need.
|
||||
// The difficulty without an index is that we need to get all checkboxes, checked and unchecked
|
||||
// and calculate our index based off that to compare it and find the checkbox we need.
|
||||
let checkboxes: number[] = []
|
||||
|
||||
// Searching in two different loops for each search term since that is way easier and more predicatble
|
||||
// More "intelligent" solutions sometimes don't have all values or duplicates.
|
||||
// Because we're sorting and removing duplicates of them, we can safely put everything in one giant array.
|
||||
listPrefixes.forEach(pref => {
|
||||
while ((inChecked = text.indexOf(`${pref} ${checked}`, startIndex)) > -1) {
|
||||
checkboxes.push(inChecked)
|
||||
startIndex = startIndex + searchLength
|
||||
}
|
||||
|
||||
startIndex = 0
|
||||
while ((inUnchecked = text.indexOf(`${pref} ${unchecked}`, startIndex)) > -1) {
|
||||
checkboxes.push(inUnchecked)
|
||||
startIndex = startIndex + searchLength
|
||||
}
|
||||
})
|
||||
|
||||
checkboxes.sort((a, b) => a - b)
|
||||
checkboxes = checkboxes.filter((v, i, s) => s.indexOf(v) === i && v > -1)
|
||||
|
||||
return checkboxes
|
||||
}
|
||||
|
||||
interface CheckboxStatistics {
|
||||
total: number
|
||||
checked: number
|
||||
}
|
||||
|
||||
export const getChecklistStatistics = (text: string): CheckboxStatistics => {
|
||||
const checkboxes = findCheckboxesInText(text)
|
||||
|
||||
const res: CheckboxStatistics = {
|
||||
total: checkboxes.length,
|
||||
checked: 0,
|
||||
}
|
||||
|
||||
checkboxes.forEach(c => {
|
||||
const box = text.substr(c, searchLength)
|
||||
if (box.trim().endsWith(checked)) {
|
||||
res.checked++
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
Reference in New Issue