debug markdown diff output edge cases
This commit is contained in:
parent
70a22ad5ff
commit
8c5ac6ade7
34
TODO.md
34
TODO.md
@ -1,33 +1 @@
|
|||||||
|
Support for displaying and responding to `#markdown` path suffix.
|
||||||
- create production mode build and serve settings: `webpack.js` and `src/server/babel.index.js`
|
|
||||||
|
|
||||||
|
|
||||||
## State changes
|
|
||||||
|
|
||||||
main page edits input documents
|
|
||||||
compare page views compare documents
|
|
||||||
|
|
||||||
compare button:
|
|
||||||
|
|
||||||
- generate id
|
|
||||||
- post input documents to server
|
|
||||||
- input documents copied to compare documents
|
|
||||||
- input documents cleared
|
|
||||||
- go to compare route
|
|
||||||
|
|
||||||
edit button:
|
|
||||||
|
|
||||||
- compare documents copied to input documents
|
|
||||||
- compare documents cleared
|
|
||||||
- go to main route
|
|
||||||
|
|
||||||
client start:
|
|
||||||
|
|
||||||
- load input documents from localStore
|
|
||||||
|
|
||||||
server start:
|
|
||||||
|
|
||||||
- load compare documents from database
|
|
||||||
|
|
||||||
|
|
||||||
* client actually never needs to query server for compare documents... huh!
|
|
@ -44,20 +44,14 @@ export const isShowDifference= isShow(Show.DIFFERENCE)
|
|||||||
export const diff = createSelector(
|
export const diff = createSelector(
|
||||||
[format, safeInput],
|
[format, safeInput],
|
||||||
(format, safeInput) => {
|
(format, safeInput) => {
|
||||||
|
if (format==Format.PLAINTEXT)
|
||||||
return Dubdiff.plaintextDiff(safeInput.original, safeInput.final)
|
return Dubdiff.plaintextDiff(safeInput.original, safeInput.final)
|
||||||
/*
|
else if (format==Format.MARKDOWN)
|
||||||
let diff = JsDiff.diffWords (input.original.replace(/ /g, ' '), input.final.replace(/ /g, ' '))
|
return Dubdiff.markdownDiff(safeInput.original, safeInput.final)
|
||||||
return diff.map(({added, removed, value})=>({added, removed, value:value.replace(/ /g, ' ')})).map(part => (
|
|
||||||
part.added ? <ins>{part.value}</ins> :
|
|
||||||
part.removed ? <del>{part.value}</del> :
|
|
||||||
<span>{part.value}</span>
|
|
||||||
))
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
html diff
|
html diff
|
||||||
---
|
---
|
||||||
|
@ -61,3 +61,4 @@ class EditorsDiff extends Diff {
|
|||||||
export default EditorsDiff
|
export default EditorsDiff
|
||||||
|
|
||||||
const isSpace = str => /[ ]+/.test(str)
|
const isSpace = str => /[ ]+/.test(str)
|
||||||
|
const isNewline = str => /[\n]+/.test(str)
|
@ -44,10 +44,12 @@ export function diffToHtml(diff) {
|
|||||||
// In essence, moves the markdown formatting elements in or out of the inserted and deleted blocks, as appropriate
|
// In essence, moves the markdown formatting elements in or out of the inserted and deleted blocks, as appropriate
|
||||||
|
|
||||||
//rules:
|
//rules:
|
||||||
|
|
||||||
// 1. if a multiline del block is followed by an ins block,
|
// 1. if a multiline del block is followed by an ins block,
|
||||||
// the first line of the ins block should be inserted at the end of the first line of the del block
|
// the first line of the ins block should be inserted at the end of the first line of the del block
|
||||||
// so the markdown will apply to the ins text as it should
|
// so the markdown will apply to the ins text as it should
|
||||||
// 2. after a newline, if an ins or del block begins with a markdown line formatting prefix (eg. for a title or list)
|
// 2. multiline ins and del blocks should be broken up into a series of single line blocks
|
||||||
|
// 3. after a newline, if an ins or del block begins with a markdown line formatting prefix (eg. for a title or list)
|
||||||
// then that prefix should be moved out of the block
|
// then that prefix should be moved out of the block
|
||||||
|
|
||||||
//not yet implemented rules:
|
//not yet implemented rules:
|
||||||
@ -59,8 +61,10 @@ export function diffToHtml(diff) {
|
|||||||
function rewriteMarkdownDiff(diff) {
|
function rewriteMarkdownDiff(diff) {
|
||||||
//apply transformation rules
|
//apply transformation rules
|
||||||
let transformedDiff = diff
|
let transformedDiff = diff
|
||||||
transformedDiff= applyTransformationRule1(transformedDiff)
|
transformedDiff= applyTransformationRuleMultilineDelThenIns(transformedDiff)
|
||||||
transformedDiff= applyTransformationRule2(transformedDiff)
|
transformedDiff= applyTransformationRuleBreakUpDelIns(transformedDiff)
|
||||||
|
transformedDiff= applyTransformationRuleFormattingPrefix(transformedDiff)
|
||||||
|
transformedDiff= applyTransformationRuleRemoveEmpty(transformedDiff)
|
||||||
return transformedDiff
|
return transformedDiff
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +72,7 @@ function rewriteMarkdownDiff(diff) {
|
|||||||
// 1. if a multiline del block is followed by an ins block,
|
// 1. if a multiline del block is followed by an ins block,
|
||||||
// the first line of the ins block should be inserted at the end of the first line of the del block
|
// the first line of the ins block should be inserted at the end of the first line of the del block
|
||||||
// so the markdown will apply to the ins text as it should
|
// so the markdown will apply to the ins text as it should
|
||||||
function applyTransformationRule1(diff) {
|
function applyTransformationRuleMultilineDelThenIns(diff) {
|
||||||
let transformedDiff = []
|
let transformedDiff = []
|
||||||
|
|
||||||
const B_ADDED='added', B_REMOVED='removed', B_SAME='same'
|
const B_ADDED='added', B_REMOVED='removed', B_SAME='same'
|
||||||
@ -117,6 +121,46 @@ function applyTransformationRule1(diff) {
|
|||||||
return transformedDiff
|
return transformedDiff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Transformation rule 2
|
||||||
|
// 2. multiline del and ins blocks should be broken up
|
||||||
|
// into a series of single line blocks
|
||||||
|
function applyTransformationRuleBreakUpDelIns(diff) {
|
||||||
|
let transformedDiff = []
|
||||||
|
|
||||||
|
const B_ADDED='added', B_REMOVED='removed', B_SAME='same'
|
||||||
|
let blockType = null
|
||||||
|
let blockIsMultiline = false
|
||||||
|
|
||||||
|
//iterate the input tokens to create the intermediate representation
|
||||||
|
diff.forEach((block) => {
|
||||||
|
|
||||||
|
blockType = (block.added ? B_ADDED : (block.removed ? B_REMOVED : B_SAME))
|
||||||
|
blockIsMultiline = isMultilineDiffBlock(block)
|
||||||
|
|
||||||
|
//transform rule applys when:
|
||||||
|
// the current block is an ins or del and is multiline
|
||||||
|
if ((blockType == B_REMOVED || blockType == B_ADDED) && blockIsMultiline) {
|
||||||
|
|
||||||
|
//split the first line from the current block
|
||||||
|
let blockSplit = splitMultilineDiffBlock(block)
|
||||||
|
|
||||||
|
blockSplit.forEach(blockSplitLine => transformedDiff.push(blockSplitLine))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//otherwise, we just add the current block to the transformed list
|
||||||
|
transformedDiff.push(block)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return transformedDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Transformation rule number 4: remove empty blocks
|
||||||
|
function applyTransformationRuleRemoveEmpty(diff) {
|
||||||
|
|
||||||
|
return diff.filter(({value}) => value.length>0)
|
||||||
|
}
|
||||||
|
|
||||||
// matches markdown prefixes that affect the formatting of the whole subsequent line
|
// matches markdown prefixes that affect the formatting of the whole subsequent line
|
||||||
// ^ - start of line
|
// ^ - start of line
|
||||||
@ -126,18 +170,18 @@ function applyTransformationRule1(diff) {
|
|||||||
// |([ \t]+[\*\+-]) - unordered lists
|
// |([ \t]+[\*\+-]) - unordered lists
|
||||||
// |([ \t]+[0-9]+\.) - numeric lists
|
// |([ \t]+[0-9]+\.) - numeric lists
|
||||||
// )?
|
// )?
|
||||||
// [ \t]* - trailing whitespace
|
// [ \t]+ - trailing whitespace
|
||||||
const MARKDOWN_PREFIX = /^([ \t]*\>)*(([ \t]*#*)|([ \t]*[\*\+-])|([ \t]*[\d]+\.))?[ \t]*/
|
const MARKDOWN_PREFIX = /^([ \t]*\>)*(([ \t]*#*)|([ \t]*[\*\+-])|([ \t]*[\d]+\.))?[ \t]+/
|
||||||
|
|
||||||
//matches strings that end with a newline followed by some whitespace
|
//matches strings that end with a newline followed by some whitespace
|
||||||
const NEWLINE_SUFFIX = /\n\s*$/
|
const NEWLINE_SUFFIX = /\n\s*$/
|
||||||
|
|
||||||
// transformation rule 2:
|
// transformation rule 3:
|
||||||
// after a newline, if an ins or del block begins with a markdown line formatting prefix (eg. for a title or list)
|
// after a newline, if an ins or del block begins with a markdown line formatting prefix (eg. for a title or list)
|
||||||
// then that prefix should be moved out of the block
|
// then that prefix should be moved out of the block
|
||||||
// also, if an ins block begins with a formatting prefix and follows immediately after a del block that follows a newline,
|
// also, if an ins block begins with a formatting prefix and follows immediately after a del block that follows a newline,
|
||||||
// the prefix should be moved out of the block _and_ an extra newline character should be added to the beginning of it
|
// the prefix should be moved out of the block _and_ an extra newline character should be added to the beginning of it
|
||||||
function applyTransformationRule2(diff) {
|
function applyTransformationRuleFormattingPrefix(diff) {
|
||||||
let transformedDiff = []
|
let transformedDiff = []
|
||||||
|
|
||||||
let isNewline = true
|
let isNewline = true
|
||||||
@ -186,42 +230,21 @@ function isMultilineDiffBlock({value}) {
|
|||||||
//but with the string split by newlines
|
//but with the string split by newlines
|
||||||
//if the diff block has no newlines, an array containing only that diff will be returned
|
//if the diff block has no newlines, an array containing only that diff will be returned
|
||||||
//if the diff block has newlines, the resulting array will have a series of blocks,
|
//if the diff block has newlines, the resulting array will have a series of blocks,
|
||||||
// each of which subsequent to the first block will begin with a newline
|
// consisting of the block text, interleaved with newlines
|
||||||
|
// ,
|
||||||
|
// each of which begin with a newline
|
||||||
//if the diff block begins with a newline, the returned array will begin with an empty diff
|
//if the diff block begins with a newline, the returned array will begin with an empty diff
|
||||||
function splitMultilineDiffBlock({added, removed, value}) {
|
function splitMultilineDiffBlock({added, removed, value}) {
|
||||||
//find the indices of the diff block that coorespond to newlines
|
let lines = value.split('\n')
|
||||||
const splits = indicesOf(value, c => (c=='\n') )
|
let blocks = []
|
||||||
|
// lines = lines.filter(line=>line.length>0)
|
||||||
|
lines.forEach((line, index) => {
|
||||||
|
blocks.push({added, removed, value:line})
|
||||||
|
if (index<lines.length-1) blocks.push({value:'\n'})
|
||||||
|
} )
|
||||||
|
|
||||||
splits.push(value.length)
|
console.log(lines)
|
||||||
|
console.log(blocks)
|
||||||
//create a range from each index
|
|
||||||
const ranges = splits.reduce(
|
|
||||||
//the accumulator is a structure with the last index and the list of ranges
|
|
||||||
//the ranges are a {start, end} structure
|
|
||||||
({last, ranges}, i) => {
|
|
||||||
ranges = ranges.concat([{start:last, end:i}])
|
|
||||||
return {last:i, ranges}
|
|
||||||
},
|
|
||||||
//start with the zero index and an empty array
|
|
||||||
{last: 0, ranges:[]}
|
|
||||||
).ranges
|
|
||||||
|
|
||||||
|
|
||||||
//map the ranges into blocks
|
|
||||||
const blocks = ranges.map(
|
|
||||||
//each block is the same as the given original block, but with the values split at newlines
|
|
||||||
({start, end}) => ({added, removed, value:value.substring(start, end)})
|
|
||||||
)
|
|
||||||
|
|
||||||
//console.log({value, splits, ranges, blocks})
|
|
||||||
|
|
||||||
return blocks
|
return blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
//collect all the indices of the given string that satisfy the test function
|
|
||||||
const indicesOf = (string, test) => string.split('').reduce(
|
|
||||||
//add indexes that satisfy the test function to the array
|
|
||||||
(acc, x, i) => (test(x) ? acc.concat([i]) : acc ),
|
|
||||||
//start with the empty array
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import chai from 'chai'
|
import chai from 'chai'
|
||||||
|
|
||||||
import {markdownDiff, diffToString} from '../src/common/util/dubdiff'
|
import {markdownDiff, diffToString, diffToHtml} from '../src/common/util/dubdiff'
|
||||||
|
|
||||||
let diff = (a,b) => diffToString(markdownDiff(a,b))
|
let diff = (a,b) => diffToString(markdownDiff(a,b))
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ describe('dubdiff', () => {
|
|||||||
`# Title
|
`# Title
|
||||||
other`,
|
other`,
|
||||||
`# Subtitle`
|
`# Subtitle`
|
||||||
)).to.equal('# [-Title-]{+Subtitle+}[-\nother-]')
|
)).to.equal('# [-Title-]{+Subtitle+}\n[-other-]')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('pulls prefixes out of ins or del blocks after newline', () => {
|
it('pulls prefixes out of ins or del blocks after newline', () => {
|
||||||
@ -71,4 +71,17 @@ other`,
|
|||||||
'This [link](https://somewhere.org) changed.'
|
'This [link](https://somewhere.org) changed.'
|
||||||
)).to.equal('This [link](https://somewhere.[-com-]{+org+}) [-is the same-]{+changed+}.')
|
)).to.equal('This [link](https://somewhere.[-com-]{+org+}) [-is the same-]{+changed+}.')
|
||||||
})
|
})
|
||||||
|
it('deletes a title' , () => {
|
||||||
|
expect(diff(
|
||||||
|
'Hello\n# Title 1\n# Title 2',
|
||||||
|
'Hello\n# Title 2'
|
||||||
|
)).to.equal('Hello\n# Title [-1-]\n# [-Title -]2',)
|
||||||
})
|
})
|
||||||
|
it('deletes a more different title' , () => {
|
||||||
|
expect(diff(
|
||||||
|
'Hello\n# Filbert\n# Title 2',
|
||||||
|
'Hello\n# Title 2'
|
||||||
|
)).to.equal('Hello\n# [-Filbert-]\n# Title 2',)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user