From c23d6e2b1093940acd0d443f67a1f4530bf37b18 Mon Sep 17 00:00:00 2001 From: Erik Brommers <1458944+eb1@users.noreply.github.com> Date: Sun, 22 Sep 2024 14:39:42 -0700 Subject: [PATCH] Fix #600 Check for empty gloss fields in gloss mode. Also started some work on #601 (not complete). --- www/js/views/AdaptViews.js | 389 +++++++++++++++++++++++++------------ 1 file changed, 260 insertions(+), 129 deletions(-) diff --git a/www/js/views/AdaptViews.js b/www/js/views/AdaptViews.js index 42e8aefa..418ce35b 100644 --- a/www/js/views/AdaptViews.js +++ b/www/js/views/AdaptViews.js @@ -806,6 +806,8 @@ define(function (require) { moveCursor: function (event, moveForward) { var next_edit = null; var temp_cursor = null; + var endID = null; + var endSP = null; var keep_going = true; var FTEmpty = true; var top = 0; @@ -817,6 +819,22 @@ define(function (require) { } strID = strID.substr(strID.indexOf("-") + 1); // remove "pile-" console.log("moveCursor: forward: " + moveForward + ", id: " + strID); + // for free translations, figure out where the end of the selection is before blurring + // (this is needed for going forwards only) + if (editorMode === editorModeEnum.FREE_TRANSLATING && moveForward === true) { + if (selectedEnd === null) { + // make sure we have a selectedEnd + selectedEnd = selectedStart; + } + endID = $(selectedEnd).attr('id'); + if (endID === undefined) { + // this might be the tt-input div if we are in a typeahead (multiple KB) input - + // if so, go up one more level to find the pile + endID = $(selectedEnd.parentElement).attr('id'); + } + console.log("moveCursor: endID for free translation: " + endID); + endSP = this.collection.findWhere({spid: endID}); + } event.stopPropagation(); event.preventDefault(); // unfocus any current selection (both the nav button that triggered the event and the selected start) @@ -838,33 +856,45 @@ define(function (require) { } else { curSP = this.collection.at(idx); } - while (curSP) { - if (curSP.get('target') === '') { - console.log("possible SP slot: "+ curSP.get('spid') + " -- " + curSP.get('source')); - // found an empty target -- is it filtered? - if ($('#pile-' + curSP.get('spid')).hasClass("filter") === false && ($('#pile-' + curSP.get('spid')).hasClass("pile"))) - { - // looks like a non-filtered pile -- break out of the while loop - break; // found the next slot + if (editorMode !== editorModeEnum.FREE_TRANSLATING) { + // adapting or glossing -- find the previous empty slot + while (curSP) { + if ((editorMode === editorModeEnum.ADAPTING && curSP.get('target') === '') || + (editorMode === editorModeEnum.GLOSSING && curSP.get('gloss') === '')) { + console.log("possible SP slot: "+ curSP.get('spid') + " -- " + curSP.get('source')); + // found an empty slot -- is it filtered? + if ($('#pile-' + curSP.get('spid')).hasClass("filter") === false && ($('#pile-' + curSP.get('spid')).hasClass("pile"))) + { + // looks like a non-filtered pile -- break out of the while loop + break; // found the next slot + } } + idx--; + if (idx < 0) { + // first pile + curSP = null; + break; + } + curSP = this.collection.at(idx); } - idx--; - if (idx < 0) { - // first pile - curSP = null; - break; - } - curSP = this.collection.at(idx); + if (curSP) { + if ($('#pile-' + curSP.get('spid')).length !== 0) { + // everything's okay -- select the SourcePhrase + next_edit = $('#pile-' + curSP.get('spid')).get(0); + keep_going = false; + } + } // note - first node is handled at the bottom of this function } - if (curSP) { - if ($('#pile-' + curSP.get('spid')).length !== 0) { - // everything's okay -- select the SourcePhrase - next_edit = $('#pile-' + curSP.get('spid')).get(0); - keep_going = false; - } - } // note - first node is handled at the bottom of this function // Free Translation processing - if ((editorMode === editorModeEnum.FREE_TRANSLATING) && (next_edit !== null)) { + if (editorMode === editorModeEnum.FREE_TRANSLATING) { + // select the previous sourcephrase + if (curSP) { + if ($('#pile-' + curSP.get('spid')).length !== 0) { + // everything's okay -- select the SourcePhrase + next_edit = $('#pile-' + curSP.get('spid')).get(0); + keep_going = false; + } + } selectedEnd = lastSelectedFT = next_edit; // free translation -- lastSelectedFT is the END of the selection temp_cursor = next_edit; // keep going backwards until we hit punctuation, the first pile, or a free translation @@ -919,140 +949,226 @@ define(function (require) { // edb 8/8/24: reworked to iterate through SP model list instead of DOM, and only stop when there's an empty target var idx = this.collection.indexOf(curSP) + 1; curSP = this.collection.at(idx); - while (curSP) { - if (curSP.get('target') === '') { - console.log("possible SP slot: "+ curSP.get('spid') + " -- " + curSP.get('source')); - // found an empty target -- is it filtered? - if ($('#pile-' + curSP.get('spid')).hasClass("filter") === false && ($('#pile-' + curSP.get('spid')).hasClass("pile"))) - { - // looks like a non-filtered pile -- break out of the while loop - break; // found the next slot + if (editorMode !== editorModeEnum.FREE_TRANSLATING) { + // adapting or glossing -- find next empty slot + while (curSP) { + if ((editorMode === editorModeEnum.ADAPTING && curSP.get('target') === '') || + (editorMode === editorModeEnum.GLOSSING && curSP.get('gloss') === '')) { + console.log("possible SP slot: "+ curSP.get('spid') + " -- " + curSP.get('source')); + // found an empty slot -- is it filtered? + if ($('#pile-' + curSP.get('spid')).hasClass("filter") === false && ($('#pile-' + curSP.get('spid')).hasClass("pile"))) + { + // looks like a non-filtered pile -- break out of the while loop + break; // found the next slot + } } + idx++; + curSP = this.collection.at(idx); } - idx++; - curSP = this.collection.at(idx); - } - if (curSP) { - if ($('#pile-' + curSP.get('spid')).length !== 0) { - // everything's okay -- select the SourcePhrase - next_edit = $('#pile-' + curSP.get('spid')).get(0); - keep_going = false; - } - } else { - // reached the last pile - next_edit = null; - // Check for a chapter after the current one in the current book - var nextChapter = ""; - var book = window.Application.BookList.where({bookid: chapter.get('bookid')}); - var chaps = book[0].get('chapters'); - if (chaps.length > 1) { - if ((chaps.indexOf(chapter.get('chapterid')) !== -1) && - (chaps.indexOf(chapter.get('chapterid')) < (chaps.length - 1))) { - // There is a chapter after this one - nextChapter = chaps[chaps.indexOf(chapter.get('chapterid')) + 1]; + if (curSP) { + if ($('#pile-' + curSP.get('spid')).length !== 0) { + // everything's okay -- select the SourcePhrase + next_edit = $('#pile-' + curSP.get('spid')).get(0); + keep_going = false; } - } - // If there is a next chapter, let the user continue or exit; - // if there isn't one, just allow them to exit - if (navigator.notification) { - // on mobile device - navigator.notification.beep(1); - if (nextChapter.length > 0) { - navigator.notification.confirm( - i18next.t('view.dscAdaptContinue', {chapter: chapter.get('name')}), - function (buttonIndex) { - if (buttonIndex === 1) { - // Next chapter - // update the URL, but replace the history (so we go back to the welcome screen) - window.Application.router.navigate("adapt/" + nextChapter, {trigger: true, replace: true}); - } else { + } else { + // reached the last pile (adapting or gloss mode) + next_edit = null; + // Check for a chapter after the current one in the current book + var nextChapter = ""; + var book = window.Application.BookList.where({bookid: chapter.get('bookid')}); + var chaps = book[0].get('chapters'); + if (chaps.length > 1) { + if ((chaps.indexOf(chapter.get('chapterid')) !== -1) && + (chaps.indexOf(chapter.get('chapterid')) < (chaps.length - 1))) { + // There is a chapter after this one + nextChapter = chaps[chaps.indexOf(chapter.get('chapterid')) + 1]; + } + } + // If there is a next chapter, let the user continue or exit; + // if there isn't one, just allow them to exit + if (navigator.notification) { + // on mobile device + navigator.notification.beep(1); + if (nextChapter.length > 0) { + navigator.notification.confirm( + i18next.t('view.dscAdaptContinue', {chapter: chapter.get('name')}), + function (buttonIndex) { + if (buttonIndex === 1) { + // Next chapter + // update the URL, but replace the history (so we go back to the welcome screen) + window.Application.router.navigate("adapt/" + nextChapter, {trigger: true, replace: true}); + } else { + // exit + // save the model + chapter.trigger('change'); + // head back to the home page + window.Application.home(); + } + }, + i18next.t('view.ttlMain'), + [i18next.t('view.lblNext'), i18next.t('view.lblFinish')] + ); + } else { + // no option to continue, just one to exit + navigator.notification.alert( + i18next.t('view.dscAdaptComplete', {chapter: chapter.get('name')}), + function () { // exit // save the model chapter.trigger('change'); // head back to the home page window.Application.home(); } - }, - i18next.t('view.ttlMain'), - [i18next.t('view.lblNext'), i18next.t('view.lblFinish')] - ); + ); + } } else { - // no option to continue, just one to exit - navigator.notification.alert( - i18next.t('view.dscAdaptComplete', {chapter: chapter.get('name')}), - function () { - // exit - // save the model - chapter.trigger('change'); - // head back to the home page + // in browser + if (nextChapter > 0) { + if (confirm(i18next.t('view.dscAdaptContinue', {chapter: chapter.get('name')}))) { + // update the URL, but replace the history (so we go back to the welcome screen) + window.Application.router.navigate("adapt/" + nextChapter, {trigger: true, replace: true}); + } else { window.Application.home(); } - ); - } - } else { - // in browser - if (nextChapter > 0) { - if (confirm(i18next.t('view.dscAdaptContinue', {chapter: chapter.get('name')}))) { - // update the URL, but replace the history (so we go back to the welcome screen) - window.Application.router.navigate("adapt/" + nextChapter, {trigger: true, replace: true}); } else { + alert(i18next.t('view.dscAdaptComplete', {chapter: chapter.get('name')})); window.Application.home(); } - } else { - alert(i18next.t('view.dscAdaptComplete', {chapter: chapter.get('name')})); - window.Application.home(); } } } // Free Translation processing if (editorMode === editorModeEnum.FREE_TRANSLATING) { - selectedStart = lastSelectedFT; // move from the end (not the start) of the selection - } - if ((editorMode === editorModeEnum.FREE_TRANSLATING) && (next_edit !== null)) { - // in FT mode, we need to also find the end of the selection - selectedStart = selectedEnd = lastSelectedFT = temp_cursor = next_edit; // initial value - // first, find out whether the start of our selection has a free translation defined - var ft = $(next_edit).find(".ft").html(); - if (ft.length > 0) { - FTEmpty = false; - console.log("moveCursor (forwards) - not empty; FT at selection: " + ft); + // select the sourcephrase after the END of the last selection + var idx = this.collection.indexOf(endSP) + 1; + curSP = this.collection.at(idx); + while (curSP) { + // Move forwards some more if we picked a filtered SP + if ($('#pile-' + curSP.get('spid')).hasClass("filter") === false && ($('#pile-' + curSP.get('spid')).hasClass("pile"))) + { + // non-filtered pile -- stop here / break out of the while loop + break; // found the next slot + } + idx++; + curSP = this.collection.at(idx); } - // now find the end of the selection - keep_going = true; - while (keep_going === true) { - // move forwards - if (temp_cursor !== null) { - next_edit = temp_cursor; - if ((next_edit.nextElementSibling !== null) && ($(next_edit.nextElementSibling).hasClass('pile')) && (!$(next_edit.nextElementSibling).hasClass('filter'))) { - // there is a next sibling, and it is a non-filtered pile - temp_cursor = next_edit.nextElementSibling; - // does it have a free translation? - var ft = $(temp_cursor).find(".ft").html(); - if (ft.length > 0) { - // found the next free translation -- stop moving forward - console.log("moveCursor (forwards) - stopping BEFORE next FT: " + ft); - keep_going = false; - } - // check for punctuation (go from the inside out) - if ($(temp_cursor).children(".source").first().hasClass("pp")) { - // comes before -- don't include - keep_going = false; - } else if ($(temp_cursor).children(".source").first().hasClass("fp")) { - // comes after -- include - next_edit = temp_cursor; + if (curSP) { + if ($('#pile-' + curSP.get('spid')).length !== 0) { + // everything's okay -- select the SourcePhrase + next_edit = $('#pile-' + curSP.get('spid')).get(0); + keep_going = false; + } + // in FT mode, we need to also find the end of the selection + selectedStart = selectedEnd = lastSelectedFT = temp_cursor = next_edit; // initial value + // first, find out whether the start of our selection has a free translation defined + var ft = $(next_edit).find(".ft").html(); + if (ft.length > 0) { + FTEmpty = false; + console.log("moveCursor (forwards) - not empty; FT at selection: " + ft); + } + // now find the end of the selection + keep_going = true; + while (keep_going === true) { + // move forwards + if (temp_cursor !== null) { + next_edit = temp_cursor; + if ((next_edit.nextElementSibling !== null) && ($(next_edit.nextElementSibling).hasClass('pile')) && (!$(next_edit.nextElementSibling).hasClass('filter'))) { + // there is a next sibling, and it is a non-filtered pile + temp_cursor = next_edit.nextElementSibling; + // does it have a free translation? + var ft = $(temp_cursor).find(".ft").html(); + if (ft.length > 0) { + // found the next free translation -- stop moving forward + console.log("moveCursor (forwards) - stopping BEFORE next FT: " + ft); + keep_going = false; + } + // check for punctuation (go from the inside out) + if ($(temp_cursor).children(".source").first().hasClass("pp")) { + // comes before -- don't include + keep_going = false; + } else if ($(temp_cursor).children(".source").first().hasClass("fp")) { + // comes after -- include + next_edit = temp_cursor; + keep_going = false; + } + } else { + // reached a stopping point keep_going = false; } } else { - // reached a stopping point + // no temp_cursor -- stop backing up keep_going = false; } + } + // Set selectedEnd and lastSelectedFT to the end of the selection + selectedEnd = lastSelectedFT = next_edit; + } else { + // reached the last pile (FT mode) + next_edit = null; + // Check for a chapter after the current one in the current book + var nextChapter = ""; + var book = window.Application.BookList.where({bookid: chapter.get('bookid')}); + var chaps = book[0].get('chapters'); + if (chaps.length > 1) { + if ((chaps.indexOf(chapter.get('chapterid')) !== -1) && + (chaps.indexOf(chapter.get('chapterid')) < (chaps.length - 1))) { + // There is a chapter after this one + nextChapter = chaps[chaps.indexOf(chapter.get('chapterid')) + 1]; + } + } + // If there is a next chapter, let the user continue or exit; + // if there isn't one, just allow them to exit + if (navigator.notification) { + // on mobile device + navigator.notification.beep(1); + if (nextChapter.length > 0) { + navigator.notification.confirm( + i18next.t('view.dscAdaptContinue', {chapter: chapter.get('name')}), + function (buttonIndex) { + if (buttonIndex === 1) { + // Next chapter + // update the URL, but replace the history (so we go back to the welcome screen) + window.Application.router.navigate("adapt/" + nextChapter, {trigger: true, replace: true}); + } else { + // exit + // save the model + chapter.trigger('change'); + // head back to the home page + window.Application.home(); + } + }, + i18next.t('view.ttlMain'), + [i18next.t('view.lblNext'), i18next.t('view.lblFinish')] + ); + } else { + // no option to continue, just one to exit + navigator.notification.alert( + i18next.t('view.dscAdaptComplete', {chapter: chapter.get('name')}), + function () { + // exit + // save the model + chapter.trigger('change'); + // head back to the home page + window.Application.home(); + } + ); + } } else { - // no temp_cursor -- stop backing up - keep_going = false; + // in browser + if (nextChapter > 0) { + if (confirm(i18next.t('view.dscAdaptContinue', {chapter: chapter.get('name')}))) { + // update the URL, but replace the history (so we go back to the welcome screen) + window.Application.router.navigate("adapt/" + nextChapter, {trigger: true, replace: true}); + } else { + window.Application.home(); + } + } else { + alert(i18next.t('view.dscAdaptComplete', {chapter: chapter.get('name')})); + window.Application.home(); + } } - } - // Set selectedEnd and lastSelectedFT to the end of the selection - selectedEnd = lastSelectedFT = next_edit; + } } } // done moving the cursor -- now select it if possible @@ -4726,6 +4842,9 @@ define(function (require) { // found the selection end -- set the value selectedEnd = next_edit; } + // keep a copy of the ending ID we're working on + $("#fteditor").attr("data-endid", $(selectedEnd).attr('id')); + // set the last selected FT slot to then END of our selection lastSelectedFT = selectedEnd; // we're also working on a specific source phrase (the FT gets saved there) - @@ -4781,6 +4900,8 @@ define(function (require) { $(event.currentTarget).html(model.get('freetrans')); // original FT value for the selected pile event.stopPropagation(); event.preventDefault(); + // reset the dirty bit + isDirty = false; $(event.currentTarget).blur(); } else if ((event.keyCode === 9) || (event.keyCode === 13)) { // tab or enter key -- accept the edit and move the cursor @@ -4913,6 +5034,9 @@ define(function (require) { // selectedStart got cleared out -- set it to the one we kept in the editor field selectedStart = $("#fteditor").attr('data-spid'); } + if (selectedEnd === null) { + selectedEnd = $("fteditor").attr('data-endid'); + } if (selectedEnd) { // there is an end selection set -- move it forwards if possible if ((selectedEnd.nextElementSibling !== null) && ($(selectedEnd.nextElementSibling).hasClass('pile')) && (!$(selectedEnd.nextElementSibling).hasClass('filter'))) { @@ -4963,6 +5087,10 @@ define(function (require) { $(value).addClass("ui-selected"); // also build a default FT in case we need it below strFT += $(value).find(".target").html() + " "; + if (index > idxStart) { + // clear out any existing ft text in the UI (model gets cleared on blur) + $(value).find(".ft").html(""); + } } }); // do we need to change the selection in the FT editor field? @@ -4991,6 +5119,9 @@ define(function (require) { // selectedStart got cleared out -- set it to the one we kept in the editor field selectedStart = $("#fteditor").attr('data-spid'); } + if (selectedEnd === null) { + selectedEnd = $("fteditor").attr('data-endid'); + } if (selectedEnd && selectedEnd !== selectedStart) { // there is an end selection still set -- move it back one if possible if ((selectedEnd.previousElementSibling !== null) && ($(selectedEnd.previousElementSibling).hasClass('pile')) && (!$(selectedEnd.previousElementSibling).hasClass('filter'))) {