diff --git a/package.json b/package.json
index 68f4b9d7cd..010bf54b45 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"diff-match-patch": "^1.0.5",
"draft-js": "^0.11.4",
"flux": "^4.0.1",
+ "format-message-parse": "^6.2.4",
"immutability-helper": "^3.1.1",
"immutable": "^4.0.0-rc.12",
"jquery": "^3.4.1",
diff --git a/public/css/sass/components/segment/IcuHighlight.scss b/public/css/sass/components/segment/IcuHighlight.scss
new file mode 100644
index 0000000000..1e59541182
--- /dev/null
+++ b/public/css/sass/components/segment/IcuHighlight.scss
@@ -0,0 +1,26 @@
+@import '../../commons/colors';
+
+
+.icuItem {
+ display: inline-block;
+ position: relative;
+ color: $linkBlue;
+ &.icuItem-error {
+ color: $redDefault;
+ cursor: pointer;
+ }
+}
+
+.icu-tooltip {
+ padding: 6px;
+ max-width: 400px;
+ h3 {
+ font-size: 16px;
+ margin: 0;
+ margin-bottom: 4px;
+ }
+ p {
+ white-space: normal;
+ }
+}
+
diff --git a/public/css/sass/main.scss b/public/css/sass/main.scss
index 852c5e5bbb..21bf247f41 100644
--- a/public/css/sass/main.scss
+++ b/public/css/sass/main.scss
@@ -24,6 +24,7 @@
@import 'components/segment/Tag';
@import 'components/segment/TooltipInfo';
@import 'components/segment/Glossary';
+@import 'components/segment/IcuHighlight';
@import 'components/segment/Editor';
@import 'components/bulk-approve-bar/bulk_approve_bar';
@import 'components/header/search';
diff --git a/public/js/cat_source/es6/components/segments/Editarea.js b/public/js/cat_source/es6/components/segments/Editarea.js
index c20d6de1fb..964a879b00 100644
--- a/public/js/cat_source/es6/components/segments/Editarea.js
+++ b/public/js/cat_source/es6/components/segments/Editarea.js
@@ -24,7 +24,7 @@ import insertTag from './utils/DraftMatecatUtils/TagMenu/insertTag'
import checkForMissingTags from './utils/DraftMatecatUtils/TagMenu/checkForMissingTag'
import updateEntityData from './utils/DraftMatecatUtils/updateEntityData'
import LexiqaUtils from '../../utils/lxq.main'
-import updateLexiqaWarnings from './utils/DraftMatecatUtils/updateLexiqaWarnings'
+import updateOffsetBasedOnEditorState from './utils/DraftMatecatUtils/updateOffsetBasedOnEditorState'
import {tagSignatures} from './utils/DraftMatecatUtils/tagModel'
import SegmentActions from '../../actions/SegmentActions'
import getFragmentFromSelection from './utils/DraftMatecatUtils/DraftSource/src/component/handlers/edit/getFragmentFromSelection'
@@ -39,6 +39,11 @@ import {
isSelectedEntity,
getEntitiesSelected,
} from './utils/DraftMatecatUtils/manageCaretPositionNearEntity'
+import {
+ createICUDecorator,
+ createIcuTokens,
+ isEqualICUTokens,
+} from './utils/DraftMatecatUtils/createICUDecorator'
const {hasCommandModifier, isOptionKeyCommand, isCtrlKeyCommand} =
KeyBindingUtil
@@ -65,7 +70,16 @@ class Editarea extends React.Component {
constructor(props) {
super(props)
- const {onEntityClick, updateTagsInEditor, getUpdatedSegmentInfo} = this
+ const {onEntityClick, getUpdatedSegmentInfo} = this
+
+ const translation = this.props.translation
+
+ // If GuessTag is Enabled, clean translation from tags
+ const cleanTranslation = SegmentUtils.checkCurrentSegmentTPEnabled(
+ this.props.segment,
+ )
+ ? DraftMatecatUtils.removeTagsFromText(translation)
+ : translation
this.decoratorsStructure = [
{
@@ -83,16 +97,7 @@ class Editarea extends React.Component {
},
]
const decorator = new CompositeDecorator(this.decoratorsStructure)
- //const decorator = new CompoundDecorator(this.decoratorsStructure);
- // Escape html
- const translation = this.props.translation
- // If GuessTag is Enabled, clean translation from tags
- const cleanTranslation = SegmentUtils.checkCurrentSegmentTPEnabled(
- this.props.segment,
- )
- ? DraftMatecatUtils.removeTagsFromText(translation)
- : translation
// Inizializza Editor State con solo testo
const plainEditorState = EditorState.createEmpty(decorator)
const contentEncoded = DraftMatecatUtils.encodeContent(
@@ -121,6 +126,7 @@ class Editarea extends React.Component {
[DraftMatecatConstants.LEXIQA_DECORATOR]: false,
[DraftMatecatConstants.QA_BLACKLIST_DECORATOR]: false,
[DraftMatecatConstants.SEARCH_DECORATOR]: false,
+ [DraftMatecatConstants.ICU_DECORATOR]: true,
},
previousSourceTagMap: null,
}
@@ -136,9 +142,7 @@ class Editarea extends React.Component {
this.updateTranslationInStore,
100,
)
- this.updateTagsInEditorDebounced = debounce(updateTagsInEditor, 500)
this.onCompositionStopDebounced = debounce(this.onCompositionStop, 1000)
- this.focusEditorDebounced = debounce(this.focusEditor, 500)
}
getSearchParams = () => {
@@ -166,6 +170,15 @@ class Editarea extends React.Component {
}
}
+ addIcuDecorator = (tokens) => {
+ const newDecorator = createICUDecorator(tokens)
+ remove(
+ this.decoratorsStructure,
+ (decorator) => decorator.name === DraftMatecatConstants.ICU_DECORATOR,
+ )
+ this.decoratorsStructure.push(newDecorator)
+ }
+
addSearchDecorator = () => {
let {tagRange} = this.state
let {searchParams, occurrencesInSearch, currentInSearchIndex} =
@@ -208,7 +221,10 @@ class Editarea extends React.Component {
lxqDecodedTranslation,
false,
)
- const updatedLexiqaWarnings = updateLexiqaWarnings(editorState, ranges)
+ const updatedLexiqaWarnings = updateOffsetBasedOnEditorState(
+ editorState,
+ ranges,
+ )
if (updatedLexiqaWarnings.length > 0) {
const newDecorator = DraftMatecatUtils.activateLexiqa(
editorState,
@@ -411,6 +427,18 @@ class Editarea extends React.Component {
changedDecorator = true
this.removeDecorator(DraftMatecatConstants.SEARCH_DECORATOR)
}
+ const contentState = editorState.getCurrentContent()
+ const plainText = contentState.getPlainText()
+ const icuTokens = createIcuTokens(plainText, editorState)
+ if (
+ !prevProps ||
+ !this.prevIcuTokens ||
+ !isEqualICUTokens(icuTokens, this.prevIcuTokens)
+ ) {
+ this.prevIcuTokens = icuTokens
+ changedDecorator = true
+ this.addIcuDecorator(icuTokens)
+ }
} else {
//Search
if (
@@ -837,8 +865,8 @@ class Editarea extends React.Component {
? 'left'
: 'right'
: !isRTL
- ? 'right'
- : 'left'
+ ? 'right'
+ : 'left'
const updatedStateNearZwsp = checkCaretIsNearZwsp({
editorState: this.state.editorState,
diff --git a/public/js/cat_source/es6/components/segments/IcuHighlight.js b/public/js/cat_source/es6/components/segments/IcuHighlight.js
new file mode 100644
index 0000000000..987688f5f5
--- /dev/null
+++ b/public/js/cat_source/es6/components/segments/IcuHighlight.js
@@ -0,0 +1,27 @@
+import React, {useRef} from 'react'
+import Tooltip from '../common/Tooltip'
+
+export const IcuHighlight = ({start, end, tokens, children}) => {
+ const token = tokens.find((item) => item.start === start && item.end === end)
+ const refToken = useRef()
+ return (
+
+ {token.type === 'error' ? (
+
+ ICU syntax error
+ {token.message}
+
+ }
+ >
+ {children}
+
+ ) : (
+ {children}
+ )}
+
+ )
+}
diff --git a/public/js/cat_source/es6/components/segments/SegmentSource.js b/public/js/cat_source/es6/components/segments/SegmentSource.js
index d6c72f5c0d..c53409cbc4 100644
--- a/public/js/cat_source/es6/components/segments/SegmentSource.js
+++ b/public/js/cat_source/es6/components/segments/SegmentSource.js
@@ -12,7 +12,7 @@ import DraftMatecatUtils from './utils/DraftMatecatUtils'
import * as DraftMatecatConstants from './utils/DraftMatecatUtils/editorConstants'
import SegmentConstants from '../../constants/SegmentConstants'
import LexiqaUtils from '../../utils/lxq.main'
-import updateLexiqaWarnings from './utils/DraftMatecatUtils/updateLexiqaWarnings'
+import updateOffsetBasedOnEditorState from './utils/DraftMatecatUtils/updateOffsetBasedOnEditorState'
import getFragmentFromSelection from './utils/DraftMatecatUtils/DraftSource/src/component/handlers/edit/getFragmentFromSelection'
import {getSplitPointTag} from './utils/DraftMatecatUtils/tagModel'
import {SegmentContext} from './SegmentContext'
@@ -20,6 +20,10 @@ import Assistant from '../icons/Assistant'
import Education from '../icons/Education'
import {TERM_FORM_FIELDS} from './SegmentFooterTabGlossary/SegmentFooterTabGlossary'
import {getEntitiesSelected} from './utils/DraftMatecatUtils/manageCaretPositionNearEntity'
+import {
+ createICUDecorator,
+ createIcuTokens,
+} from './utils/DraftMatecatUtils/createICUDecorator'
class SegmentSource extends React.Component {
static contextType = SegmentContext
@@ -74,6 +78,7 @@ class SegmentSource extends React.Component {
[DraftMatecatConstants.GLOSSARY_DECORATOR]: false,
[DraftMatecatConstants.QA_GLOSSARY_DECORATOR]: false,
[DraftMatecatConstants.SEARCH_DECORATOR]: false,
+ [DraftMatecatConstants.ICU_DECORATOR]: true,
},
isShowingOptionsToolbar: false,
}
@@ -82,6 +87,8 @@ class SegmentSource extends React.Component {
: 0
this.delayAiAssistant
+
+ this.firstIcuCheck = false
}
getSearchParams = () => {
@@ -205,7 +212,10 @@ class SegmentSource extends React.Component {
lxqDecodedSource,
true,
)
- const updatedLexiqaWarnings = updateLexiqaWarnings(editorState, ranges)
+ const updatedLexiqaWarnings = updateOffsetBasedOnEditorState(
+ editorState,
+ ranges,
+ )
if (updatedLexiqaWarnings.length > 0) {
const newDecorator = DraftMatecatUtils.activateLexiqa(
editorState,
@@ -224,6 +234,18 @@ class SegmentSource extends React.Component {
this.removeDecorator(DraftMatecatConstants.LEXIQA_DECORATOR)
}
}
+ addIcuDecorator = () => {
+ const {editorState} = this.state
+ const contentState = editorState.getCurrentContent()
+ const plainText = contentState.getPlainText()
+ const tokens = createIcuTokens(plainText, editorState)
+ const newDecorator = createICUDecorator(tokens)
+ remove(
+ this.decoratorsStructure,
+ (decorator) => decorator.name === DraftMatecatConstants.ICU_DECORATOR,
+ )
+ this.decoratorsStructure.push(newDecorator)
+ }
updateSourceInStore = () => {
if (this.state.source !== '') {
@@ -333,6 +355,11 @@ class SegmentSource extends React.Component {
changedDecorator = true
this.removeDecorator(DraftMatecatConstants.SEARCH_DECORATOR)
}
+ if (!this.firstIcuCheck) {
+ this.firstIcuCheck = true
+ changedDecorator = true
+ this.addIcuDecorator()
+ }
} else {
//Search
if (
@@ -353,9 +380,8 @@ class SegmentSource extends React.Component {
this.removeDecorator()
;(activeDecorators[DraftMatecatConstants.LEXIQA_DECORATOR] = false),
(activeDecorators[DraftMatecatConstants.GLOSSARY_DECORATOR] = false),
- (activeDecorators[
- DraftMatecatConstants.QA_GLOSSARY_DECORATOR
- ] = false),
+ (activeDecorators[DraftMatecatConstants.QA_GLOSSARY_DECORATOR] =
+ false),
this.addSearchDecorator()
activeDecorators[DraftMatecatConstants.SEARCH_DECORATOR] = true
changedDecorator = true
diff --git a/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/createICUDecorator.js b/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/createICUDecorator.js
new file mode 100644
index 0000000000..202f0ddf7d
--- /dev/null
+++ b/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/createICUDecorator.js
@@ -0,0 +1,65 @@
+import * as DraftMatecatConstants from './editorConstants'
+import parse from 'format-message-parse'
+import {IcuHighlight} from '../../IcuHighlight'
+import {isEqual} from 'lodash'
+import updateOffsetBasedOnEditorState from './updateOffsetBasedOnEditorState'
+export const createICUDecorator = (tokens = []) => {
+ return {
+ name: DraftMatecatConstants.ICU_DECORATOR,
+ strategy: (contentBlock, callback) => {
+ const currentText = contentBlock.getText()
+ tokens.forEach((token) => {
+ const subString = currentText.substring(token.start, token.end)
+ if (
+ token.end <= currentText.length &&
+ token.type !== 'text' &&
+ subString === token.text
+ ) {
+ callback(token.start, token.end)
+ }
+ })
+ },
+ component: IcuHighlight,
+ props: {
+ tokens,
+ },
+ }
+}
+
+export const createIcuTokens = (text, editorState) => {
+ const tokens = []
+ let error
+ try {
+ parse(text, {tokens: tokens})
+ } catch (e) {
+ error = {
+ type: 'error',
+ text: e.found,
+ start: e.column,
+ end: e.column + e.found.length,
+ message: e.message,
+ }
+ console.log(e)
+ }
+ let index = 0
+ const updatedTokens = tokens.map((token) => {
+ const value = {
+ type: token[0],
+ text: token[1],
+ start: index,
+ end: index + token[1].length,
+ }
+ index = index + token[1].length
+ return value
+ })
+ if (error) updatedTokens.push(error)
+ return updateOffsetBasedOnEditorState(editorState, updatedTokens)
+}
+
+export const isEqualICUTokens = (tokens, otherTokens) => {
+ const filterTokensFn = (token) => token.type !== 'text'
+ return isEqual(
+ tokens.filter(filterTokensFn),
+ otherTokens.filter(filterTokensFn),
+ )
+}
diff --git a/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/editorConstants.js b/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/editorConstants.js
index 53bb568433..820e227395 100644
--- a/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/editorConstants.js
+++ b/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/editorConstants.js
@@ -8,3 +8,4 @@ export const QA_GLOSSARY_DECORATOR = 'qaCheckGlossary'
export const QA_BLACKLIST_DECORATOR = 'qaCheckBlacklist'
export const SEARCH_DECORATOR = 'search'
export const SPLIT_DECORATOR = 'split'
+export const ICU_DECORATOR = 'icu'
diff --git a/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/updateLexiqaWarnings.js b/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/updateOffsetBasedOnEditorState.js
similarity index 90%
rename from public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/updateLexiqaWarnings.js
rename to public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/updateOffsetBasedOnEditorState.js
index ce2ac85544..a0e64a7de9 100644
--- a/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/updateLexiqaWarnings.js
+++ b/public/js/cat_source/es6/components/segments/utils/DraftMatecatUtils/updateOffsetBasedOnEditorState.js
@@ -1,23 +1,22 @@
import {each, cloneDeep} from 'lodash'
-import getEntities from './getEntities'
-import {isToReplaceForLexiqa} from './tagModel'
+// import getEntities from './getEntities'
-const updateLexiqaWarnings = (editorState, warnings) => {
+const updateOffsetBasedOnEditorState = (editorState, warnings) => {
const contentState = editorState.getCurrentContent()
const blocks = contentState.getBlockMap()
let maxCharsInBlocks = 0
let updatedWarnings = []
- const entities = getEntities(editorState)
+ // const entities = getEntities(editorState)
blocks.forEach((loopedContentBlock) => {
const firstBlockKey = contentState.getFirstBlock().getKey()
const loopedBlockKey = loopedContentBlock.getKey()
// Add current block length
const newLineChar = loopedBlockKey !== firstBlockKey ? 1 : 0
maxCharsInBlocks += loopedContentBlock.getLength() + newLineChar
- const entitiesInBlock = entities.filter(
+ /* const entitiesInBlock = entities.filter(
(ent) => ent.blockKey === loopedBlockKey,
- )
+ )*/
each(warnings, (warn) => {
// Todo: warnings between 2 block are now ignored
const alreadyScannedChars =
@@ -64,4 +63,4 @@ const updateLexiqaWarnings = (editorState, warnings) => {
return updatedWarnings
}
-export default updateLexiqaWarnings
+export default updateOffsetBasedOnEditorState
diff --git a/yarn.lock b/yarn.lock
index f0ee5a9586..f87f2287b2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4130,6 +4130,11 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
+format-message-parse@^6.2.4:
+ version "6.2.4"
+ resolved "https://registry.yarnpkg.com/format-message-parse/-/format-message-parse-6.2.4.tgz#2c9b39a32665bd247cb1c31ba2723932d9edf3f9"
+ integrity sha512-k7WqXkEzgXkW4wkHdS6Cv2Ou0rIFtiDelZjgoe1saW4p7FT7zS8OeAUpAekhormqzpeecR97e4vBft1zMsfFOQ==
+
fs-extra@^11.1.1:
version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"