From a12001deac811d2fbcb480d3406e1a5ab448aa36 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Wed, 28 Aug 2019 10:24:34 +0200 Subject: [PATCH] Fix multiple issues --- app/README.md => README.md | 0 app/src/js/i18n/build-dictionary.js | 5 +- packages/bauhaus-library/package.json | 1 + packages/bauhaus-library/src/panel/index.js | 25 +- .../bauhaus-library/src/search-rmes/index.js | 8 +- .../bauhaus-library/src/utils/array-utils.js | 20 ++ .../src/utils/array-utils.spec.js | 16 + .../correspondences/association.spec.js | 38 --- .../correspondences/associations.spec.js | 38 --- .../families/edition/validation.spec.js | 36 -- .../operations/edition/validation.spec.js | 56 --- .../series/edition/validation.spec.js | 56 --- .../operations/shared/relations/index.spec.js | 189 ----------- src/js/utils/draftjs/draftjsToMd.js | 210 ------------ src/js/utils/draftjs/mdToDraftjs.js | 321 ------------------ 15 files changed, 54 insertions(+), 965 deletions(-) rename app/README.md => README.md (100%) create mode 100644 packages/bauhaus-library/src/utils/array-utils.js create mode 100644 packages/bauhaus-library/src/utils/array-utils.spec.js delete mode 100644 src/js/actions/classifications/correspondences/association.spec.js delete mode 100644 src/js/actions/classifications/correspondences/associations.spec.js delete mode 100644 src/js/components/operations/families/edition/validation.spec.js delete mode 100644 src/js/components/operations/operations/edition/validation.spec.js delete mode 100644 src/js/components/operations/series/edition/validation.spec.js delete mode 100644 src/js/components/operations/shared/relations/index.spec.js delete mode 100644 src/js/utils/draftjs/draftjsToMd.js delete mode 100644 src/js/utils/draftjs/mdToDraftjs.js diff --git a/app/README.md b/README.md similarity index 100% rename from app/README.md rename to README.md diff --git a/app/src/js/i18n/build-dictionary.js b/app/src/js/i18n/build-dictionary.js index bed349ceb..e64d7a2c9 100644 --- a/app/src/js/i18n/build-dictionary.js +++ b/app/src/js/i18n/build-dictionary.js @@ -78,7 +78,4 @@ export const getMessageForSecondLang = message => { export default createDictionary(getLang()); -export const bauhausLibraryDictionary = createDictionary( - getLang(), - bauhausLibrary -); +export const bauhausLibraryDictionary = createDictionary(getLang(), dictionary); diff --git a/packages/bauhaus-library/package.json b/packages/bauhaus-library/package.json index 22518ac2c..49afa0c85 100644 --- a/packages/bauhaus-library/package.json +++ b/packages/bauhaus-library/package.json @@ -47,6 +47,7 @@ "useTabs": true }, "dependencies": { + "lodash": "^4.17.15", "react-loading": "^2.0.3", "react-select": "^3.0.4" } diff --git a/packages/bauhaus-library/src/panel/index.js b/packages/bauhaus-library/src/panel/index.js index cf2e11adb..8be56d11e 100644 --- a/packages/bauhaus-library/src/panel/index.js +++ b/packages/bauhaus-library/src/panel/index.js @@ -3,20 +3,19 @@ import PropTypes from 'prop-types'; import Flag from '../flag'; import './panel.scss'; -function Panel({ title, children, context = 'concepts', flag }) { - const flagComponent = flag ? ` ( ${} )` : ''; - return ( -
-
-

- {title} - {flagComponent} -

-
-
{children}
+const Panel = ({ title, children, context = 'concepts', flag }) => ( +
+
+

+ {title} + {flag && ` ( `} + {flag && } + {flag && ` )`} +

- ); -} +
{children}
+
+); Panel.propTypes = { title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, diff --git a/packages/bauhaus-library/src/search-rmes/index.js b/packages/bauhaus-library/src/search-rmes/index.js index f9f9dc98d..8ed37c038 100644 --- a/packages/bauhaus-library/src/search-rmes/index.js +++ b/packages/bauhaus-library/src/search-rmes/index.js @@ -3,7 +3,7 @@ import I18NContext from '../i18n-provider'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import Pagination from '../pagination'; -import { filterKeyDeburr, nbResults } from 'js/utils/array-utils'; +import { filterKeyDeburr, nbResults } from '../utils/array-utils'; function SearchRmes({ items = [], @@ -17,7 +17,7 @@ function SearchRmes({ label, }) { const [search, handleSearch] = useState(''); - const D = useContext(I18NContext).backToTop || label; + const D = useContext(I18NContext); const filter = filterKeyDeburr( Object.keys(items[0] || {}).filter(k => k !== 'id') @@ -42,7 +42,7 @@ function SearchRmes({ value={search} onChange={e => handleSearch(e.target.value)} type="text" - placeholder={D.searchLabelPlaceholder} + placeholder={D.searchLabelPlaceholder || placeholder} className="form-control" aria-label={D.search} /> @@ -64,7 +64,7 @@ function SearchRmes({
)}
-

{nbResults(hits)}

+

{nbResults(hits, D)}

diff --git a/packages/bauhaus-library/src/utils/array-utils.js b/packages/bauhaus-library/src/utils/array-utils.js new file mode 100644 index 000000000..b93df0fd4 --- /dev/null +++ b/packages/bauhaus-library/src/utils/array-utils.js @@ -0,0 +1,20 @@ +import _ from 'lodash'; + +export const filterKeyDeburr = keys => rawStr => { + const str = _.deburr(rawStr).toLocaleLowerCase(); + return item => { + let isIn = false; + for (var i = 0; i < keys.length; i++) { + if (_.deburr((item[keys[i]] || '').toLocaleLowerCase()).includes(str)) { + isIn = true; + break; + } + } + return isIn; + }; +}; + +export const nbResults = (array, dictionnary) => { + const { result, results } = dictionnary; + return `${array.length} ${array.length > 1 ? results : result}`; +}; diff --git a/packages/bauhaus-library/src/utils/array-utils.spec.js b/packages/bauhaus-library/src/utils/array-utils.spec.js new file mode 100644 index 000000000..1b57088be --- /dev/null +++ b/packages/bauhaus-library/src/utils/array-utils.spec.js @@ -0,0 +1,16 @@ +import * as A from './array-utils'; + +const D = { result: 'result', results: 'results' }; + +describe('array utils', () => { + describe('nbResults', () => { + it('should return string ends with any letter', () => { + expect(A.nbResults([], D)).toMatch(/[A-Za-z]{1}$/); + expect(A.nbResults(['A'], D)).toMatch(/[A-Za-z]{1}$/); + }); + + it("should return string ends with 's'", () => { + expect(A.nbResults(['a', 'b'], D).endsWith('s')).toBeTruthy(); + }); + }); +}); diff --git a/src/js/actions/classifications/correspondences/association.spec.js b/src/js/actions/classifications/correspondences/association.spec.js deleted file mode 100644 index 152fec7f5..000000000 --- a/src/js/actions/classifications/correspondences/association.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import getCorrespondenceAssociation from './association'; -import * as A from 'js/actions/constants'; -import api from 'js/remote-api/classifications-api'; - -const dispatch = jest.fn(); -jest.mock('js/remote-api/classifications-api'); - -describe('Associations actions', () => { - it('should call dispatch LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS_SUCCESS action with the right data', async () => { - api.getCorrespondenceAssociation = function() { - return Promise.resolve('results'); - }; - await getCorrespondenceAssociation(1, 2)(dispatch); - expect(dispatch).toHaveBeenCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATION, - payload: { correspondenceId: 1, associationId: 2 }, - }); - expect(dispatch).toHaveBeenLastCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATION_SUCCESS, - payload: { correspondenceId: 1, associationId: 2, results: 'results' }, - }); - }); - - it('should call dispatch LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS_FAILURE action with an error object', async () => { - api.getCorrespondenceAssociation = function() { - return Promise.reject('error'); - }; - await getCorrespondenceAssociation(1, 2)(dispatch); - expect(dispatch).toHaveBeenCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATION, - payload: { correspondenceId: 1, associationId: 2 }, - }); - expect(dispatch).toHaveBeenLastCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATION_FAILURE, - payload: { err: 'error', correspondenceId: 1, associationId: 2 }, - }); - }); -}); diff --git a/src/js/actions/classifications/correspondences/associations.spec.js b/src/js/actions/classifications/correspondences/associations.spec.js deleted file mode 100644 index 1946a00e2..000000000 --- a/src/js/actions/classifications/correspondences/associations.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import getCorrespondenceAssociations from './associations'; -import * as A from 'js/actions/constants'; -import api from 'js/remote-api/classifications-api'; - -const dispatch = jest.fn(); -jest.mock('js/remote-api/classifications-api'); - -describe('Associations actions', () => { - it('should call dispatch LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS_SUCCESS action with the right data', async () => { - api.getCorrespondenceAssociations = function() { - return Promise.resolve('results'); - }; - await getCorrespondenceAssociations(1)(dispatch); - expect(dispatch).toHaveBeenCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS, - payload: { id: 1 }, - }); - expect(dispatch).toHaveBeenLastCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS_SUCCESS, - payload: { id: 1, results: 'results' }, - }); - }); - - it('should call dispatch LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS_FAILURE action with an error object', async () => { - api.getCorrespondenceAssociations = function() { - return Promise.reject('error'); - }; - await getCorrespondenceAssociations(1)(dispatch); - expect(dispatch).toHaveBeenCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS, - payload: { id: 1 }, - }); - expect(dispatch).toHaveBeenLastCalledWith({ - type: A.LOAD_CLASSIFICATION_CORRESPONDENCE_ASSOCIATIONS_FAILURE, - payload: { err: 'error', id: 1 }, - }); - }); -}); diff --git a/src/js/components/operations/families/edition/validation.spec.js b/src/js/components/operations/families/edition/validation.spec.js deleted file mode 100644 index c0887254d..000000000 --- a/src/js/components/operations/families/edition/validation.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import { validate } from './validation'; - -describe('validation family', () => { - it('should return an error if the prefLabelLg1 is not defined', () => { - expect(validate({ prefLabelLg2: 'prefLabelLg2' })).toEqual({ - errorMessage: 'The title is required', - fields: { - prefLabelLg1: true, - prefLabelLg2: false, - }, - }); - }); - it('should return an error if the prefLabelLg2 is not defined', () => { - expect(validate({ prefLabelLg1: 'prefLabelLg1' })).toEqual({ - errorMessage: 'The title is required', - fields: { - prefLabelLg1: false, - prefLabelLg2: true, - }, - }); - }); - it('should not return any errors', () => { - expect( - validate({ - prefLabelLg1: 'prefLabelLg1', - prefLabelLg2: 'prefLabelLg2', - }) - ).toEqual({ - errorMessage: '', - fields: { - prefLabelLg1: false, - prefLabelLg2: false, - }, - }); - }); -}); diff --git a/src/js/components/operations/operations/edition/validation.spec.js b/src/js/components/operations/operations/edition/validation.spec.js deleted file mode 100644 index b0c23bc2b..000000000 --- a/src/js/components/operations/operations/edition/validation.spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import { validate } from './validation'; - -describe('validation operations', () => { - it('should return an error if the prefLabelLg1 is not defined', () => { - expect( - validate({ prefLabelLg2: 'prefLabelLg2', series: 'series' }) - ).toEqual({ - errorMessage: 'The title is required', - fields: { - series: false, - prefLabelLg1: true, - prefLabelLg2: false, - }, - }); - }); - it('should return an error if the prefLabelLg2 is not defined', () => { - expect( - validate({ prefLabelLg1: 'prefLabelLg1', series: 'series' }) - ).toEqual({ - errorMessage: 'The title is required', - fields: { - series: false, - prefLabelLg1: false, - prefLabelLg2: true, - }, - }); - }); - it('should return an error if the series is not defined', () => { - expect( - validate({ prefLabelLg1: 'prefLabelLg1', prefLabelLg2: 'prefLabelLg2' }) - ).toEqual({ - errorMessage: 'The series is required', - fields: { - series: true, - prefLabelLg1: false, - prefLabelLg2: false, - }, - }); - }); - it('should not return any errors', () => { - expect( - validate({ - prefLabelLg1: 'prefLabelLg1', - prefLabelLg2: 'prefLabelLg2', - series: 'series', - }) - ).toEqual({ - errorMessage: '', - fields: { - series: false, - prefLabelLg1: false, - prefLabelLg2: false, - }, - }); - }); -}); diff --git a/src/js/components/operations/series/edition/validation.spec.js b/src/js/components/operations/series/edition/validation.spec.js deleted file mode 100644 index d8c80713d..000000000 --- a/src/js/components/operations/series/edition/validation.spec.js +++ /dev/null @@ -1,56 +0,0 @@ -import { validate } from './validation'; - -describe('validation series', () => { - it('should return an error if the prefLabelLg1 is not defined', () => { - expect( - validate({ prefLabelLg2: 'prefLabelLg2', family: 'family' }) - ).toEqual({ - errorMessage: 'The title is required', - fields: { - family: false, - prefLabelLg1: true, - prefLabelLg2: false, - }, - }); - }); - it('should return an error if the prefLabelLg2 is not defined', () => { - expect( - validate({ prefLabelLg1: 'prefLabelLg1', family: 'family' }) - ).toEqual({ - errorMessage: 'The title is required', - fields: { - family: false, - prefLabelLg1: false, - prefLabelLg2: true, - }, - }); - }); - it('should return an error if the family is not defined', () => { - expect( - validate({ prefLabelLg1: 'prefLabelLg1', prefLabelLg2: 'prefLabelLg2' }) - ).toEqual({ - errorMessage: 'The family is required', - fields: { - family: true, - prefLabelLg1: false, - prefLabelLg2: false, - }, - }); - }); - it('should not return any errors', () => { - expect( - validate({ - prefLabelLg1: 'prefLabelLg1', - prefLabelLg2: 'prefLabelLg2', - family: 'family', - }) - ).toEqual({ - errorMessage: '', - fields: { - family: false, - prefLabelLg1: false, - prefLabelLg2: false, - }, - }); - }); -}); diff --git a/src/js/components/operations/shared/relations/index.spec.js b/src/js/components/operations/shared/relations/index.spec.js deleted file mode 100644 index f5c3266f7..000000000 --- a/src/js/components/operations/shared/relations/index.spec.js +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react'; -import RelationsView, { - RelationsViewPerLg, - RelationsViewPerLgContent, -} from './index'; -import { shallow } from 'enzyme'; -import { Note } from 'js/components/shared/note/note'; -import { Link } from 'react-router-dom'; - -describe('RelationsView', () => { - it('should display only the first lang', () => { - const component = shallow( - - ); - const relationsViewPerLg = component.find(RelationsViewPerLg); - expect(relationsViewPerLg.length).toBe(1); - expect(relationsViewPerLg.props().langSuffix).toBe('Lg1'); - expect(relationsViewPerLg.props().currentLang).toBe('Lg1'); - }); - - it('should display the first and second langs', () => { - const component = shallow( - - ); - const relationsViewPerLg = component.find(RelationsViewPerLg); - expect(relationsViewPerLg.length).toBe(2); - expect(relationsViewPerLg.get(0).props.langSuffix).toBe('Lg1'); - expect(relationsViewPerLg.get(0).props.currentLang).toBe('Lg1'); - - expect(relationsViewPerLg.get(1).props.langSuffix).toBe('Lg2'); - expect(relationsViewPerLg.get(1).props.currentLang).toBe('Lg2'); - }); -}); - -describe('RelationsViewPerLgContent', () => { - describe('parent', () => { - it('should not display the title if the parent is not defined', () => { - const component = shallow( - - ); - - expect(component.find('.linksTitle').length).toBe(0); - }); - it('should display the title if the parent is defined', () => { - const component = shallow( - - ); - - expect( - component - .find('.linksTitle') - .html() - .includes('parentTitle') - ).toBeTruthy(); - }); - it('should display a link to the parent', () => { - const component = shallow( - - ); - - const link = component.find(Link).get(0); - expect(link.props.to).toBe('/operations/parentPath/1'); - expect(link.props.children).toBe('labelLg1'); - }); - }); - - describe('children', () => { - it('should not display the title if the children are not defined', () => { - const component = shallow( - - ); - - expect(component.find('.linksTitle').length).toBe(1); - }); - it('should display the title if the children are defined', () => { - const component = shallow( - - ); - - expect(component.find('.linksTitle').length).toBe(1); - expect( - component - .find('.linksTitle') - .html() - .includes('childrenTitle') - ).toBeTruthy(); - }); - - it('should display a link per child', () => { - const component = shallow( - - ); - - expect(component.find('li').length).toBe(3); - - const links = component.find(Link); - expect(links.length).toBe(3); - - expect(links.get(0).props.to).toBe('/operations/childrenPath/0'); - expect(links.get(0).props.children).toBe('label1'); - - expect(links.get(1).props.to).toBe('/operations/childrenPath/1'); - expect(links.get(1).props.children).toBe('label2'); - - expect(links.get(2).props.to).toBe('/operations/childrenPath/2'); - expect(links.get(2).props.children).toBe('label3'); - }); - - it('should sort children alphabetically', () => { - const component = shallow( - - ); - - expect(component.find('li').length).toBe(3); - - const links = component.find(Link); - expect(links.length).toBe(3); - - expect(links.get(0).props.to).toBe('/operations/childrenPath/0'); - expect(links.get(0).props.children).toBe('label1'); - - expect(links.get(1).props.to).toBe('/operations/childrenPath/1'); - expect(links.get(1).props.children).toBe('label2'); - - expect(links.get(2).props.to).toBe('/operations/childrenPath/2'); - expect(links.get(2).props.children).toBe('label3'); - }); - }); -}); -describe('RelationsViewPerLg', () => { - it('should display one Note', () => { - const component = shallow( - - ); - - const note = component.find(Note); - expect(note.length).toBe(1); - - const props = note.props(); - expect(props.allowEmpty).toBeTruthy(); - expect(props.title).toBe('title'); - expect(props.lang).toBe('lg1'); - expect(props.alone).toBeTruthy(); - }); -}); diff --git a/src/js/utils/draftjs/draftjsToMd.js b/src/js/utils/draftjs/draftjsToMd.js deleted file mode 100644 index d77771703..000000000 --- a/src/js/utils/draftjs/draftjsToMd.js +++ /dev/null @@ -1,210 +0,0 @@ -const defaultMarkdownDict = { - BOLD: "__", - ITALIC: "*", - STRIKETHROUGH: "+" -}; - -const RGB_REGEXP = /((bg)?color)-rgb\((.*)\)/; - -const blockStyleDict = { - "unordered-list-item": "- ", - "header-one": "# ", - "header-two": "## ", - "header-three": "### ", - "header-four": "#### ", - "header-five": "##### ", - "header-six": "###### ", - blockquote: "> " -}; - -const wrappingBlockStyleDict = { - "code-block": "```" -}; - -const getBlockStyle = (currentStyle, appliedBlockStyles) => { - if (currentStyle === "ordered-list-item") { - const counter = appliedBlockStyles.reduce((prev, style) => { - if (style === "ordered-list-item") { - return prev + 1; - } - return prev; - }, 1); - return `${counter}. `; - } - return blockStyleDict[currentStyle] || ""; -}; - -const applyWrappingBlockStyle = (currentStyle, content) => { - if (currentStyle in wrappingBlockStyleDict) { - const wrappingSymbol = wrappingBlockStyleDict[currentStyle]; - return `${wrappingSymbol}\n${content}\n${wrappingSymbol}`; - } - - return content; -}; - -const applyAtomicStyle = (block, entityMap, content) => { - if (block.type !== "atomic") return content; - // strip the test that was added in the media block - const strippedContent = content.substring( - 0, - content.length - block.text.length - ); - const key = block.entityRanges[0].key; - const type = entityMap[key].type; - const data = entityMap[key].data; - if (type === "draft-js-video-plugin-video") { - return `${strippedContent}[[ embed url=${data.url || data.src} ]]`; - } - return `${strippedContent}![${data.fileName || ""}](${data.url || data.src})`; -}; - -const getEntityStart = entity => { - switch (entity.type) { - case "LINK": - return "["; - default: - return ""; - } -}; - -const getEntityEnd = entity => { - switch (entity.type) { - case "LINK": - return `](${entity.data.url})`; - default: - return ""; - } -}; - -function fixWhitespacesInsideStyle(text, style) { - const { symbol } = style; - - // Text before style-opening marker (including the marker) - const pre = text.slice(0, style.range.start); - // Text between opening and closing markers - const body = text.slice(style.range.start, style.range.end); - // Trimmed text between markers - const bodyTrimmed = body.trim(); - // Text after closing marker - const post = text.slice(style.range.end); - - const bodyTrimmedStart = style.range.start + body.indexOf(bodyTrimmed); - - // Text between opening marker and trimmed content (leading spaces) - const prefix = text.slice(style.range.start, bodyTrimmedStart); - // Text between the end of trimmed content and closing marker (trailing spaces) - const postfix = text.slice( - bodyTrimmedStart + bodyTrimmed.length, - style.range.end - ); - - // Temporary text that contains trimmed content wrapped into original pre- and post-texts - const newText = `${pre}${bodyTrimmed}${post}`; - // Insert leading and trailing spaces between pre-/post- contents and their respective markers - return newText.replace( - `${symbol}${bodyTrimmed}${symbol}`, - `${prefix}${symbol}${bodyTrimmed}${symbol}${postfix}` - ); -} - -function getInlineStyleRangesByLength(inlineStyleRanges) { - return [...inlineStyleRanges].sort((a, b) => b.length - a.length); -} - -export function draftjsToMd(raw, extraMarkdownDict) { - const markdownDict = { ...defaultMarkdownDict, ...extraMarkdownDict }; - const appliedBlockStyles = []; - - return raw.blocks - .map(block => { - // totalOffset is a difference of index position between raw string and enhanced ones - let totalOffset = 0; - let returnString = ""; - - // add block style - returnString += getBlockStyle(block.type, appliedBlockStyles); - appliedBlockStyles.push(block.type); - - const appliedStyles = []; - returnString += block.text - .split("") - .reduce((text, currentChar, index) => { - let newText = text; - - const sortedInlineStyleRanges = getInlineStyleRangesByLength( - block.inlineStyleRanges - ); - - // find all styled at this character - sortedInlineStyleRanges - .filter(range => range.offset === index) - .filter( - range => markdownDict[range.style] || RGB_REGEXP.test(range.style) - ) - .forEach(currentStyle => { - let symbol; - let symbolLength; - if (RGB_REGEXP.test(currentStyle.style)) { - symbol = `${currentStyle.style}`; - symbolLength = symbol.length; - newText += symbol; - totalOffset += symbolLength; - } else { - symbolLength = markdownDict[currentStyle.style].length; - newText += markdownDict[currentStyle.style]; - totalOffset += symbolLength; - symbol = markdownDict[currentStyle.style]; - } - - appliedStyles.push({ - symbol, - range: { - start: currentStyle.offset + totalOffset, - end: currentStyle.offset + currentStyle.length + totalOffset - }, - end: currentStyle.offset + (currentStyle.length - 1) - }); - }); - - // check for entityRanges starting and add if existing - const entitiesStartAtChar = block.entityRanges.filter( - range => range.offset === index - ); - entitiesStartAtChar.forEach(entity => { - newText += getEntityStart(raw.entityMap[entity.key]); - }); - - // add the current character to the md string - newText += currentChar; - - // check for entityRanges ending and add if existing - const entitiesEndAtChar = block.entityRanges.filter( - range => range.offset + range.length - 1 === index - ); - entitiesEndAtChar.forEach(entity => { - newText += getEntityEnd(raw.entityMap[entity.key]); - }); - - // apply the 'ending' tags for any styles that end in the current position in order (stack) - while ( - appliedStyles.length !== 0 && - appliedStyles[appliedStyles.length - 1].end === index - ) { - const endingStyle = appliedStyles.pop(); - newText += endingStyle.symbol; - - newText = fixWhitespacesInsideStyle(newText, endingStyle); - totalOffset += endingStyle.symbol.length; - } - - return newText; - }, ""); - - returnString = applyWrappingBlockStyle(block.type, returnString); - returnString = applyAtomicStyle(block, raw.entityMap, returnString); - - return returnString; - }) - .join("\n"); -} diff --git a/src/js/utils/draftjs/mdToDraftjs.js b/src/js/utils/draftjs/mdToDraftjs.js deleted file mode 100644 index 0381856cd..000000000 --- a/src/js/utils/draftjs/mdToDraftjs.js +++ /dev/null @@ -1,321 +0,0 @@ -const parse = require('@textlint/markdown-to-ast').parse; - -const defaultInlineStyles = { - Strong: { - type: 'BOLD', - symbol: '__', - }, - Emphasis: { - type: 'ITALIC', - symbol: '*', - }, - STRIKETHROUGH: { - type: 'STRIKETHROUGH', - symbol: '+', - }, -}; - -export const REGEXPS = [ - { - regexp: /^(\+)/, - type: defaultInlineStyles.STRIKETHROUGH.type, - }, - { - regexp: /^((bg)?color-rgb\(\d*,\d*,\d*\))/, - }, -]; - -const defaultBlockStyles = { - List: 'unordered-list-item', - Header1: 'header-one', - Header2: 'header-two', - Header3: 'header-three', - Header4: 'header-four', - Header5: 'header-five', - Header6: 'header-six', - CodeBlock: 'code-block', - BlockQuote: 'blockquote', -}; - -const getBlockStyleForMd = (node, blockStyles) => { - const style = node.type; - const ordered = node.ordered; - const depth = node.depth; - if (style === 'List' && ordered) { - return 'ordered-list-item'; - } else if (style === 'Header') { - return blockStyles[`${style}${depth}`]; - } else if ( - node.type === 'Paragraph' && - node.children && - node.children[0] && - node.children[0].type === 'Image' - ) { - return 'atomic'; - } else if ( - node.type === 'Paragraph' && - node.raw && - node.raw.match(/^\[\[\s\S+\s.*\S+\s\]\]/) - ) { - return 'atomic'; - } - return blockStyles[style]; -}; - -const joinCodeBlocks = splitMd => { - const opening = splitMd.indexOf('```'); - const closing = splitMd.indexOf('```', opening + 1); - - if (opening >= 0 && closing >= 0) { - const codeBlock = splitMd.slice(opening, closing + 1); - const codeBlockJoined = codeBlock.join('\n'); - const updatedSplitMarkdown = [ - ...splitMd.slice(0, opening), - codeBlockJoined, - ...splitMd.slice(closing + 1), - ]; - - return joinCodeBlocks(updatedSplitMarkdown); - } - - return splitMd; -}; - -const splitMdBlocks = md => { - const splitMd = md.split('\n'); - - // Process the split markdown include the - // one syntax where there's an block level opening - // and closing symbol with content in the middle. - const splitMdWithCodeBlocks = joinCodeBlocks(splitMd); - return splitMdWithCodeBlocks; -}; - -const parseMdLine = (line, existingEntities, extraStyles = {}) => { - const inlineStyles = { ...defaultInlineStyles, ...extraStyles.inlineStyles }; - const blockStyles = { ...defaultBlockStyles, ...extraStyles.blockStyles }; - - const astString = parse(line); - let text = ''; - const inlineStyleRanges = []; - const entityRanges = []; - const entityMap = existingEntities; - - const addInlineStyleRange = (offset, length, style) => { - inlineStyleRanges.push({ offset, length, style }); - }; - - const getRawLength = children => - children.reduce( - (prev, current) => prev + (current.value ? current.value.length : 0), - 0 - ); - - const addLink = child => { - const entityKey = Object.keys(entityMap).length; - entityMap[entityKey] = { - type: 'LINK', - mutability: 'MUTABLE', - data: { - url: child.url, - }, - }; - entityRanges.push({ - key: entityKey, - length: getRawLength(child.children), - offset: text.length, - }); - }; - - const addImage = child => { - const entityKey = Object.keys(entityMap).length; - entityMap[entityKey] = { - type: 'IMAGE', - mutability: 'IMMUTABLE', - data: { - url: child.url, - src: child.url, - fileName: child.alt || '', - }, - }; - entityRanges.push({ - key: entityKey, - length: 1, - offset: text.length, - }); - }; - - const addVideo = child => { - const string = child.raw; - - // RegEx: [[ embed url= ]] - const url = string.match(/^\[\[\s(?:embed)\s(?:url=(\S+))\s\]\]/)[1]; - - const entityKey = Object.keys(entityMap).length; - entityMap[entityKey] = { - type: 'draft-js-video-plugin-video', - mutability: 'IMMUTABLE', - data: { - src: url, - }, - }; - entityRanges.push({ - key: entityKey, - length: 1, - offset: text.length, - }); - }; - - const parseChildren = (child, style) => { - // RegEx: [[ embed url= ]] - const videoShortcodeRegEx = /^\[\[\s(?:embed)\s(?:url=(\S+))\s\]\]/; - switch (child.type) { - case 'Link': - addLink(child); - break; - case 'Image': - addImage(child); - break; - case 'Paragraph': - if (videoShortcodeRegEx.test(child.raw)) { - addVideo(child); - } - break; - default: - } - if (!videoShortcodeRegEx.test(child.raw) && child.children && style) { - const rawLength = getRawLength(child.children); - addInlineStyleRange(text.length, rawLength, style.type); - const newStyle = inlineStyles[child.type]; - child.children.forEach(grandChild => { - parseChildren(grandChild, newStyle); - }); - } else if (!videoShortcodeRegEx.test(child.raw) && child.children) { - const newStyle = inlineStyles[child.type]; - child.children.forEach(grandChild => { - parseChildren(grandChild, newStyle); - }); - } else { - if (style) { - addInlineStyleRange(text.length, child.value.length, style.type); - } - if (inlineStyles[child.type]) { - addInlineStyleRange( - text.length, - child.value.length, - inlineStyles[child.type].type - ); - } - - if (child.type === 'Str') { - let i = 0; - let startIndex = null; - let finalText = ''; - - const REGEXPS = [ - { - regexp: /^(\+)/, - type: inlineStyles.STRIKETHROUGH.type, - }, - { - regexp: /^((bg)?color-rgb\(\d*,\d*,\d*\))/, - }, - ]; - - let removedSymbolLength = 0; - const regexpPredicate = reg => reg.regexp.test(child.value.substr(i)); - while (i < child.value.length) { - const regexpConfig = REGEXPS.find(regexpPredicate); - if (regexpConfig) { - const matches = child.value.substr(i).match(regexpConfig.regexp); - const symbol = matches[1]; - if (startIndex === null) { - startIndex = i + text.length - removedSymbolLength; - i += symbol.length - 1; - } else { - addInlineStyleRange( - startIndex, - i - - startIndex - - symbol.length - - removedSymbolLength + - text.length, - regexpConfig.type || symbol - ); - removedSymbolLength += symbol.length * 2; - - startIndex = null; - i += symbol.length - 1; - } - } else { - finalText += child.value[i]; - } - - i++; - } - - text = `${text}${finalText}`; - } else { - text = `${text}${ - child.type === 'Image' || videoShortcodeRegEx.test(child.raw) - ? ' ' - : child.value - }`; - } - } - }; - - astString.children.forEach(child => { - const style = inlineStyles[child.type]; - parseChildren(child, style); - }); - - // add block style if it exists - let blockStyle = 'unstyled'; - if (astString.children[0]) { - const style = getBlockStyleForMd(astString.children[0], blockStyles); - if (style) { - blockStyle = style; - } - } - - return { - text, - inlineStyleRanges, - entityRanges, - blockStyle, - entityMap, - }; -}; - -export function mdToDraftjs(mdString, extraStyles) { - const paragraphs = splitMdBlocks(mdString); - const blocks = []; - let entityMap = {}; - - paragraphs.forEach(paragraph => { - const result = parseMdLine(paragraph, entityMap, extraStyles); - blocks.push({ - text: result.text, - type: result.blockStyle, - depth: 0, - inlineStyleRanges: result.inlineStyleRanges, - entityRanges: result.entityRanges, - }); - entityMap = result.entityMap; - }); - - // add a default value - // not sure why that's needed but Draftjs convertToRaw fails without it - if (Object.keys(entityMap).length === 0) { - entityMap = { - data: '', - mutability: '', - type: '', - }; - } - return { - blocks, - entityMap, - }; -}