diff --git a/CHANGELOG.md b/CHANGELOG.md index bd6754f8f3aeee5d9557ecb84cce1c84f7b326de..acf202950f2f2039e0e18625105d7e76b5963c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.13.2 - 2024-02-20] ## + +### Fixed ### + +* [#247](https://gitlab.com/linkahead/linkahead-webui/-/issues/247) Linkify absorbs commas + etc. (, . : ;) into link adress +* Query shortcuts are now displayed when using the new query panel. +* [249](https://gitlab.com/linkahead/linkahead-webui/-/issues/249) only the + first ≈100 options were shown in edit-mode reference dropdown menus in case + of `BUILD_MAX_EDIT_MODE_DROPDOWN_OPTIONS=-1`. + ## [0.13.1 - 2023-11-17] ## ### Fixed ### diff --git a/CITATION.cff b/CITATION.cff index 411ef9b3c7665d62e3e4bd385dbf80cfc951599b..9c61739c32ef1730d3f7ab5608875b4d712c70ae 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -20,6 +20,6 @@ authors: given-names: Stefan orcid: https://orcid.org/0000-0001-7214-8125 title: LinkAhead - WebUI -version: 0.13.1 +version: 0.13.2 doi: 10.3390/data4020083 -date-released: 2023-11-17 +date-released: 2024-02-20 diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 40dd207437cc353a46c7bbcd0cfb501e939f8610..c96629cd9081bad240999f431a1f9306b8cfa704 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -1,4 +1,4 @@ -* LinkAhead Server 0.9.0 +* LinkAhead Server 0.12.0 * Make 4.2.0 # Java Script Libraries (included in this repository) @@ -31,5 +31,19 @@ editor plugins. Please refer to the `package.json` within `libs/ckeditor...zip` for a full list of said plugins. -## For testing +## For unit testing * qunit-2.9.2 + +### Debian 12 (also on WSL) +* unzip +* gettext +* firefox-esr +* xvfb +* libpci-dev +* libegl1 +* imagemagick (to use `convert` on `screenshot.xwd`) + +Install test dependencies on Debian +```bash +sudo apt install unzip gettext firefox-esr xvfb x11-apps libpci-dev libegl1 imagemagick +``` diff --git a/README_SETUP.md b/README_SETUP.md index 3a27cb5e34d1785991a2379c703561926913e96a..9b8a6457d9a0b79fe0d6acf3e8df9294f0074981 100644 --- a/README_SETUP.md +++ b/README_SETUP.md @@ -63,10 +63,16 @@ See `build.properties.d/00_default.properties` for more information. ## Test +* See [DEPENDENCIES](DEPENDENCIES#for-unit-testing) for the requirements to run the unit tests. * Run `make test` to compile/copy the web interface and the tests to a newly created `public` folder. * Run `make run-test-server` to start a python http server. * The test suite can be started with `firefox http://localhost:8000/`. +* *On WSL (as of Feb 2024, WSL v2.0.14.0)*, port 8000 is sometimes not properly forwarded to the host + resulting in an unreachable test suite. Switching to a different port can be done easily + by using the `TEST_PORT` environment variable, e.g. with `TEST_PORT=8111 make + run-test-server`. Using the X11 `firefox` from within WSL always works. +* To run the test suite non-interactively (as in CI/CD) use `make -d run-qunit` ## Clean diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js index 1a2c9092c2b6d234cc52691089c7094e93c49a2a..5b97fe30d5ecd1cc4bd4e00bff366014d6dc58a9 100644 --- a/src/core/js/edit_mode.js +++ b/src/core/js/edit_mode.js @@ -1033,14 +1033,16 @@ var edit_mode = new function () { $(manualInsertButton).click(function () { // toggle input // forward: - if ($(result).find('input.caosdb-f-manual-id-insert').is(':hidden')){ + if ($(result).find('input.caosdb-f-manual-id-insert').is(':hidden')) { // show ID input; hide dropdown $(result).find('.dropdown').hide(); $(result).find('input.caosdb-f-manual-id-insert').show(); $('.caosdb-f-entity-save-button').hide() } else { // backward: - $(result).find('input.caosdb-f-manual-id-insert')[0].dispatchEvent(new KeyboardEvent('keyup', {'key':'Enter'})); + $(result).find('input.caosdb-f-manual-id-insert')[0].dispatchEvent(new KeyboardEvent('keyup', { + 'key': 'Enter' + })); } }); // Add input for manual ID insertion @@ -1053,8 +1055,8 @@ var edit_mode = new function () { // was entered to the candidates and select that item $(result).find('input.caosdb-f-manual-id-insert').hide(); var sel_id = $(result).find('input.caosdb-f-manual-id-insert')[0].value - if (sel_id!=""){ - if ($(result).find(`option[value="${sel_id}"]`).length==0){ + if (sel_id != "") { + if ($(result).find(`option[value="${sel_id}"]`).length == 0) { select.append(`<option value="${sel_id}">ID: ${sel_id}</option>`); // workaround for selectpicker bug (options are // doubled otherwise) @@ -1891,14 +1893,16 @@ var edit_mode = new function () { * which can be referenced by the property. */ this.retrieve_datatype_list = async function (datatype) { - var find_entity = ["FILE", "REFERENCE"].includes(datatype) ? "" : `"${datatype}"`; + const find_entity = ["FILE", "REFERENCE"].includes(datatype) ? "" : `"${datatype}"`; const max_options = parseInt("${BUILD_MAX_EDIT_MODE_DROPDOWN_OPTIONS}"); //for each query; there might be more candidates in total - if (max_options != -1){ + + const query_suffix = max_options != -1 ? `&P=0L${max_options}` : ""; + if (max_options != -1) { var n_entities = datatype !== "FILE" ? await edit_mode.query(`COUNT Record ${find_entity}`, true) : 0; var n_files = await edit_mode.query(`COUNT File ${find_entity}`, true); } - var entities = datatype !== "FILE" ? edit_mode.query(`FIND Record ${find_entity}&P=0L${max_options}`, true) : []; - var files = edit_mode.query(`FIND File ${find_entity}&P=0L${max_options}`, true); + const entities = datatype !== "FILE" ? edit_mode.query(`FIND Record ${find_entity}${query_suffix}`, true) : []; + const files = edit_mode.query(`FIND File ${find_entity}${query_suffix}`, true); await Promise.all([entities, files]) @@ -1907,7 +1911,7 @@ var edit_mode = new function () { .concat(edit_mode ._create_reference_options(await files)); - if (max_options !=- 1 && (n_entities > max_options || n_files > max_options)) { + if (max_options != -1 && (n_entities > max_options || n_files > max_options)) { // add notification that not all entities are shown options = [$(`<option disabled=true value="NA"/>`).text( `More than ${max_options} possible options; showing only a subset. You may need to enter the id manually.` diff --git a/src/core/js/ext_cosmetics.js b/src/core/js/ext_cosmetics.js index 9443bf65c8fa3effa57a64491563479ca51c91d0..aeb88cc9763ccc48dc01f0a25670fb67cf471198 100644 --- a/src/core/js/ext_cosmetics.js +++ b/src/core/js/ext_cosmetics.js @@ -1,10 +1,11 @@ /* * This file is a part of the LinkAhead Project. * - * Copyright (C) 2021-2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021-2024 IndiScale GmbH <info@indiscale.com> * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> * Copyright (C) 2023 Florian Spreckelsen <f.spreckelsen@indiscale.com> * Copyright (C) 2023 Daniel Hornung <d.hornung@indiscale.com> + * Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -51,66 +52,81 @@ var cosmetics = new function () { }); } - /** - * Remove all the custom datetime elements again, for example when entering the edit mode. - */ - var _custom_datetime_clear = function() { - $('.caosdb-v-property-datetime-customized-newvalue').each(function () { - $(this).remove(); + /** + * Remove all the custom datetime elements again, for example when entering the edit mode. + */ + var _custom_datetime_clear = function () { + $('.caosdb-v-property-datetime-customized-newvalue').each(function () { + $(this).remove(); + } + ) } - ) - } - var _linkify = function () { - $('.caosdb-f-property-text-value').each(function (index) { - if (!($(this).hasClass("caosdb-v-property-linkified")) && (/https?:\/\//.test(this.innerText))) { - var result = this.innerText.replace(/https?:\/\/[^\s]*/g, function (href, index) { - var link_text = href; - if (_link_cut_off_length > 4 && link_text.length > _link_cut_off_length) { - link_text = link_text.substring(0, _link_cut_off_length - 5) + "[...]"; - } + /** + * Return a string with all occurences of a http(s) url in a given string replaced by <a> elements + * @param {string} text - The text to be searched for URLs + * @returns {string} text with <a> elements instead of raw links + */ + function linkify_string(text) { + // const match_url_regex = /https?:\/\/[^\s]*/g // original + // https://regexr.com helps you design regex + const match_url_regex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.?[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g + + return text.replace(match_url_regex, function (href) { + var link_text = href; + if (_link_cut_off_length > 4 && link_text.length > _link_cut_off_length) { + link_text = link_text.substring(0, _link_cut_off_length - 5) + "[...]"; + } - return `<a title="Open ${href} in a new tab." target="_blank" class="caosdb-v-property-href-value" href="${href}">${link_text} <i class="bi bi-box-arrow-up-right"></i></a>`; - }); + return `<a title="Open ${href} in a new tab." target="_blank" class="caosdb-v-property-href-value" href="${href}">${link_text} <i class="bi bi-box-arrow-up-right"></i></a>`; + }); + } + /** + * Turn all URLs in .caosdb-f-property-text-value DOM elements into actual links + */ + var _linkify_all_text_values = function () { + $('.caosdb-f-property-text-value').each(function (index) { + if (!($(this).hasClass("caosdb-v-property-linkified")) && (/https?:\/\//.test(this.innerText))) { + var linkified_text_value = linkify_string(this.innerText); // add class to highlight that this has been linkified already // (see https://gitlab.com/caosdb/caosdb-webui/-/issues/199). $(this).addClass("caosdb-v-property-linkified") $(this).hide(); - $(this).after(result); + $(this).after(linkified_text_value); } }); } /** - * Customize datetime formatting. + * Convert any substring of a text-value beginning with 'http(s)://' into a + * link. * * A listener detects edit-mode changes and previews */ - var custom_datetime = function () { - _custom_datetime(); + var linkify = function () { + _linkify_all_text_values(); - // edit-mode-listener to delete replacement - document.body.addEventListener(edit_mode.start_edit.type, _custom_datetime_clear, true); - // edit-mode-listener to recreate - document.body.addEventListener(edit_mode.end_edit.type, _custom_datetime, true); + // edit-mode-listener + document.body.addEventListener(edit_mode.end_edit.type, _linkify_all_text_values, true); // preview listener - document.body.addEventListener(preview.previewReadyEvent.type, _custom_datetime, true); + document.body.addEventListener(preview.previewReadyEvent.type, _linkify_all_text_values, true); } /** - * Convert any substring of a text-value beginning with 'http(s)://' into a - * link. + * Customize datetime formatting. * * A listener detects edit-mode changes and previews */ - var linkify = function () { - _linkify(); + var custom_datetime = function () { + _custom_datetime(); - // edit-mode-listener - document.body.addEventListener(edit_mode.end_edit.type, _linkify, true); + // edit-mode-listener to delete replacement + document.body.addEventListener(edit_mode.start_edit.type, _custom_datetime_clear, true); + // edit-mode-listener to recreate + document.body.addEventListener(edit_mode.end_edit.type, _custom_datetime, true); // preview listener - document.body.addEventListener(preview.previewReadyEvent.type, _linkify, true); + document.body.addEventListener(preview.previewReadyEvent.type, _custom_datetime, true); } this.init = function () { @@ -123,7 +139,6 @@ var cosmetics = new function () { linkify(); } } - } diff --git a/src/core/js/query_shortcuts.js b/src/core/js/query_shortcuts.js index cd69b4cf791e4281ff8e35be8698a91d71abc0a4..08a23efdc44d634f2c588173a6ac2f3d2ca0e06e 100644 --- a/src/core/js/query_shortcuts.js +++ b/src/core/js/query_shortcuts.js @@ -44,7 +44,7 @@ * } * ] */ -var query_shortcuts = new function() { +var query_shortcuts = new function () { this.dependencies = ["form_elements", "log", "transaction"]; @@ -68,7 +68,7 @@ var query_shortcuts = new function() { * * @return {HTMLElement} The toolbox drop-down. */ - this.make_toolbox_button = function() { + this.make_toolbox_button = function () { var ret = $( `<div class="dropdown text-end caosdb-f-shortcuts-toolbox-button"> <button title="Shortcuts Toolbox" class="btn dropdown-bs-toggle" type="button" @@ -86,17 +86,17 @@ var query_shortcuts = new function() { // bind callback functions ret.find("button[data-tool='delete']") - .click(function() { + .click(function () { var shortcuts_panel = $(".caosdb-shortcuts-container"); query_shortcuts.init_delete_shortcut_form(shortcuts_panel[0]); }); ret.find("button[data-tool='create']") - .click(function() { + .click(function () { var shortcuts_panel = $(".caosdb-shortcuts-container"); query_shortcuts.init_create_shortcut_form(shortcuts_panel[0]); }); ret.find("button[data-tool='edit']") - .click(function() { + .click(function () { var shortcuts_panel = $(".caosdb-shortcuts-container"); query_shortcuts.init_update_shortcut_form(shortcuts_panel[0]); }); @@ -104,13 +104,13 @@ var query_shortcuts = new function() { return ret[0]; } - this.init_datamodel = function() { + this.init_datamodel = function () { // TODO handle the datamodel dependencies in a save way (lazy-loading is // bad, if the data-model is not there). - if(typeof this._shortcuts_property_query_id == "undefined") { + if (typeof this._shortcuts_property_query_id == "undefined") { query("FIND Property " + query_shortcuts._shortcuts_property_query_name) .then(result => { - if(result.length > 0) { + if (result.length > 0) { query_shortcuts._shortcuts_property_query_id = getEntityID(result[0]); } else { query_shortcuts._shortcuts_property_query_id = null; @@ -118,10 +118,10 @@ var query_shortcuts = new function() { }) .catch(query_shortcuts.logger.error); } - if(typeof this._shortcuts_property_description_id == "undefined") { + if (typeof this._shortcuts_property_description_id == "undefined") { query("FIND Property " + this._shortcuts_property_description_name) .then(result => { - if(result.length > 0) { + if (result.length > 0) { query_shortcuts._shortcuts_property_description_id = getEntityID(result[0]); } else { query_shortcuts._shortcuts_property_description_id = null; @@ -129,10 +129,10 @@ var query_shortcuts = new function() { }) .catch(query_shortcuts.logger.error); } - if(typeof this._shortcuts_record_type_id == "undefined") { + if (typeof this._shortcuts_record_type_id == "undefined") { query("FIND RecordType " + query_shortcuts._shortcuts_record_type_name) .then(result => { - if(result.length > 0) { + if (result.length > 0) { query_shortcuts._shortcuts_record_type_id = getEntityID(result[0]); } else { query_shortcuts._shortcuts_record_type_id = null; @@ -142,7 +142,7 @@ var query_shortcuts = new function() { } } - this.init = async function() { + this.init = async function () { this.init_datamodel(); var header = $('<details class="caosdb-f-shortcuts-panel-header"><summary class="caosdb-f-shortcuts-panel-header-title">Shortcuts</summary></details>') header.find("summary").append(this.make_toolbox_button()); @@ -153,13 +153,19 @@ var query_shortcuts = new function() { body.append(await this.retrieve_global_shortcuts()); - if(isAuthenticated()) { + if (isAuthenticated()) { body.append(await this.retrieve_user_shortcuts()); } if (body.children(".caosdb-f-query-shortcut").length > 0) { // append if not empty - $("#caosdb-query-panel").append(shortcuts_panel); + if ("${BUILD_MODULE_LEGACY_QUERY_FORM}" != "ENABLED") { + // The selector has a different name with the new panel + const queryPanelSelectorName = ".caosdb-f-query-form"; + $(queryPanelSelectorName).after(shortcuts_panel); + } else { + $("#caosdb-query-panel").append(shortcuts_panel); + } } return shortcuts_panel[0]; @@ -178,9 +184,9 @@ var query_shortcuts = new function() { * @param {string} str * @returns {string[]} array with all ids of placeholders */ - this.find_placeholders = function(str) { + this.find_placeholders = function (str) { const re = /\{(\w*?)\}/g; - var match = [...str.matchAll(re)]; + var match = [...str.matchAll(re)]; var ret = [] for (const ph of match) { @@ -202,7 +208,7 @@ var query_shortcuts = new function() { * @return {string} the description with all placeholders replaced with * input form elements. */ - this.replace_placeholders_with_inputs = function(description) { + this.replace_placeholders_with_inputs = function (description) { var placeholders = this.find_placeholders(description); var ret = description; @@ -229,7 +235,7 @@ var query_shortcuts = new function() { * @return {string} the query string with all placeholders replaced by the * values in the dictionary. */ - this.replace_placeholders_with_values = function(query, values) { + this.replace_placeholders_with_values = function (query, values) { var placeholders = this.find_placeholders(query); var ret = query; @@ -251,7 +257,7 @@ var query_shortcuts = new function() { * @param {HTMLElement} shortcut_form - a single shortcut form. * @returns {object} a dictionary with the inputs' names as keys. */ - this.extract_placeholder_values = function(shortcut_form) { + this.extract_placeholder_values = function (shortcut_form) { var ret = {} for (const input of $(shortcut_form).find("input")) { ret[input.name] = input.value; @@ -272,7 +278,7 @@ var query_shortcuts = new function() { * @param {string} query_string * @param {HTMLElement} A `DIV.caosdb-f-query-shortcut.row`. */ - this.generate_shortcut_form = function(description, query_string) { + this.generate_shortcut_form = function (description, query_string) { var preparedstr = query_shortcuts.replace_placeholders_with_inputs(description); var shortcut_form = $( `<div class="row caosdb-f-query-shortcut"> @@ -285,18 +291,28 @@ var query_shortcuts = new function() { ); // function for inserting the generated query string into the query panel - var insert_to_query_panel = (_) => { + var insert_to_query_panel = (inputSelectorName) => { var values = query_shortcuts.extract_placeholder_values(shortcut_form[0]); var replaced_query_string = query_shortcuts.replace_placeholders_with_values(query_string, values); - $("#caosdb-query-textarea") + + $(inputSelectorName) .focus() .val(replaced_query_string); + + return replaced_query_string; }; // callback for the submission var execute = (_) => { - insert_to_query_panel(); - $("#caosdb-query-form").submit(); + + if ("${BUILD_MODULE_LEGACY_QUERY_FORM}" != "ENABLED") { + const queryString = insert_to_query_panel(".caosdb-f-query-form input"); + window.localStorage.setItem("query.queryString", queryString); + $(".caosdb-f-query-panel form").submit() + } else { + insert_to_query_panel("#caosdb-query-textarea"); + $("#caosdb-query-form").submit(); + } }; shortcut_form.find(".caosdb-f-query-shortcut-right-col") @@ -304,7 +320,7 @@ var query_shortcuts = new function() { return shortcut_form[0]; } - this.generate_user_shortcut = function(description, query_string, id) { + this.generate_user_shortcut = function (description, query_string, id) { const ret = query_shortcuts.generate_shortcut_form(description, query_string); $(ret).toggleClass("caosdb-f-user-query-shortcut", true) .attr("data-entity-id", id) @@ -321,7 +337,7 @@ var query_shortcuts = new function() { * * @returns {HTMLElement[]} array of query shortcut forms. */ - this.retrieve_global_shortcuts = async function() { + this.retrieve_global_shortcuts = async function () { try { var temparray = await query_shortcuts._query_global_shortcuts(); @@ -332,7 +348,7 @@ var query_shortcuts = new function() { ret.push(this.generate_shortcut_form(tempel.description, tempel.query)); } return ret; - } catch (err){ + } catch (err) { query_shortcuts.logger.error(err); } @@ -350,7 +366,7 @@ var query_shortcuts = new function() { * * @returns {HTMLElement[]} array of query shortcut forms. */ - this.retrieve_user_shortcuts = async function() { + this.retrieve_user_shortcuts = async function () { try { var temparray = await query_shortcuts._query_user_shortcuts(); @@ -359,7 +375,7 @@ var query_shortcuts = new function() { var entity_id = getEntityID(temparray[i]); var description = getProperty(temparray[i], query_shortcuts._shortcuts_property_description_name, false); var query_string = getProperty(temparray[i], query_shortcuts._shortcuts_property_query_name, false); - if(query_string && description) { + if (query_string && description) { ret.push(this.generate_user_shortcut(description, query_string, entity_id)); } else { this.logger.warn("User-defined query shortcut without description or query", temparray[i]); @@ -367,8 +383,8 @@ var query_shortcuts = new function() { } return ret; } catch (err) { - if (typeof err.message === "string" - || err.message instanceof String) { + if (typeof err.message === "string" || + err.message instanceof String) { if (err.message.indexOf("404") > -1) { query_shortcuts.logger.warn(err); return; @@ -412,28 +428,28 @@ var query_shortcuts = new function() { * @param {HTMLElement} panel - the shortcuts panel with global and * user-defined shortcuts. */ - this.init_delete_shortcut_form = function(panel) { + this.init_delete_shortcut_form = function (panel) { $(panel).find(".alert").remove(); var form = this.make_delete_form(panel, this.delete_callback, this.handle_delete_success, this.handle_delete_error); this.init_cud_shortcut_form(panel, form); return panel; } - this.init_create_shortcut_form = function(panel) { + this.init_create_shortcut_form = function (panel) { $(panel).find(".alert").remove(); var form = this.make_create_form(panel, this.create_callback, this.handle_create_success, this.handle_create_error); this.init_cud_shortcut_form(panel, form); return panel; } - this.init_update_shortcut_form = function(panel) { + this.init_update_shortcut_form = function (panel) { $(panel).find(".alert").remove(); var selector = this.make_update_selector(panel, this.init_update_single_shortcut_form); this.init_cud_shortcut_form(panel, selector); } - this.init_update_single_shortcut_form = function(panel, entity_id) { + this.init_update_single_shortcut_form = function (panel, entity_id) { $(panel).find(".alert").remove(); var form = query_shortcuts.make_update_form(panel, entity_id, query_shortcuts.update_callback, query_shortcuts.handle_update_success, query_shortcuts.handle_update_error); query_shortcuts.init_cud_shortcut_form(panel, form); @@ -441,7 +457,7 @@ var query_shortcuts = new function() { } - this.handle_update_error = function(panel, results) { + this.handle_update_error = function (panel, results) { query_shortcuts.logger.trace("enter handle_update_error", panel, results); var errors = $(results).find(".alert-danger"); @@ -451,7 +467,7 @@ var query_shortcuts = new function() { } - this.handle_create_error = function(panel, results) { + this.handle_create_error = function (panel, results) { query_shortcuts.logger.trace("enter handle_create_error", panel, results); var errors = $(results).find(".alert-danger"); @@ -465,7 +481,7 @@ var query_shortcuts = new function() { * Add an "Edit" button to each query shortcut which opens the actual * update form. */ - this.make_update_selector = function(panel, init_update_form) { + this.make_update_selector = function (panel, init_update_form) { caosdb_utils.assert_html_element(panel, "param `panel`"); // clone panel @@ -485,7 +501,7 @@ var query_shortcuts = new function() { // remove old buttons wrapper.children().remove(); - if($(item).hasClass("caosdb-f-user-query-shortcut")) { + if ($(item).hasClass("caosdb-f-user-query-shortcut")) { // user shortcut // insert "UPDATE" button var entity_id = $(item).attr("data-entity-id"); @@ -497,75 +513,84 @@ var query_shortcuts = new function() { // global shortcut // gray out and add hint that this is a global shortcut which cannot be deleted. $(item) - .css({"color": "#CCCCCC"}) + .css({ + "color": "#CCCCCC" + }) .attr("title", "This is a global shortcut which cannot be updated via the browser."); } }); - var form = form_elements.make_form({fields: cloned.children().toArray(), submit: false}); + var form = form_elements.make_form({ + fields: cloned.children().toArray(), + submit: false + }); return form; } - this.init_cud_shortcut_form = function(panel, form) { + this.init_cud_shortcut_form = function (panel, form) { // hide content $(panel).children().hide(); var query_panel = $(".caosdb-query-form").hide(); // show original again on cancel - form.addEventListener("caosdb.form.cancel", function(e) { + form.addEventListener("caosdb.form.cancel", function (e) { query_panel.show(); $(panel).children().show(); }, true); // show results in query panel - form.addEventListener("caosdb.form.success", function(e) { + form.addEventListener("caosdb.form.success", function (e) { query_panel.show(); }, true); - form.addEventListener("caosdb.form.submit", function(e) { + form.addEventListener("caosdb.form.submit", function (e) { $(panel).find(".alert").remove(); }, true); $(panel).append(form); } - this.highlight_resulting_entities = function(panel, results) { + this.highlight_resulting_entities = function (panel, results) { for (const elem of $(results).find("[data-entity-id]")) { var result_id = $(elem).attr("data-entity-id"); var highlight = $(panel) - .find(".caosdb-f-user-query-shortcut[data-entity-id='" - + result_id + "']") - .toggleClass("caosdb-v-success-query-shortcut",true); + .find(".caosdb-f-user-query-shortcut[data-entity-id='" + + result_id + "']") + .toggleClass("caosdb-v-success-query-shortcut", true); highlight - .css({"background": "#90EE90"}); + .css({ + "background": "#90EE90" + }); } setTimeout(() => { $(".caosdb-v-success-query-shortcut") - .css({"background": "unset"}); - setTimeout(() => { - $(".caosdb-v-success-query-shortcut") - .toggleClass("caosdb-v-success-query-shortcut",false); - }, 2000); + .css({ + "background": "unset" + }); + setTimeout(() => { + $(".caosdb-v-success-query-shortcut") + .toggleClass("caosdb-v-success-query-shortcut", false); + }, 2000); }, 5000); } - this.append_messages_handler = function(panel, results) { + this.append_messages_handler = function (panel, results) { for (const entity of results) { var result_id = undefined; - if($(entity).is("[data-entity-id]")){ + if ($(entity).is("[data-entity-id]")) { result_id = $(entity).attr("data-entity-id"); } else { result_id = $(elem).attr("data-entity-id"); } - if(typeof result_id !== "undefined") { + if (typeof result_id !== "undefined") { var alerts = $(entity) .find("div.alert") .toggleClass("col-md-12"); $(panel) - .find(".caosdb-f-user-query-shortcut[data-entity-id='" - + result_id + "']") + .find(".caosdb-f-user-query-shortcut[data-entity-id='" + + result_id + "']") .append(alerts); } } @@ -575,7 +600,7 @@ var query_shortcuts = new function() { * Reset the query shortcuts, i.e. remove the old container and call init * again. */ - this.reset = async function() { + this.reset = async function () { $(".caosdb-shortcuts-container").remove(); return await query_shortcuts.init(); } @@ -583,7 +608,7 @@ var query_shortcuts = new function() { /** * Reset the shortcut container, highlight the new shortcuts. */ - this.handle_create_success = async function(panel, results) { + this.handle_create_success = async function (panel, results) { query_shortcuts.logger.trace("enter handle_create_success", panel, results); var new_panel = await query_shortcuts.reset(); query_shortcuts.highlight_resulting_entities(new_panel, results); @@ -596,7 +621,7 @@ var query_shortcuts = new function() { /** * Reset the shortcut container, highlight the updated shortcuts. */ - this.handle_update_success = async function(panel, results) { + this.handle_update_success = async function (panel, results) { query_shortcuts.logger.trace("enter handle_update_success", panel, results); var new_panel = await query_shortcuts.reset(); query_shortcuts.highlight_resulting_entities(new_panel, results); @@ -605,7 +630,7 @@ var query_shortcuts = new function() { query_shortcuts.logger.trace("leave handle_update_success"); } - this.make_dismissible_alert = function(type, content) { + this.make_dismissible_alert = function (type, content) { var ret = $( `<div class="alert alert-` + type + ` alert-dismissible" role="alert"> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"> @@ -621,7 +646,7 @@ var query_shortcuts = new function() { * This method does not reset the shortcuts container, so the form's [OK] * button is still visible. */ - this.handle_delete_success = function(panel, results) { + this.handle_delete_success = function (panel, results) { query_shortcuts.logger.trace("enter handle_delete_success", panel, results); query_shortcuts.append_messages_handler(panel, results); @@ -636,7 +661,7 @@ var query_shortcuts = new function() { * This method does not reset the shortcuts container, so the form's * [Submit] and [Cancel] buttons are still visible. */ - this.handle_delete_error = function(panel, results) { + this.handle_delete_error = function (panel, results) { query_shortcuts.logger.trace("enter handle_delete_error", panel, results); query_shortcuts.append_messages_handler(panel, results); @@ -645,35 +670,46 @@ var query_shortcuts = new function() { } - this.make_shortcut_buttons = function(execute, customize) { + this.make_shortcut_buttons = function (execute, customize) { const execute_button = $('<button class="btn btn-primary" type="button" title="Execute query."><i class="bi-search"></i></button>') - .css({"font-size": "12px"}) + .css({ + "font-size": "12px" + }) .click(execute); const customize_button = $('<button class="btn btn-primary" type="button" title="Write to the Query Panel for customization."><i class="bi-pencil"></i></button>') - .css({"font-size": "12px"}) + .css({ + "font-size": "12px" + }) .click(customize) - .mouseenter(function(e) { + .mouseenter(function (e) { $("#caosdb-query-textarea") - .css({"border-color": "#66afe9", + .css({ + "border-color": "#66afe9", "outline": 0, - "-webkit-box-shadow": - "inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)", - "box-shadow": - "inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)"}); + "-webkit-box-shadow": "inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)", + "box-shadow": "inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)" + }); }) - .mouseleave(function(e) { + .mouseleave(function (e) { $('#caosdb-query-textarea').attr("style", ""); }); const button_group = $('<div class="btn-group"/>') - .css({"font-size": "12px", position: "absolute", top: 0, right: "12px"}) + .css({ + "font-size": "12px", + position: "absolute", + top: 0, + right: "12px" + }) .append(customize_button, execute_button) .hide() - .mouseleave(function(e) { + .mouseleave(function (e) { $(this).hide(); }); const hover = $('<button class="btn btn-secondary caosdb-button-search"><i class="bi-search" aria-hidden="true"></i></button>') - .css({"font-size": "12px"}) + .css({ + "font-size": "12px" + }) .click(execute) .mouseenter(() => { button_group.fadeIn(150); @@ -692,7 +728,7 @@ var query_shortcuts = new function() { * @param {function} delete_callback - the submit function for the form. * @returns {HTMLElement} a form. */ - this.make_delete_form = function(panel, delete_callback, success_handler, error_handler) { + this.make_delete_form = function (panel, delete_callback, success_handler, error_handler) { caosdb_utils.assert_html_element(panel, "param `panel`"); caosdb_utils.assert_type(delete_callback, "function", "param `delete_callback`"); // clone panel @@ -713,7 +749,7 @@ var query_shortcuts = new function() { // remove old buttons wrapper.children().remove(); - if($(item).hasClass("caosdb-f-user-query-shortcut")) { + if ($(item).hasClass("caosdb-f-user-query-shortcut")) { // user shortcut // insert checkbox var entity_id = $(item).attr("data-entity-id"); @@ -726,7 +762,9 @@ var query_shortcuts = new function() { // global shortcut // gray out and add hint that this is a global shortcut which cannot be deleted. $(item) - .css({"color": "#CCCCCC"}) + .css({ + "color": "#CCCCCC" + }) .attr("title", "This is a global shortcut which cannot be deleted."); } }); @@ -744,22 +782,22 @@ var query_shortcuts = new function() { return form; } - this.make_form_entity = function(entity_id) { + this.make_form_entity = function (entity_id) { var entity = $(` <div class="invisible" data-entity-role="Record"> </div>`); - if(typeof entity_id === "string" || entity instanceof String) { + if (typeof entity_id === "string" || entity instanceof String) { entity.attr("data-entity-id", entity_id); - entity.append(`<div class="caosdb-id">` - + entity_id + `</div>`); + entity.append(`<div class="caosdb-id">` + + entity_id + `</div>`); } return entity[0]; } - this.make_query_shortcut_parent = function() { - return $(`<div class="caosdb-parent-name">` - + query_shortcuts._shortcuts_record_type_name + `</div>`)[0]; + this.make_query_shortcut_parent = function () { + return $(`<div class="caosdb-parent-name">` + + query_shortcuts._shortcuts_record_type_name + `</div>`)[0]; } @@ -771,7 +809,7 @@ var query_shortcuts = new function() { * @param {function} create_callback - the submit function for the form. * @returns {HTMLElement} a form. */ - this.make_create_form = function(panel, create_callback, success_handler, error_handler) { + this.make_create_form = function (panel, create_callback, success_handler, error_handler) { caosdb_utils.assert_html_element(panel, "param `panel`"); caosdb_utils.assert_type(create_callback, "function", "param `create_callback`"); // clone panel @@ -812,30 +850,36 @@ var query_shortcuts = new function() { * @param {HTMLElement} form - form which contains the fields where the * class is to be added. */ - this._toggle_entity_property_class = function(form) { + this._toggle_entity_property_class = function (form) { form.addEventListener("caosdb.form.ready", () => { $(form).find(".caosdb-f-field").toggleClass("caosdb-f-entity-property", true); }); $(form).find(".caosdb-f-field").toggleClass("caosdb-f-entity-property", true); } - this.make_create_fields = function(include) { + this.make_create_fields = function (include) { return [ include, { - type: "text", name: query_shortcuts._shortcuts_property_description_name, - label: "Description", required: true, cached: true, + type: "text", + name: query_shortcuts._shortcuts_property_description_name, + label: "Description", + required: true, + cached: true, //help: query_shortcuts._description_help, TODO }, { - type: "text", name: query_shortcuts._shortcuts_property_query_name, - label: "Query", required: true, cached: true, + type: "text", + name: query_shortcuts._shortcuts_property_query_name, + label: "Query", + required: true, + cached: true, //help: query_shortcuts._query_help, TODO } ]; } - this.make_update_fields = function(include, old_description, old_query) { + this.make_update_fields = function (include, old_description, old_query) { var fields = this.make_create_fields(include); fields[1]["cached"] = false; fields[2]["cached"] = false; @@ -845,23 +889,23 @@ var query_shortcuts = new function() { } - this.get_description_id = function() { - while(typeof query_shortcuts._shortcuts_property_description_id == "undefined") { + this.get_description_id = function () { + while (typeof query_shortcuts._shortcuts_property_description_id == "undefined") { // wait } return query_shortcuts._shortcuts_property_description_id; } - this.get_query_id = function() { - while(typeof query_shortcuts._shortcuts_property_query_id == "undefined") { + this.get_query_id = function () { + while (typeof query_shortcuts._shortcuts_property_query_id == "undefined") { // wait } return query_shortcuts._shortcuts_property_query_id; } - this.get_record_type_id = function() { - while(typeof query_shortcuts._shortcuts_record_type_id == "undefined") { + this.get_record_type_id = function () { + while (typeof query_shortcuts._shortcuts_record_type_id == "undefined") { // wait } return query_shortcuts._shortcuts_record_type_id; @@ -890,7 +934,7 @@ var query_shortcuts = new function() { * @param {function} update_callback - the submit function for the form. * @returns {HTMLElement} a form. */ - this.make_update_form = function(panel, entity_id, update_callback, success_handler, error_handler) { + this.make_update_form = function (panel, entity_id, update_callback, success_handler, error_handler) { caosdb_utils.assert_html_element(panel, "param `panel`"); caosdb_utils.assert_type(update_callback, "function", "param `update_callback`"); caosdb_utils.assert_type(success_handler, "function", "param `success_handler`"); @@ -940,18 +984,18 @@ var query_shortcuts = new function() { /** * Return the check entities of the form as an array of ids. */ - this.get_checked_ids = function(form) { + this.get_checked_ids = function (form) { var checked = []; $(form).find(":checked").each((idx, item) => { checked.push(item.name); }); - return checked; + return checked; } /** * Return the entities of the form in XML representation. */ - this.get_shortcut_entities = function(form) { + this.get_shortcut_entities = function (form) { var entities = []; entities.push(getEntityXML(form)); return entities; @@ -961,7 +1005,7 @@ var query_shortcuts = new function() { * @throws {HTMLElement[]} if the results contains `DIV.alert-errors`. * @returns {HTMLElement[]} if the transaction was successful. */ - this.delete_callback = async function(form) { + this.delete_callback = async function (form) { query_shortcuts.logger.trace("enter delete_callback", form); var dels_array = query_shortcuts.get_checked_ids(form); @@ -979,7 +1023,7 @@ var query_shortcuts = new function() { * @throws {HTMLElement[]} if the results contains `DIV.alert-errors`. * @returns {HTMLElement[]} if the transaction was successful. */ - this.create_callback = async function(form) { + this.create_callback = async function (form) { query_shortcuts.logger.trace("enter create_callback", form); var insert_xml = query_shortcuts.get_shortcut_entities(form); @@ -997,7 +1041,7 @@ var query_shortcuts = new function() { * @throws {HTMLElement[]} if the results contains `DIV.alert-errors`. * @returns {HTMLElement[]} if the transaction was successful. */ - this.update_callback = async function(form) { + this.update_callback = async function (form) { query_shortcuts.logger.trace("enter update_callback", form); var update_xml = query_shortcuts.get_shortcut_entities(form); @@ -1009,8 +1053,8 @@ var query_shortcuts = new function() { // the recordtype for user-defined query shortcuts var qs_id = query_shortcuts.get_record_type_id(); - $(update_xml).find("Property[name='"+ query_shortcuts._shortcuts_property_description_name+"']").attr("id", desc_id); - $(update_xml).find("Property[name='" + query_shortcuts._shortcuts_property_query_name + "']").attr("id", query_id); + $(update_xml).find("Property[name='" + query_shortcuts._shortcuts_property_description_name + "']").attr("id", desc_id); + $(update_xml).find("Property[name='" + query_shortcuts._shortcuts_property_query_name + "']").attr("id", query_id); $(update_xml).find("Parent[name='" + query_shortcuts._shortcuts_record_type_name + "']").attr("id", qs_id); query_shortcuts.logger.debug("update shortcuts", update_xml); @@ -1026,7 +1070,7 @@ var query_shortcuts = new function() { * Transform the reponse XML into an array of entities in HTML * representation. */ - this.transform_entities = async function(response) { + this.transform_entities = async function (response) { var ret = await query_shortcuts._transformEntities(response); // throw errors: @@ -1100,6 +1144,9 @@ var query_shortcuts = new function() { color: #333; } + .caosdb-f-query-panel.condensed .caosdb-shortcuts-container { + display: none; + } `; var styleSheet = document.createElement("style"); @@ -1111,6 +1158,6 @@ var query_shortcuts = new function() { -$(document).ready(function() { +$(document).ready(function () { caosdb_modules.register(query_shortcuts); }); diff --git a/src/doc/conf.py b/src/doc/conf.py index b941fb5457da034386ba7475a95d7f5da19ef8c5..a33fa2072f6c2c82e644be56519081d46728434a 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -22,13 +22,13 @@ import sphinx_rtd_theme project = 'linkahead-webui' -copyright = '2022, IndiScale GmbH' +copyright = '2022 - 2024, IndiScale GmbH' author = 'Daniel Hornung' # The short X.Y version -version = '0.13.1' +version = '0.13.2' # The full version, including alpha/beta/rc tags -release = '0.13.1' +release = '0.13.2' # -- General configuration --------------------------------------------------- diff --git a/src/doc/index.rst b/src/doc/index.rst index 5ed1f417a7456157fa8a0c6905fbd1d7be5d513d..f5b5d9c5828c8ff7e3d5d26b37dbe6653fc00541 100644 --- a/src/doc/index.rst +++ b/src/doc/index.rst @@ -1,6 +1,6 @@ Welcome to the documentation of LinkAhead's web UI! -================================================ +=================================================== .. toctree:: :maxdepth: 2 @@ -12,6 +12,8 @@ Welcome to the documentation of LinkAhead's web UI! administration/index.rst Extending the UI <extension> API <api/index> + Related Projects <related_projects/index> + Back to overview <https://docs.indiscale.com/> .. note:: diff --git a/src/doc/related_projects/index.rst b/src/doc/related_projects/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..e7d2eaaced5f1c50cf54580ea1a63cdcbe014df5 --- /dev/null +++ b/src/doc/related_projects/index.rst @@ -0,0 +1,25 @@ +Related Projects +++++++++++++++++ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + :hidden: + +.. container:: projects + + For in-depth documentation for users, administrators and developers, you may want to visit the subproject-specific documentation pages for: + + :`Server <https://docs.indiscale.com/caosdb-server>`_: The Java part of the LinkAhead server. + + :`MySQL backend <https://docs.indiscale.com/caosdb-mysqlbackend>`_: The MySQL/MariaDB components of the LinkAhead server. + + :`PyLinkAhead <https://docs.indiscale.com/caosdb-pylib>`_: The LinkAhead Python library. + + :`Advanced user tools <https://docs.indiscale.com/caosdb-advanced-user-tools>`_: The advanced Python tools for LinkAhead. + + :`LinkAhead Crawler <https://docs.indiscale.com/caosdb-crawler/>`_: The crawler is the main tool for automatic data integration in LinkAhead. + + :`LinkAhead <https://docs.indiscale.com/caosdb-deploy>`_: Your all inclusive LinkAhead software package. + + :`Back to Overview <https://docs.indiscale.com/>`_: LinkAhead Documentation. diff --git a/src/server_side_scripting/ext_table_preview/pandas_table_preview.py b/src/server_side_scripting/ext_table_preview/pandas_table_preview.py index 6cfb1d2fc20130133f753067b7194836ccae3ba7..13c9b394648548bd6124de8a7318b18bb8a9221c 100755 --- a/src/server_side_scripting/ext_table_preview/pandas_table_preview.py +++ b/src/server_side_scripting/ext_table_preview/pandas_table_preview.py @@ -99,28 +99,28 @@ def read_file(fipath, ftype): def create_table_preview(fi, entity_id): if not ending_is_valid(fi.path): print("Cannot create preview for Entity with ID={}, because download" - "failed.".format(entity_id), file=sys.stderr) + "failed.".format(fi.id), file=sys.stderr) sys.exit(5) ending = get_ending(fi.path) if not size_is_ok(fi): print("Skipped creating a preview for Entity with ID={}, because the" - "file is large!".format(entity_id), file=sys.stderr) + "file is large!".format(fi.id), file=sys.stderr) sys.exit(2) try: tmpfile = fi.download() except Exception: print("Cannot create preview for Entity with ID={}, because download" - "failed.".format(entity_id), file=sys.stderr) + "failed.".format(fi.id), file=sys.stderr) sys.exit(3) try: df = read_file(tmpfile, ending) except ValueError: - print("Cannot read File Entity with ID={}.".format(entity_id), + print("Cannot read File Entity with ID={}.".format(fi.id), file=sys.stderr) sys.exit(4) diff --git a/test/core/js/modules/ext_cosmetics.js.js b/test/core/js/modules/ext_cosmetics.js.js index f903469c6512779aea4df737e01eadd22a374daa..32ecd1750b96846187e24dfe2f44f6db6bc9785d 100644 --- a/test/core/js/modules/ext_cosmetics.js.js +++ b/test/core/js/modules/ext_cosmetics.js.js @@ -1,9 +1,10 @@ /* * This file is a part of the LinkAhead Project. * - * Copyright (C) 2021-2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021-2024 IndiScale GmbH <info@indiscale.com> * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> * Copyright (C) 2023 Daniel Hornung <d.hornung@indiscale.com> + * Copyright (C) 2024 Joscha Schmiedt <joscha@schmiedt.dev> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -51,12 +52,12 @@ QUnit.test("custom datetime", function (assert) { const text_value = $(`<span class="caosdb-f-property-datetime-value">${test_case[0]}</span>`); container.append(text_value); assert.equal($(container).find(" ").length, 0, "Test original datetime."); - cosmetics.custom_datetime(); - const newValueElement = + cosmetics.custom_datetime(); + const newValueElement = container[0].querySelector("span.caosdb-v-property-datetime-customized-newvalue"); - assert.ok(newValueElement, "Datetime customization: Test if result exists."); - assert.equal(newValueElement.innerHTML, test_case[1], - "Datetime customization: compared result."); + assert.ok(newValueElement, "Datetime customization: Test if result exists."); + assert.equal(newValueElement.innerHTML, test_case[1], + "Datetime customization: compared result."); container.remove(); } }); @@ -64,22 +65,22 @@ QUnit.test("custom datetime", function (assert) { QUnit.test("linkify - https", function (assert) { assert.ok(cosmetics.linkify, "linkify available"); var test_cases = [ - ["https://link", 1], - ["this is other text https://link", 1], - ["https://link this is other text", 1], - ["this is other text https://link and this as well", 1], - ["this is other text https://link", 1], - ["this is other text https://link and here comes another link https://link and more text", 2], + ["https://link", 1, "https://link",], + ["this is other text https://link.com", 1, "https://link.com"], + ["https://link; this is other text", 1, "https://link"], + ["this is other text https://link.de and this as well", 1, "https://link.de"], + ["this is other text https://link:3000", 1, "https://link:3000"], + ["this is other text https://link.org/test, and here comes another link https://link.org/test and more text", 2, "https://link.org/test"], ]; for (let test_case of test_cases) { - const container = $('<div></div>'); + var container = $('<div></div>'); $(document.body).append(container); const text_value = $(`<div class="caosdb-f-property-text-value">${test_case[0]}</div>`); container.append(text_value); - assert.equal($(container).find("a[href='https://link']").length, 0, "no link present"); + assert.equal($(container).find(`a[href='${test_case[2]}']`).length, 0, "no link present"); cosmetics.linkify(); - assert.equal($(container).find("a[href='https://link']").length, test_case[1], "link is present"); + assert.equal($(container).find(`a[href='${test_case[2]}']`).length, test_case[1], `link is present: ${$(container)[0].outerHTML}`); container.remove(); } });