debug markdown diff output edge cases
This commit is contained in:
parent
70a22ad5ff
commit
8c5ac6ade7
34
TODO.md
34
TODO.md
@ -1,33 +1 @@
|
||||
|
||||
- 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!
|
||||
Support for displaying and responding to `#markdown` path suffix.
|
@ -44,20 +44,14 @@ export const isShowDifference= isShow(Show.DIFFERENCE)
|
||||
export const diff = createSelector(
|
||||
[format, safeInput],
|
||||
(format, safeInput) => {
|
||||
return Dubdiff.plaintextDiff(safeInput.original, safeInput.final)
|
||||
/*
|
||||
let diff = JsDiff.diffWords (input.original.replace(/ /g, ' '), input.final.replace(/ /g, ' '))
|
||||
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>
|
||||
))
|
||||
*/
|
||||
if (format==Format.PLAINTEXT)
|
||||
return Dubdiff.plaintextDiff(safeInput.original, safeInput.final)
|
||||
else if (format==Format.MARKDOWN)
|
||||
return Dubdiff.markdownDiff(safeInput.original, safeInput.final)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
html diff
|
||||
---
|
||||
|
@ -60,4 +60,5 @@ class EditorsDiff extends Diff {
|
||||
|
||||
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
|
||||
|
||||
//rules:
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
||||
//not yet implemented rules:
|
||||
@ -59,8 +61,10 @@ export function diffToHtml(diff) {
|
||||
function rewriteMarkdownDiff(diff) {
|
||||
//apply transformation rules
|
||||
let transformedDiff = diff
|
||||
transformedDiff= applyTransformationRule1(transformedDiff)
|
||||
transformedDiff= applyTransformationRule2(transformedDiff)
|
||||
transformedDiff= applyTransformationRuleMultilineDelThenIns(transformedDiff)
|
||||
transformedDiff= applyTransformationRuleBreakUpDelIns(transformedDiff)
|
||||
transformedDiff= applyTransformationRuleFormattingPrefix(transformedDiff)
|
||||
transformedDiff= applyTransformationRuleRemoveEmpty(transformedDiff)
|
||||
return transformedDiff
|
||||
}
|
||||
|
||||
@ -68,7 +72,7 @@ function rewriteMarkdownDiff(diff) {
|
||||
// 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
|
||||
// so the markdown will apply to the ins text as it should
|
||||
function applyTransformationRule1(diff) {
|
||||
function applyTransformationRuleMultilineDelThenIns(diff) {
|
||||
let transformedDiff = []
|
||||
|
||||
const B_ADDED='added', B_REMOVED='removed', B_SAME='same'
|
||||
@ -117,6 +121,46 @@ function applyTransformationRule1(diff) {
|
||||
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
|
||||
// ^ - start of line
|
||||
@ -126,18 +170,18 @@ function applyTransformationRule1(diff) {
|
||||
// |([ \t]+[\*\+-]) - unordered lists
|
||||
// |([ \t]+[0-9]+\.) - numeric lists
|
||||
// )?
|
||||
// [ \t]* - trailing whitespace
|
||||
const MARKDOWN_PREFIX = /^([ \t]*\>)*(([ \t]*#*)|([ \t]*[\*\+-])|([ \t]*[\d]+\.))?[ \t]*/
|
||||
// [ \t]+ - trailing whitespace
|
||||
const MARKDOWN_PREFIX = /^([ \t]*\>)*(([ \t]*#*)|([ \t]*[\*\+-])|([ \t]*[\d]+\.))?[ \t]+/
|
||||
|
||||
//matches strings that end with a newline followed by some whitespace
|
||||
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)
|
||||
// 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,
|
||||
// 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 isNewline = true
|
||||
@ -186,42 +230,21 @@ function isMultilineDiffBlock({value}) {
|
||||
//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 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
|
||||
function splitMultilineDiffBlock({added, removed, value}) {
|
||||
//find the indices of the diff block that coorespond to newlines
|
||||
const splits = indicesOf(value, c => (c=='\n') )
|
||||
let lines = value.split('\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)
|
||||
|
||||
//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})
|
||||
console.log(lines)
|
||||
console.log(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 {markdownDiff, diffToString} from '../src/common/util/dubdiff'
|
||||
import {markdownDiff, diffToString, diffToHtml} from '../src/common/util/dubdiff'
|
||||
|
||||
let diff = (a,b) => diffToString(markdownDiff(a,b))
|
||||
|
||||
@ -43,7 +43,7 @@ describe('dubdiff', () => {
|
||||
`# Title
|
||||
other`,
|
||||
`# Subtitle`
|
||||
)).to.equal('# [-Title-]{+Subtitle+}[-\nother-]')
|
||||
)).to.equal('# [-Title-]{+Subtitle+}\n[-other-]')
|
||||
})
|
||||
|
||||
it('pulls prefixes out of ins or del blocks after newline', () => {
|
||||
@ -70,5 +70,18 @@ other`,
|
||||
'This [link](https://somewhere.com) is the same.',
|
||||
'This [link](https://somewhere.org) 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