diff --git a/app/static/js/CorpusAnalysis/QueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder.js index 379a39ea..b37cdf14 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder.js @@ -34,7 +34,6 @@ class ConcordanceQueryBuilder { { onOpenStart: () => { this.tokenAttributeBuilderFunctions.preparePositionalAttrModal(); - this.tokenAttributeBuilderFunctions.optionToggleHandler(); }, onCloseStart: () => { this.tokenAttributeBuilderFunctions.resetPositionalAttrModal(); diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js index bfd6fd86..157ca2a7 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js @@ -19,17 +19,13 @@ class ElementReferencesQueryBuilder { // Token Attribute Builder Elements this.positionalAttrModal = M.Modal.getInstance(document.querySelector('#corpus-analysis-concordance-positional-attr-modal')); this.positionalAttrSelection = document.querySelector('#corpus-analysis-concordance-positional-attr-selection'); + this.tokenBuilderContent = document.querySelector('#corpus-analysis-concordance-token-builder-content'); this.tokenQuery = document.querySelector('#corpus-analysis-concordance-token-query'); + this.tokenQueryTemplate = document.querySelector('#corpus-analysis-concordance-token-query-template'); this.tokenSubmitButton = document.querySelector('#corpus-analysis-concordance-token-submit'); this.noValueMessage = document.querySelector('#corpus-analysis-concordance-no-value-message'); this.isTokenQueryInvalid = false; - this.wordInput = document.querySelector('#corpus-analysis-concordance-word-input'); - this.lemmaInput = document.querySelector('#corpus-analysis-concordance-lemma-input'); - this.englishPosSelection = document.querySelector('#corpus-analysis-concordance-english-pos-selection'); - this.germanPosSelection = document.querySelector('#corpus-analysis-concordance-german-pos-selection'); - this.simplePosSelection = document.querySelector('#corpus-analysis-concordance-simple-pos-selection'); - this.ignoreCaseCheckbox = document.querySelector('#corpus-analysis-concordance-ignore-case-checkbox'); } } diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js index 769a7b6a..f6c0004e 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js @@ -34,7 +34,9 @@ class GeneralFunctionsQueryBuilder { resetMaterializeSelection(selectionElements, value = "default") { selectionElements.forEach(selectionElement => { - selectionElement.querySelector(`option[value=${value}]`).selected = true; + if (selectionElement.querySelector(`option[value=${value}]`) !== null) { + selectionElement.querySelector(`option[value=${value}]`).selected = true; + } let instance = M.FormSelect.getInstance(selectionElement); instance.destroy(); M.FormSelect.init(selectionElement); diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js index e11d4731..74d604eb 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js @@ -12,13 +12,7 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu button.addEventListener('click', () => {this.actionButtonInOptionSectionHandler(button.dataset.optionsAction);}); }); - // Eventlistener for kind of token this.elements.tokenSubmitButton.addEventListener('click', () => {this.addTokenToQuery();}); - this.elements.wordInput.addEventListener('input', () => {this.optionToggleHandler();}); - this.elements.lemmaInput.addEventListener('input', () => {this.optionToggleHandler();}); - this.elements.englishPosSelection.addEventListener('change', () => {this.optionToggleHandler();}); - this.elements.germanPosSelection.addEventListener('change', () => {this.optionToggleHandler();}); - this.elements.simplePosSelection.addEventListener('change', () => {this.optionToggleHandler();}); } resetPositionalAttrModal() { @@ -28,32 +22,38 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu - + `; this.elements.positionalAttrSelection.innerHTML = originalSelectionList; this.elements.tokenQuery.innerHTML = ''; - this.toggleClass(['word', 'lemma', 'english-pos', 'german-pos', 'simple-pos'], 'hide', 'add'); - this.toggleClass(['word', 'input-field-options'], 'hide', 'remove'); + this.elements.tokenBuilderContent.innerHTML = ''; + this.toggleClass(['input-field-options'], 'hide', 'remove'); this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add'); - - document.querySelector('#corpus-analysis-concordance-positional-attr-selection option[value="word"]').selected = true; - this.elements.wordInput.value = ''; - this.elements.lemmaInput.value = ''; - this.resetMaterializeSelection([this.elements.englishPosSelection, this.elements.germanPosSelection, this.elements.simplePosSelection]); this.resetMaterializeSelection([this.elements.positionalAttrSelection], "word"); + this.elements.ignoreCaseCheckbox.checked = false; this.elements.editingModusOn = false; this.elements.editedQueryChipElementIndex = undefined; } preparePositionalAttrModal() { let selection = this.elements.positionalAttrSelection.value; - this.toggleClass(['word', 'lemma', 'english-pos', 'german-pos', 'simple-pos'], 'hide', 'add'); if (selection !== 'empty-token') { - this.toggleClass([selection], 'hide', 'remove'); - this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add'); - this.resetMaterializeSelection([this.elements.englishPosSelection, this.elements.germanPosSelection, this.elements.simplePosSelection]); + let selectionTemplate = document.querySelector(`.token-builder-section[data-token-builder-section="${selection}"]`); + let selectionTemplateClone = selectionTemplate.content.cloneNode(true); + + this.elements.tokenBuilderContent.innerHTML = ''; + this.elements.tokenBuilderContent.appendChild(selectionTemplateClone); + if (this.elements.tokenBuilderContent.querySelector('select') !== null) { + let selectElement = this.elements.tokenBuilderContent.querySelector('select'); + M.FormSelect.init(selectElement); + selectElement.addEventListener('change', () => {this.optionToggleHandler();}); + } else { + this.elements.tokenBuilderContent.querySelector('input').addEventListener('input', () => {this.optionToggleHandler();}); + } } + this.optionToggleHandler(); + if (selection === 'word' || selection === 'lemma') { this.toggleClass(['input-field-options'], 'hide', 'remove'); } else if (selection === 'empty-token'){ @@ -63,35 +63,22 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu } } - tokenInputCheck() { - let input; - - if (!document.querySelector('[data-toggle-area="word"]').classList.contains('hide')) { - input = this.elements.wordInput; - } else if (!document.querySelector('[data-toggle-area="lemma"]').classList.contains('hide')){ - input = this.elements.lemmaInput; - } else if (!document.querySelector('[data-toggle-area="english-pos"]').classList.contains('hide')){ - input = this.elements.englishPosSelection; - } else if (!document.querySelector('[data-toggle-area="german-pos"]').classList.contains('hide')){ - input = this.elements.germanPosSelection; - } else if (!document.querySelector('[data-toggle-area="simple-pos"]').classList.contains('hide')){ - input = this.elements.simplePosSelection; - } - - return input; + tokenInputCheck(elem) { + return elem.querySelector('select') !== null ? elem.querySelector('select') : elem.querySelector('input'); } optionToggleHandler() { - let input = this.tokenInputCheck(); + let input = this.tokenInputCheck(this.elements.tokenBuilderContent); if ((input.value === '' || input.value === 'default') && this.elements.editingModusOn === false) { this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add'); + } else if (this.elements.positionalAttrSelection.querySelectorAll('option').length === 1) { + this.toggleClass(['and'], 'disabled', 'add'); } else { this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'remove'); } } disableTokenSubmit() { - this.elements.isTokenQueryInvalid = true; this.elements.tokenSubmitButton.classList.add('red'); this.elements.noValueMessage.classList.remove('hide'); setTimeout(() => { @@ -102,93 +89,52 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu }, 3000); } - tokenChipFactory(prettyQueryText, tokenText) { - tokenText = encodeURI(tokenText); - let builderElement; - let queryChipElement; - builderElement = document.createElement('div'); - builderElement.innerHTML = ` -
- ${prettyQueryText} - close -
`; - queryChipElement = builderElement.firstElementChild; - this.elements.tokenQuery.appendChild(queryChipElement); - } - addTokenToQuery() { - let c = this.elements.ignoreCaseCheckbox.checked ? ' %c' : ''; let tokenQueryPrettyText = ''; let tokenQueryCQLText = ''; - this.elements.isTokenQueryInvalid = false; - - this.elements.tokenQuery.childNodes.forEach(element => { - tokenQueryPrettyText += ' ' + element.firstChild.data + ' '; - tokenQueryCQLText += decodeURI(element.dataset.tokentext); + let input; + let kindOfToken = this.kindOfTokenCheck(this.elements.positionalAttrSelection.value); + + // Takes all rows of the token query (if there is a query concatenation). + // Adds their contents to tokenQueryPrettyText and tokenQueryCQLText, which will later be expanded with the current input field. + let tokenQueryRows = this.elements.tokenQuery.querySelectorAll('.row'); + tokenQueryRows.forEach(row => { + let ignoreCaseCheckbox = row.querySelector('input[type="checkbox"]'); + let c = ignoreCaseCheckbox !== null && ignoreCaseCheckbox.checked ? ' %c' : ''; + let tokenQueryRowInput = this.tokenInputCheck(row.querySelector('.token-query-template-content')); + let tokenQueryKindOfToken = this.kindOfTokenCheck(tokenQueryRowInput.closest('.input-field').dataset.kindOfToken); + let tokenConditionPrettyText = row.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText; + let tokenConditionCQLText = row.querySelector('[data-condition-cql-text]').dataset.conditionCqlText; + tokenQueryPrettyText += `${tokenQueryKindOfToken}=${tokenQueryRowInput.value}${c} ${tokenConditionPrettyText} `; + tokenQueryCQLText += `${tokenQueryKindOfToken}="${tokenQueryRowInput.value}"${c} ${tokenConditionCQLText}`; }); - switch (this.elements.positionalAttrSelection.value) { - case 'word': - if (this.elements.wordInput.value === '') { - this.disableTokenSubmit(); - } else { - tokenQueryPrettyText += `word=${this.elements.wordInput.value}${c}`; - tokenQueryCQLText += `word="${this.elements.wordInput.value}"${c}`; - this.elements.wordInput.value = ''; - } - break; - case 'lemma': - if (this.elements.lemmaInput.value === '') { - this.disableTokenSubmit(); - } else { - tokenQueryPrettyText += `lemma=${this.elements.lemmaInput.value}${c}`; - tokenQueryCQLText += `lemma="${this.elements.lemmaInput.value}"${c}`; - this.elements.lemmaInput.value = ''; - } - break; - case 'english-pos': - if (this.elements.englishPosSelection.value === 'default') { - this.disableTokenSubmit(); - } else { - tokenQueryPrettyText += `pos=${this.elements.englishPosSelection.value}`; - tokenQueryCQLText += `pos="${this.elements.englishPosSelection.value}"`; - this.elements.englishPosSelection.value = ''; - } - break; - case 'german-pos': - if (this.elements.germanPosSelection.value === 'default') { - this.disableTokenSubmit(); - } else { - tokenQueryPrettyText += `pos=${this.elements.germanPosSelection.value}`; - tokenQueryCQLText += `pos="${this.elements.germanPosSelection.value}"`; - this.elements.germanPosSelection.value = ''; - } - break; - case 'simple-pos': - if (this.elements.simplePosSelection.value === 'default') { - this.disableTokenSubmit(); - } else { - tokenQueryPrettyText += `simple_pos=${this.elements.simplePosSelection.value}`; - tokenQueryCQLText += `simple_pos="${this.elements.simplePosSelection.value}"`; - this.elements.simplePosSelection.value = ''; - } - break; - case 'empty-token': - tokenQueryPrettyText += 'empty token'; - default: - break; + if (kindOfToken === 'empty-token') { + tokenQueryPrettyText += 'empty token'; + } else { + let c = this.elements.ignoreCaseCheckbox.checked ? ' %c' : ''; + input = this.tokenInputCheck(this.elements.tokenBuilderContent); + tokenQueryPrettyText += `${kindOfToken}=${input.value}${c}`; + tokenQueryCQLText += `${kindOfToken}="${input.value}"${c}`; } // isTokenQueryInvalid looks if a valid value is passed. If the input fields/dropdowns are empty (isTokenQueryInvalid === true), no token is added. - if (this.elements.isTokenQueryInvalid === false) { - tokenQueryCQLText = '[' + tokenQueryCQLText + ']'; + if ((input.value === '' || input.value === 'default') && this.elements.positionalAttrSelection.value !== 'empty-token') { + this.disableTokenSubmit(); + } else { + tokenQueryCQLText = `[${tokenQueryCQLText}]`; this.submitQueryChipElement('token', tokenQueryPrettyText, tokenQueryCQLText, null, false, true); this.elements.positionalAttrModal.close(); } } + kindOfTokenCheck(kindOfToken) { + return kindOfToken === 'english-pos' || kindOfToken === 'german-pos' ? 'pos' : kindOfToken; + } + + actionButtonInOptionSectionHandler(elem) { - let input = this.tokenInputCheck(); + let input = this.tokenInputCheck(this.elements.tokenBuilderContent); switch (elem) { case 'option-group': input.value += '(option1|option2)'; @@ -201,10 +147,10 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu input.value += '.'; break; case 'and': - this.conditionHandler('and', " & "); + this.conditionHandler('and'); break; case 'or': - this.conditionHandler('or', " | "); + this.conditionHandler('or'); break; default: break; @@ -213,32 +159,8 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu } characterIncidenceModifierHandler(elem) { - // For word and lemma, the incidence modifiers are inserted in the input field. For the others, one or two chips are created which contain the respective value of the token and the incidence modifier. - switch (this.elements.positionalAttrSelection.value) { - case 'empty-token': - this.tokenChipFactory(elem.innerText, elem.dataset.token); - break; - case 'english-pos': - this.tokenChipFactory(`pos=${this.elements.englishPosSelection.value}`, `pos="${this.elements.englishPosSelection.value}"`); - this.tokenChipFactory(elem.innerText, elem.dataset.token); - break; - case 'german-pos': - this.tokenChipFactory(`pos=${this.elements.germanPosSelection.value}`, `pos="${this.elements.germanPosSelection.value}"`); - this.tokenChipFactory(elem.innerText, elem.dataset.token); - break; - case 'simple-pos': - this.tokenChipFactory(`simple_pos=${this.elements.simplePosSelection.value}`, `simple_pos="${this.elements.simplePosSelection.value}"`); - this.tokenChipFactory(elem.innerText, elem.dataset.token); - break; - default: - let input = this.tokenInputCheck(); - input.value += elem.dataset.token; - break; - } - - if (this.elements.positionalAttrSelection.value !== "word" && this.elements.positionalAttrSelection.value !== "lemma") { - this.toggleClass([this.elements.positionalAttrSelection.value], "hide", "add"); - } + let input = this.tokenInputCheck(this.elements.tokenBuilderContent); + input.value += elem.dataset.token; } characterNMSubmitHandler(modalId) { @@ -250,89 +172,96 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu let instance = M.Modal.getInstance(modal); instance.close(); - - switch (this.elements.positionalAttrSelection.value) { - case 'word': - this.elements.wordInput.value += '{' + input + '}'; - break; - case 'lemma': - this.elements.lemmaInput.value += '{' + input + '}'; - break; - default: - break; - } + let tokenInput = this.tokenInputCheck(this.elements.tokenBuilderContent); + tokenInput.value += '{' + input + '}'; } - conditionHandler(conditionText, conditionQueryContent) { - let tokenQueryPrettyText; - let tokenQueryCQLText; - let c = this.elements.ignoreCaseCheckbox.checked ? ' %c' : ''; - - switch (this.elements.positionalAttrSelection.value) { - case 'word': - tokenQueryPrettyText = `word=${this.elements.wordInput.value}${c}`; - tokenQueryCQLText = `word="${this.elements.wordInput.value}"${c}`; - this.elements.wordInput.value = ''; - break; - case 'lemma': - tokenQueryPrettyText = `lemma=${this.elements.lemmaInput.value}${c}`; - tokenQueryCQLText = `lemma="${this.elements.lemmaInput.value}"${c}`; - this.elements.lemmaInput.value = ''; - break; - case 'english-pos': - tokenQueryPrettyText = `pos=${this.elements.englishPosSelection.value}`; - tokenQueryCQLText = `pos="${this.elements.englishPosSelection.value}"`; - this.elements.englishPosSelection.value = ''; - break; - case 'german-pos': - tokenQueryPrettyText = `pos=${this.elements.germanPosSelection.value}`; - tokenQueryCQLText = `pos="${this.elements.germanPosSelection.value}"`; - this.elements.germanPosSelection.value = ''; - break; - case 'simple-pos': - tokenQueryPrettyText = `simple_pos=${this.elements.simplePosSelection.value}`; - tokenQueryCQLText = `simple_pos="${this.elements.simplePosSelection.value}"`; - this.elements.simplePosSelection.value = ''; - break; - default: - break; - } + conditionHandler(conditionText) { + let tokenQueryTemplateClone = this.elements.tokenQueryTemplate.content.cloneNode(true); + tokenQueryTemplateClone.querySelector('.token-query-template-content').appendChild(this.elements.tokenBuilderContent.firstElementChild); + let notSelectedButton = tokenQueryTemplateClone.querySelector(`[data-condition-pretty-text]:not([data-condition-pretty-text="${conditionText}"])`); + let deleteButton = tokenQueryTemplateClone.querySelector(`[data-token-query-content-action="delete"]`); + deleteButton.addEventListener('click', (event) => { + this.deleteTokenQueryRow(event.target); + }); + notSelectedButton.parentNode.removeChild(notSelectedButton); + this.elements.tokenQuery.appendChild(tokenQueryTemplateClone); // Deleting the options which do not make sense in the context of the condition like "word" AND "word". Also sets selection default. let selectionDefault = "word"; let optionDeleteList = ['empty-token']; if (conditionText === 'and') { - if (this.elements.positionalAttrSelection.value === 'word' || this.elements.positionalAttrSelection.value === 'lemma') { - selectionDefault = "english-pos"; - optionDeleteList.push('word', 'lemma'); - } else if (this.elements.positionalAttrSelection.value === 'english-pos' || this.elements.positionalAttrSelection.value === 'german-pos') { - optionDeleteList.push('english-pos', 'german-pos'); - } else { - optionDeleteList.push('simple-pos'); + switch (this.elements.positionalAttrSelection.value) { + case 'english-pos' || 'german-pos': + optionDeleteList.push('english-pos', 'german-pos'); + break; + default: + optionDeleteList.push(this.elements.positionalAttrSelection.value); + break; } + } else { + let originalSelectionList = + ` + + + + + + `; + this.elements.positionalAttrSelection.innerHTML = originalSelectionList; + M.FormSelect.init(this.elements.positionalAttrSelection); } - - this.resetMaterializeSelection([this.elements.englishPosSelection, this.elements.germanPosSelection, this.elements.simplePosSelection]); - - this.tokenChipFactory(tokenQueryPrettyText, tokenQueryCQLText); - this.tokenChipFactory(conditionText, conditionQueryContent); + let lastTokenQueryRow = this.elements.tokenQuery.lastElementChild; + if(lastTokenQueryRow.querySelector('[data-kind-of-token="word"]') || lastTokenQueryRow.querySelector('[data-kind-of-token="lemma"]')) { + this.appendIgnoreCaseCheckbox(lastTokenQueryRow.querySelector('.token-query-template-content'), this.elements.ignoreCaseCheckbox.checked); + } + this.elements.ignoreCaseCheckbox.checked = false; this.setTokenSelection(selectionDefault, optionDeleteList); } + deleteTokenQueryRow(deleteButton) { + let deletedRow = deleteButton.closest('.row'); + let condition = deletedRow.querySelector('[data-condition-pretty-text]').dataset.conditionPrettyText; + if (condition === 'and') { + let kindOfToken = deletedRow.querySelector('[data-kind-of-token]').dataset.kindOfToken; + switch (kindOfToken) { + case 'english-pos' || 'german-pos': + this.createOptionElementForPosAttrSelection('english-pos'); + this.createOptionElementForPosAttrSelection('german-pos'); + break; + default: + this.createOptionElementForPosAttrSelection(kindOfToken); + break; + } + M.FormSelect.init(this.elements.positionalAttrSelection); + } + deletedRow.remove(); + } + + createOptionElementForPosAttrSelection(kindOfToken) { + let option = document.createElement('option'); + option.value = kindOfToken; + option.text = kindOfToken; + this.elements.positionalAttrSelection.appendChild(option); + } + + appendIgnoreCaseCheckbox(parentElement, checked = false) { + let ignoreCaseCheckboxClone = document.querySelector('#ignore-case-checkbox-template').content.cloneNode(true); + parentElement.appendChild(ignoreCaseCheckboxClone); + M.Tooltip.init(parentElement.querySelectorAll('.tooltipped')); + if (checked) { + parentElement.querySelector('input[type="checkbox"]').checked = true; + } + } + setTokenSelection(selection, optionDeleteList) { optionDeleteList.forEach(option => { - this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`).remove(); + if (this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`) !== null) { + this.elements.positionalAttrSelection.querySelector(`option[value=${option}]`).remove(); + } }); this.resetMaterializeSelection([this.elements.positionalAttrSelection], selection); - - this.toggleClass(['word', 'lemma', 'english-pos', 'german-pos', 'simple-pos'], 'hide', 'add'); - this.toggleClass([selection], 'hide', 'remove'); - this.toggleClass(['incidence-modifiers', 'or', 'and'], 'disabled', 'add'); - if (selection === "word" || selection === "lemma") { - this.toggleClass(['input-field-options'], 'hide', 'remove'); - } else { - this.toggleClass(['input-field-options'], 'hide', 'add'); - } + this.preparePositionalAttrModal(); } } diff --git a/app/templates/corpora/_analysis/concordance.html.j2 b/app/templates/corpora/_analysis/concordance.html.j2 index 23e1c5b6..b160c2fb 100644 --- a/app/templates/corpora/_analysis/concordance.html.j2 +++ b/app/templates/corpora/_analysis/concordance.html.j2 @@ -17,10 +17,10 @@
-
+
Query search
-
+

-
-
-
- -
-
- mode_edit - -
+
+ +
+
-
-
- mode_edit - -
-
+ -
-
-
-
- - -
-
-
-
- -
-
-
-
- - -
-
-
+ -
-
-
-
- - -
-
-
+ -
-

- send -

+ + + -
No value entered!
+ + +
+ or + and +

+ send +

-
-
-
Options to edit your token: help_outline
-
-

-
-
- Wildcard character - Option Group - incidence modifiers - - - -
-
- or - and -
- - +
No value entered!
+
+
+
+
Options to edit your token: help_outline
+
+

+
+
+ Wildcard character + Option Group + incidence modifiers + + +
+ + +