diff --git a/app/static/css/queryBuilder.css b/app/static/css/queryBuilder.css index 4886e9a2..f48a4a64 100644 --- a/app/static/css/queryBuilder.css +++ b/app/static/css/queryBuilder.css @@ -7,6 +7,10 @@ margin-top: 23px; } +#corpus-analysis-concordance-query-builder-input-field-placeholder { + color: #9E9E9E; +} + .modal-content { overflow-x: hidden; } @@ -109,10 +113,10 @@ } [data-type="start-empty-entity"], [data-type="start-entity"], [data-type="end-entity"] { - background-color: #A6E22D; + background-color: #a6e22d; } -[data-type="start-text-annotation"]{ +[data-type="text-annotation"]{ background-color: #2FBBAB; } diff --git a/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js b/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js index 91ce0f68..2263fbde 100644 --- a/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js +++ b/app/static/js/CorpusAnalysis/CorpusAnalysisConcordance.js @@ -33,7 +33,7 @@ class CorpusAnalysisConcordance { async submitForm(queryModeId) { this.app.disableActionElements(); - let queryBuilderQuery = document.querySelector('#corpus-analysis-concordance-query-preview').innerHTML.trim(); + let queryBuilderQuery = Utils.unescape(document.querySelector('#corpus-analysis-concordance-query-preview').innerHTML.trim()); let expertModeQuery = this.elements.expertModeForm.query.value.trim(); let query = queryModeId === 'corpus-analysis-concordance-expert-mode-form' ? expertModeQuery : queryBuilderQuery; let form = queryModeId === 'corpus-analysis-concordance-expert-mode-form' ? this.elements.expertModeForm : this.elements.queryBuilderForm; diff --git a/app/static/js/CorpusAnalysis/QueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder.js index 51b7653b..8d86c06b 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder.js @@ -7,26 +7,28 @@ class ConcordanceQueryBuilder { this.tokenAttributeBuilderFunctions = new TokenAttributeBuilderFunctionsQueryBuilder(this.elements); this.structuralAttributeBuilderFunctions = new StructuralAttributeBuilderFunctionsQueryBuilder(this.elements); + // Eventlisteners for the incidence modifiers. There are two different types of incidence modifiers: token and character incidence modifiers. document.querySelectorAll('.incidence-modifier-selection').forEach(button => { - if (button.parentNode.parentNode.id === 'corpus-analysis-concordance-token-incidence-modifiers-dropdown') { - button.addEventListener('click', () => { - this.generalFunctions.tokenIncidenceModifierHandler(button.dataset.token, button.innerHTML);}); - } else if (button.parentNode.parentNode.id === 'corpus-analysis-concordance-character-incidence-modifiers-dropdown') { - button.addEventListener('click', () => {this.tokenAttributeBuilderFunctions.characterIncidenceModifierHandler(button);}); + let dropdownId = button.parentNode.parentNode.id; + if (dropdownId === 'corpus-analysis-concordance-token-incidence-modifiers-dropdown') { + button.addEventListener('click', () => this.generalFunctions.tokenIncidenceModifierHandler(button.dataset.token, button.innerHTML)); + } else if (dropdownId === 'corpus-analysis-concordance-character-incidence-modifiers-dropdown') { + button.addEventListener('click', () => this.tokenAttributeBuilderFunctions.characterIncidenceModifierHandler(button)); } }); + + // Eventlisteners for the submit of n- and m-values of the incidence modifier modal for "exactly n" or "between n and m". document.querySelectorAll('.n-m-submit-button').forEach(button => { - if (button.dataset.modalId === 'corpus-analysis-concordance-exactly-n-token-modal' || button.dataset.modalId === 'corpus-analysis-concordance-between-nm-token-modal') { - button.addEventListener('click', () => { - this.generalFunctions.tokenNMSubmitHandler(button.dataset.modalId); - }); - } else if (button.dataset.modalId === 'corpus-analysis-concordance-exactly-n-character-modal' || button.dataset.modalId === 'corpus-analysis-concordance-between-nm-character-modal') { - button.addEventListener('click', () => { - this.generalFunctions.characterNMSubmitHandler(button.dataset.modalId); - }); + let modalId = button.dataset.modalId; + if (modalId === 'corpus-analysis-concordance-exactly-n-token-modal' || modalId === 'corpus-analysis-concordance-between-nm-token-modal') { + button.addEventListener('click', () => this.generalFunctions.tokenNMSubmitHandler(modalId)); + } else if (modalId === 'corpus-analysis-concordance-exactly-n-character-modal' || modalId === 'corpus-analysis-concordance-between-nm-character-modal') { + button.addEventListener('click', () => this.generalFunctions.characterNMSubmitHandler(modalId)); } }); + document.querySelector('#corpus-analysis-concordance-text-annotation-submit').addEventListener('click', () => this.structuralAttributeBuilderFunctions.textAnnotationSubmitHandler()); + this.elements.positionalAttrModal = M.Modal.init( document.querySelector('#corpus-analysis-concordance-positional-attr-modal'), { diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js index c6dcd257..17df5ed2 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/ElementReferencesQueryBuilder.js @@ -9,6 +9,9 @@ class ElementReferencesQueryBuilder { this.sentenceElement = document.querySelector('[data-structural-attr-modal-action-button="sentence"]'); this.entityElement = document.querySelector('[data-structural-attr-modal-action-button="entity"]'); this.textAnnotationElement = document.querySelector('[data-structural-attr-modal-action-button="text-annotation"]'); + this.englishEntTypeSelection = document.querySelector('#corpus-analysis-concordance-english-ent-type-selection'); + this.germanEntTypeSelection = document.querySelector('#corpus-analysis-concordance-german-ent-type-selection'); + this.textAnnotationSelection = document.querySelector('#corpus-analysis-concordance-text-annotation-options'); // Token Attribute Builder Elements this.positionalAttrModal = M.Modal.getInstance(document.querySelector('#corpus-analysis-concordance-positional-attr-modal')); diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js index e718e811..bfe4fa7b 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/GeneralFunctionsQueryBuilder.js @@ -10,36 +10,96 @@ class GeneralFunctionsQueryBuilder { } updateChipList() { - this.elements.queryChipElements = this.elements.queryInputField.querySelectorAll('.chip'); + this.elements.queryChipElements = this.elements.queryInputField.querySelectorAll('.query-component'); } - queryChipFactory(dataType, prettyQueryText, queryText, index = null) { + removePlaceholder() { + let placeholder = this.elements.queryInputField.querySelector('#corpus-analysis-concordance-query-builder-input-field-placeholder'); + if (placeholder) { + this.elements.queryInputField.innerHTML = ''; + } + } + + addPlaceholder() { + let placeholder = Utils.HTMLToElement('Click on a button to add a query component'); + this.elements.queryInputField.appendChild(placeholder); + } + + resetMaterializeSelection(selectionElements, value = "default") { + selectionElements.forEach(selectionElement => { + selectionElement.querySelector(`option[value=${value}]`).selected = true; + let instance = M.FormSelect.getInstance(selectionElement); + instance.destroy(); + M.FormSelect.init(selectionElement); + }) + } + + + queryChipFactory(dataType, prettyQueryText, queryText, index = null, isClosingTag = false) { + // Creates a new query chip element, adds Eventlisteners for selection, deletion and drag and drop and appends it to the query input field. + queryText = Utils.escape(queryText); prettyQueryText = Utils.escape(prettyQueryText); let queryChipElement = Utils.HTMLToElement( ` - + ${prettyQueryText} - close + ${isClosingTag ? 'lock_open' : 'close'} ` ); - - queryChipElement.addEventListener('click', () => this.selectChipElement(queryChipElement)); - queryChipElement.querySelector('i').addEventListener('click', () => this.deleteChipElement(queryChipElement)); - + this.actionListeners(queryChipElement, isClosingTag); queryChipElement.addEventListener('dragstart', this.handleDragStart.bind(this, queryChipElement)); queryChipElement.addEventListener('dragend', this.handleDragEnd); - if (index !== null) { - this.elements.queryInputField.insertBefore(queryChipElement, this.elements.queryChipElements[index]); + + // Ensures that metadata is always at the end of the query and if an index is given, inserts the query chip at the given index and if there is a closing tag, inserts the query chip before the closing tag. + this.removePlaceholder(); + let lastChild = this.elements.queryInputField.lastChild; + let isLastChildTextAnnotation = lastChild && lastChild.dataset.type === 'text-annotation'; + if (!index) { + let closingTagElement = this.elements.queryInputField.querySelector('[data-closing-tag="true"]'); + if (closingTagElement) { + index = Array.from(this.elements.queryInputField.children).indexOf(closingTagElement); + } + } + if (index || isLastChildTextAnnotation) { + let insertingElement = isLastChildTextAnnotation ? lastChild : this.elements.queryChipElements[index]; + this.elements.queryInputField.insertBefore(queryChipElement, insertingElement); } else { this.elements.queryInputField.appendChild(queryChipElement); } + this.updateChipList(); this.queryPreviewBuilder(); } + actionListeners(queryChipElement, isClosingTag) { + let notQuantifiableDataTypes = ['start-sentence', 'end-sentence', 'start-entity', 'start-empty-entity', 'end-entity', 'token-incidence-modifier']; + queryChipElement.addEventListener('click', (event) => { + if (event.target.classList.contains('chip')) { + if (!notQuantifiableDataTypes.includes(queryChipElement.dataset.type)) { + this.selectChipElement(queryChipElement); + } + } + }); + queryChipElement.querySelector('i').addEventListener('click', () => { + if (isClosingTag) { + this.lockClosingChipElement(queryChipElement); + } else { + this.deleteChipElement(queryChipElement); + } + }); + } + + lockClosingChipElement(queryChipElement) { + let chipIndex = Array.from(this.elements.queryInputField.children).indexOf(queryChipElement); + this.queryChipFactory(queryChipElement.dataset.type, queryChipElement.firstChild.textContent, queryChipElement.dataset.query, chipIndex+1); + this.deleteChipElement(queryChipElement); + this.updateChipList(); + } + handleDragStart(queryChipElement, event) { + // is called when a query chip is dragged. It creates a dropzone (in form of a chip) for the dragged chip and adds it to the query input field. let queryChips = this.elements.queryInputField.querySelectorAll('.query-component'); setTimeout(() => { let targetChipElement = Utils.HTMLToElement('Drop here'); @@ -81,6 +141,7 @@ class GeneralFunctionsQueryBuilder { } queryPreviewBuilder() { + // Builds the query preview in the form of pure CQL and displays it in the query preview field. let queryPreview = document.querySelector('#corpus-analysis-concordance-query-preview'); let queryInputFieldContent = []; this.elements.queryChipElements.forEach(element => { @@ -92,17 +153,15 @@ class GeneralFunctionsQueryBuilder { }); let queryString = queryInputFieldContent.join(' '); - if (queryString.includes(' +')) { - queryString = queryString.replace(/ \+/g, '+'); - } - if (queryString.includes(' *')) { - queryString = queryString.replace(/ \*/g, '*'); - } - if (queryString.includes(' ?')) { - queryString = queryString.replace(/ \?/g, '?'); - } - if (queryString.includes(' {')) { - queryString = queryString.replace(/ \{/g, '{'); + let replacements = { + ' +': '+', + ' *': '*', + ' ?': '?', + ' {': '{' + }; + + for (let key in replacements) { + queryString = queryString.replace(key, replacements[key]); } queryString += ';'; @@ -117,6 +176,9 @@ class GeneralFunctionsQueryBuilder { this.elements.entityElement.innerHTML = 'Entity'; } this.elements.queryInputField.removeChild(attr); + if (this.elements.queryInputField.children.length === 0) { + this.addPlaceholder(); + } this.updateChipList(); this.queryPreviewBuilder(); } @@ -135,6 +197,7 @@ class GeneralFunctionsQueryBuilder { } tokenIncidenceModifierHandler(incidenceModifier, incidenceModifierPretty) { + // Adds a token incidence modifier to the query input field. let selectedChip = this.elements.queryInputField.querySelector('.chip.teal'); let selectedChipIndex = Array.from(this.elements.queryInputField.children).indexOf(selectedChip); this.queryChipFactory('token-incidence-modifier', incidenceModifierPretty, incidenceModifier, selectedChipIndex+1); @@ -142,6 +205,7 @@ class GeneralFunctionsQueryBuilder { } tokenNMSubmitHandler(modalId) { + // Adds a token incidence modifier (exactly n or between n and m) to the query input field. let modal = document.querySelector(`#${modalId}`); let input_n = modal.querySelector('.n-m-input[data-value-type="n"]').value; let input_m = modal.querySelector('.n-m-input[data-value-type="m"]') || undefined; diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js index 742533de..c9bb402c 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/StructuralAttributeBuilderFunctionsQueryBuilder.js @@ -10,24 +10,22 @@ class StructuralAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQu }); document.querySelector('.ent-type-selection-action[data-ent-type="any"]').addEventListener('click', () => { this.queryChipFactory('start-empty-entity', 'Entity Start', ''); - this.queryChipFactory('end-entity', 'Entity End', ''); - this.elements.structuralAttrModal.close(); + this.queryChipFactory('end-entity', 'Entity End', '', null, true); + this.resetAndCloseStructuralAttrModal(); }); document.querySelector('.ent-type-selection-action[data-ent-type="english"]').addEventListener('change', (event) => { this.queryChipFactory('start-entity', `Entity Type=${event.target.value}`, ``); - this.queryChipFactory('end-entity', 'Entity End', ''); - this.elements.structuralAttrModal.close(); + this.queryChipFactory('end-entity', 'Entity End', '', null, true); + this.resetAndCloseStructuralAttrModal(); }); - - } actionButtonInStrucAttrModalHandler(action) { switch (action) { case 'sentence': this.queryChipFactory('start-sentence', 'Sentence Start', ''); - this.queryChipFactory('end-sentence', 'Sentence End', ''); - this.elements.structuralAttrModal.close(); + this.queryChipFactory('end-sentence', 'Sentence End', '', null, true); + this.resetAndCloseStructuralAttrModal(); break; case 'entity': this.toggleClass(['entity-builder'], 'hide', 'toggle'); @@ -42,4 +40,36 @@ class StructuralAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQu } } + textAnnotationSubmitHandler() { + let noValueMetadataMessage = document.querySelector('#corpus-analysis-concordance-no-value-metadata-message'); + let textAnnotationSubmit = document.querySelector('#corpus-analysis-concordance-text-annotation-submit'); + let textAnnotationInput = document.querySelector('#corpus-analysis-concordance-text-annotation-input'); + let textAnnotationOptions = document.querySelector('#corpus-analysis-concordance-text-annotation-options'); + + if (textAnnotationInput.value === '') { + textAnnotationSubmit.classList.add('red'); + noValueMetadataMessage.classList.remove('hide'); + setTimeout(() => { + textAnnotationSubmit.classList.remove('red'); + }, 500); + setTimeout(() => { + noValueMetadataMessage.classList.add('hide'); + }, 3000); + } else { + let queryText = `:: match.text_${textAnnotationOptions.value}="${textAnnotationInput.value}"`; + this.queryChipFactory('text-annotation', `${textAnnotationOptions.value}=${textAnnotationInput.value}`, queryText); + this.resetAndCloseStructuralAttrModal(); + } + } + + resetAndCloseStructuralAttrModal() { + let textAnnotatinInput = document.querySelector('#corpus-analysis-concordance-text-annotation-input'); + textAnnotatinInput.value = ''; + this.resetMaterializeSelection([this.elements.englishEntTypeSelection, this.elements.germanEntTypeSelection]); + this.resetMaterializeSelection([this.elements.textAnnotationSelection], 'address'); + + this.toggleClass(['entity-builder', 'text-annotation-builder'], 'hide', 'add'); + this.elements.structuralAttrModal.close(); + } + } diff --git a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js index dff14554..7caa8707 100644 --- a/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js +++ b/app/static/js/CorpusAnalysis/QueryBuilder/TokenAttributeBuilderFunctionsQueryBuilder.js @@ -331,13 +331,4 @@ class TokenAttributeBuilderFunctionsQueryBuilder extends GeneralFunctionsQueryBu this.resetMaterializeSelection([this.elements.englishPosSelection, this.elements.germanPosSelection, this.elements.simplePosSelection]); this.resetMaterializeSelection([this.elements.positionalAttrSelection], "word"); } - - resetMaterializeSelection(selectionElements, value = "default") { - selectionElements.forEach(selectionElement => { - 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/Utils.js b/app/static/js/Utils.js index 79879c75..0377bc56 100644 --- a/app/static/js/Utils.js +++ b/app/static/js/Utils.js @@ -16,6 +16,26 @@ class Utils { }); }; + static unescape(escapedText) { + var table = { + 'lt': '<', + 'gt': '>', + 'quot': '"', + 'apos': "'", + 'amp': '&', + '#10': '\r', + '#13': '\n' + }; + + return escapedText.replace(/&(#?\w+);/g, (match, entity) => { + if (table.hasOwnProperty(entity)) { + return table[entity]; + } + + return match; + }); +} + static HTMLToElement(HTMLString) { let templateElement = document.createElement('template'); templateElement.innerHTML = HTMLString.trim(); diff --git a/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 b/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 index d6452358..d6343e95 100644 --- a/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 +++ b/app/templates/corpora/_analysis/query_builder/_query_builder.html.j2 @@ -2,7 +2,9 @@
-
+
+

Click on the buttons below to build your query.

+
arrow_forward @@ -75,8 +77,8 @@ Add Entity of any type

- + @@ -99,8 +101,8 @@
- +