From 8c5ac6ade7dd619f920b2f01322148764e05d13d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 5 Jan 2017 17:18:03 -0500 Subject: [PATCH] debug markdown diff output edge cases --- TODO.md | 34 +---------- src/common/selectors.js | 14 ++--- src/common/util/EditorsDiff.js | 3 +- src/common/util/dubdiff.js | 107 ++++++++++++++++++++------------- test/dubdiffMarkdown.js | 19 +++++- 5 files changed, 88 insertions(+), 89 deletions(-) diff --git a/TODO.md b/TODO.md index a1e047a..e9d18f6 100644 --- a/TODO.md +++ b/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! \ No newline at end of file +Support for displaying and responding to `#markdown` path suffix. \ No newline at end of file diff --git a/src/common/selectors.js b/src/common/selectors.js index e44d32e..3129712 100644 --- a/src/common/selectors.js +++ b/src/common/selectors.js @@ -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 ? {part.value} : - part.removed ? {part.value} : - {part.value} - )) -*/ + 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 --- diff --git a/src/common/util/EditorsDiff.js b/src/common/util/EditorsDiff.js index bdd7aec..2d649b2 100644 --- a/src/common/util/EditorsDiff.js +++ b/src/common/util/EditorsDiff.js @@ -60,4 +60,5 @@ class EditorsDiff extends Diff { export default EditorsDiff -const isSpace = str => /[ ]+/.test(str) \ No newline at end of file +const isSpace = str => /[ ]+/.test(str) +const isNewline = str => /[\n]+/.test(str) \ No newline at end of file diff --git a/src/common/util/dubdiff.js b/src/common/util/dubdiff.js index fe5bba9..933b716 100644 --- a/src/common/util/dubdiff.js +++ b/src/common/util/dubdiff.js @@ -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 { - 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 - [] -) +} \ No newline at end of file diff --git a/test/dubdiffMarkdown.js b/test/dubdiffMarkdown.js index a66be77..69a4f8d 100644 --- a/test/dubdiffMarkdown.js +++ b/test/dubdiffMarkdown.js @@ -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',) }) -}) \ No newline at end of file + 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',) + }) +}) +