debug markdown diff output edge cases

This commit is contained in:
Adam Brown 2017-01-05 17:18:03 -05:00
parent 70a22ad5ff
commit 8c5ac6ade7
5 changed files with 88 additions and 89 deletions

34
TODO.md
View File

@ -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!

View File

@ -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
--- ---

View File

@ -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)

View File

@ -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
[]
)

View File

@ -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',)
})
})