Skip to content

Commit

Permalink
Merge branch 'iss_573_editable_term_listing' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
jzohrab committed Jan 22, 2025
2 parents ae177ed + 565cbf4 commit 70256e0
Show file tree
Hide file tree
Showing 18 changed files with 899 additions and 397 deletions.
1 change: 1 addition & 0 deletions lute/models/term.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ class Status(db.Model): # pylint: disable=too-few-public-methods
UNKNOWN = 0
WELLKNOWN = 99
IGNORED = 98
ALLOWED = [UNKNOWN, 1, 2, 3, 4, 5, IGNORED, WELLKNOWN]

__tablename__ = "statuses"

Expand Down
31 changes: 23 additions & 8 deletions lute/static/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -2182,18 +2182,20 @@ input[name='status']:disabled + label {

/** Term listing (/term) action dropdown. ********************/

.term-action-container {
display: flex;
table#termtable .tagify,
table#termtable .translationDiv {
border: 1px solid transparent; /* Prevent elements pushing others around on hover. */
box-sizing: border-box;
}

/*
.term-action-dropdown,
.actionDiv {
table#termtable .tagify:hover,
table#termtable .translationDiv:hover {
border: 1px solid;
}

.term-action-container {
display: flex;
align-items: center;
margin-right: 10px;
}
*/

#bulkEditDiv {
margin: 0.5rem;
Expand Down Expand Up @@ -2254,6 +2256,19 @@ div#termtable_wrapper div.dt-buttons {
display: none;
}

.ajax-saved-checkmark {
position: absolute;
width: 1.5em; /* Define the size of the circle */
height: 1.5em; /* Make it a perfect circle */
background: green;
color: white;
border-radius: 50%; /* Turns the square into a circle */
font-size: 1em; /* Adjust size of the checkmark */
text-align: center;
line-height: 1.5em; /* Vertically center the checkmark */
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
z-index: 2004;
}

/** Term image search ****************/

Expand Down
202 changes: 202 additions & 0 deletions lute/static/js/lute-tagify-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/**
* Tagify helpers.
*
* Lute uses Tagify (https://github.com/yairEO/tagify)
* for parent terms and term tags.
*/


/**
* Build a parent term tagify with autocomplete.
*
* args:
* - input: the input box tagify will control
* - language_id_func: zero-arg function that returns the language.
*
* notes:
*
* - language_id_func is passed, rather than a language_id, because in
* some cases such as bulk term editing the language isn't known at
* tagify setup time. The func delegates the check until it's
* actually needed, in _fetch_whitelist.
*/
function lute_tagify_utils_setup_parent_tagify(
input,
language_id_func, // if returns null, autocomplete does nothing
this_term_text = null, // set to non-null to filter whitelist
override_base_settings = {}
) {
if (input._tagify) {
// console.log('Tagify already initialized for this input.');
return input._tagify;
}

// Do the fetch and build the whitelist.
const _fetch_whitelist = function(mytagify, e_detail_value, controller) {
const language_id = language_id_func();
if (language_id == null) {
console.log("language_id not set or not consistent");
mytagify.loading(false);
return;
}

// Create entry like "cat (a furry thing...)"
const _make_dropdown_entry = function(hsh) {
const txt = decodeURIComponent(hsh.text);
let translation = hsh.translation ?? '';
translation = translation.
replaceAll("\n", "; ").
replaceAll("\r", "").
trim();
if (translation == '')
return txt;
const max_translation_len = 40;
if (translation.length > max_translation_len)
translation = translation.slice(0, max_translation_len) + "...";
translation = translation ? `(${translation})` : '';
return [txt, translation].join(' ');
};

// Build whitelist from returned ajax data.
const _build_whitelist = function(data) {
const _make_hash = function(a) {
return {
"value": a.text,
"id": a.id,
"suggestion": _make_dropdown_entry(a),
"status": a.status,
};
};
return data.map((a) => _make_hash(a));
};

const encoded_value = encodeURIComponent(e_detail_value);
const url = `/term/search/${encoded_value}/${language_id ?? -1}`;
mytagify.loading(true);
fetch(url, {signal:controller.signal})
.then(RES => RES.json())
.then(function(data) {
// Update whitelist and render in place.
let whitelist = _build_whitelist(data);
whitelist = whitelist.filter(hsh => hsh.value != this_term_text);
mytagify.whitelist = whitelist;
mytagify.loading(false).dropdown.show(e_detail_value);
}).catch(err => {
if (err.name === 'AbortError') {
// Do nothing, fetch was aborted due to another fetch.
// console.log('AbortError: Fetch request aborted');
}
else {
console.log(`error: ${err}`);
}
mytagify.loading(false);
});
};

// Controller to handle cancellations/aborts of calls.
// https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort
var controller;

// Build whitelist in response to user input.
function build_autocomplete_dropdown(mytagify, e) {
if (e.detail.value == '' || e.detail.value.length < 1) {
controller && controller.abort();
mytagify.whitelist = [];
mytagify.loading(false).dropdown.hide();
return;
}
controller && controller.abort()
controller = new AbortController()
_fetch_whitelist(mytagify, e.detail.value, controller);
}

// Need a global tagify instance here
// so that hooks can use it.
var tagify_instance = null;

const make_Tagify_for = function(input) {
const base_settings = {
editTags: false,
pasteAsTags: false,
backspace: true,
addTagOnBlur: true, // note different
autoComplete: { enabled: true, rightKey: true, tabKey: true },
delimiters: ';;', // special delimiter to handle parents with commas.
enforceWhitelist: false,
whitelist: [],
dropdown: {
enabled: 1,
maxItems: 15,
mapValueTo: 'suggestion',
placeAbove: false,
},
templates: {
dropdownFooter(suggestions) {
var hasMore = suggestions.length - this.settings.dropdown.maxItems;
if (hasMore <= 0)
return '';
return `<footer data-selector='tagify-suggestions-footer' class="${this.settings.classNames.dropdownFooter}">
(more items available, please refine your search.)</footer>`;
}
},

// Use a hook to force build_autocomplete_dropdown.
// Pasting from the clipboard doesnt fire the
// tagify.on('input') event, so intercept it and handle
// it manually.
hooks: {
beforePaste : function(content) {
return new Promise((resolve, reject) => {
clipboardData = content.clipboardData || window.clipboardData;
pastedData = clipboardData.getData('Text');
let e = { detail: { value: pastedData } };
build_autocomplete_dropdown(tagify_instance, e);
resolve();
});
}
},
};

let settings = { ...base_settings, ...override_base_settings };
tagify_instance = new Tagify(input, settings);
return tagify_instance;
};

const tagify = make_Tagify_for(input);
tagify.on('input', function (e) {
build_autocomplete_dropdown(tagify, e)
});

return tagify;
} // end lute_tagify_utils_setup_parent_tagify


/**
* Build a term tag tagify with autocomplete.
*
* args:
* - input: the input box tagify will control
* - tags: the tags array
* - override_base_settings: {} to override
*/
function lute_tagify_utils_setup_term_tag_tagify(
input,
tags,
override_base_settings = {}
) {
if (input._tagify) {
return input._tagify;
}

const base_settings = {
delimiters: ';;', // special delim to handle tags w/ commas
editTags: false,
autoComplete: { enabled: true, rightKey: true, tabKey: true },
dropdown: { enabled: 1 },
enforceWhitelist: false,
whitelist: tags,
};
const settings = { ...base_settings, ...override_base_settings };
const tagify = new Tagify(input, settings);
return tagify;
} // end lute_tagify_utils_setup_term_tag_tagify
21 changes: 21 additions & 0 deletions lute/static/js/lute.js
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,27 @@ let show_translation_for_text = function(text) {
};


// Get all the word ids on the current page, open new tab with just those terms.
function open_term_list_for_current_page() {
const ids = new Set();
$('span.word').each(function () {
ids.add($(this).data("wid"));
});
if (ids.length == 0)
return; // Nothing to do.

const idarray = Array.from(ids);
const idlist = idarray.join('+');

// console.log('passing ids:');
// console.log(idlist);
// let msg = idlist;
// window.alert(msg);
const url = `/term/index?termids=${idlist}`;
window.open(url);
}


/** Show the translation using the next dictionary. */
function handle_translate(span_attribute) {
const tis = get_textitems_spans(span_attribute);
Expand Down
4 changes: 3 additions & 1 deletion lute/static/vendor/tagify/tagify_overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
.tagify__dropdown {
background: white !important;
color: black !important;
min-width: 400px !important;
max-width: 600px;
text-overflow: ellipsis;
}

.tagify__tag {
Expand All @@ -84,4 +87,3 @@
padding: 0.1rem !important;
font-size: 0.8rem;
}

7 changes: 6 additions & 1 deletion lute/templates/read/reading_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
<ul>
<li>
<a id="readmenu_bookmark_index" class="reading-menu-item" href="/bookmarks/{{ book.id }}" title="Index">
List
List bookmarks
</a>
</li>
<li>
Expand All @@ -91,6 +91,11 @@
</ul>
</div>
</li>
<li>
<a id="listTermsOnCurrentPage" class="reading-menu-item" href="" onclick="open_term_list_for_current_page(); return false;">
Term list
</a>
</li>
<li>
<!-- mobile users need this link, since they don't have keyboard shortcuts. -->
<a id="translateSentence" class="reading-menu-item" href="" onclick="handle_translate('sentence-id'); return false;">
Expand Down
2 changes: 1 addition & 1 deletion lute/templates/read/term_bulk_edit_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
const elements = $(parent.document).find("span.word");
if (elements.length == 0) {
console.log("No words on page ...");
return -999; // dummy
return null; // dummy
}
const first = elements.first();
return $(first).data("lang-id");
Expand Down
Loading

0 comments on commit 70256e0

Please sign in to comment.