diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d3746bad94bb5988121ad3555656e218353a4d..7d4f2b091f576a7d7468c6aa83bd41a628dcec83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `caosdb-v-property-linkified` css class to denote properties that have been linkified already. +* `caosdb-f-property-datetime-value` css class for special handling of datetime properties. Also + there is now very basic customization (via variable `BUILD_MODULE_EXT_COSMETICS_CUSTOMDATETIME`) + for how datetime values can be displayed, which comes along with the new css classes + `caosdb-v-property-datetime-customized` and `caosdb-v-property-datetime-customized-newvalue`. +* `form_elements.make_form_modal` and + `form_elements.make_scripting_submission_button` functions to create a form + modal and an SSS submission button, respectively. ### Changed (for changes in existing functionality) diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties index 795a360792b1683b7f4ba8f6dfd6e7f3047590fc..d21943b6fc51abf370b2939a7ac8a6bad72e9a04 100644 --- a/build.properties.d/00_default.properties +++ b/build.properties.d/00_default.properties @@ -52,6 +52,7 @@ BUILD_MODULE_EXT_BOOKMARKS=ENABLED BUILD_MODULE_EXT_ADD_QUERY_TO_BOOKMARKS=DISABLED BUILD_MODULE_EXT_ANNOTATION=ENABLED BUILD_MODULE_EXT_COSMETICS_LINKIFY=DISABLED +BUILD_MODULE_EXT_COSMETICS_CUSTOMDATETIME=DISABLED BUILD_MODULE_EXT_QRCODE=ENABLED BUILD_MODULE_SHOW_ID_IN_LABEL=DISABLED BUILD_MODULE_LEGACY_QUERY_FORM=ENABLED diff --git a/src/core/js/ext_cosmetics.js b/src/core/js/ext_cosmetics.js index da05f294f2022a469e74ec05d64211fe1f8b26b7..c29f07e42294ff3d8c0b4b86da829865b8a650c5 100644 --- a/src/core/js/ext_cosmetics.js +++ b/src/core/js/ext_cosmetics.js @@ -4,6 +4,7 @@ * Copyright (C) 2021-2023 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> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -31,11 +32,35 @@ var cosmetics = new function () { /** * Cut-off length of links. When linkify processes the links any href - * longer than this will be cut off at character 25 and "[...]" will be + * longer than this will be cut off at the end and "[...]" will be * appended for the link text. */ var _link_cut_off_length = 40; + var _custom_datetime = function () { + $('.caosdb-f-property-datetime-value').each(function (index) { + if (!($(this).hasClass("caosdb-v-property-datetime-customized"))) { + var result = this.innerText.replace(/T/, " ").replace(/\+.*/, ""); + result = `<span class="caosdb-v-property-datetime-customized-newvalue">${result}</span>`; + + // add class to highlight that this has been customized already + $(this).addClass("caosdb-v-property-datetime-customized") + $(this).hide(); + $(this).after(result); + } + }); + } + + /** + * 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))) { @@ -57,6 +82,22 @@ var cosmetics = new function () { }); } + /** + * Customize datetime formatting. + * + * A listener detects edit-mode changes and previews + */ + var custom_datetime = function () { + _custom_datetime(); + + // 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, _custom_datetime, true); + } + /** * Convert any substring of a text-value beginning with 'http(s)://' into a * link. @@ -73,6 +114,10 @@ var cosmetics = new function () { } this.init = function () { + this.custom_datetime = custom_datetime; + if ("${BUILD_MODULE_EXT_COSMETICS_CUSTOMDATETIME}" == "ENABLED") { + custom_datetime(); + } this.linkify = linkify; if ("${BUILD_MODULE_EXT_COSMETICS_LINKIFY}" == "ENABLED") { linkify(); diff --git a/src/core/js/ext_trigger_crawler_form.js b/src/core/js/ext_trigger_crawler_form.js index 0796ef77da36e730b05d70dbbd2e8728c6e65c79..a6e1e3a18a3582cc9b3e511880b72d15f730b346 100644 --- a/src/core/js/ext_trigger_crawler_form.js +++ b/src/core/js/ext_trigger_crawler_form.js @@ -41,7 +41,7 @@ * variable `BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX`. The default is * `Tools`. */ -var ext_trigger_crawler_form = function () { +var ext_trigger_crawler_form = function ($, form_elements) { var init = function (toolbox) { const _toolbox = toolbox || "${BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX}"; @@ -52,7 +52,7 @@ var ext_trigger_crawler_form = function () { const crawler_form = make_scripting_caller_form( script, button_name); - const modal = make_form_modal(crawler_form); + const modal = form_elements.make_form_modal(crawler_form, "Trigger the crawler", "Crawl the selected path"); navbar.add_tool(button_name, _toolbox, { @@ -63,32 +63,6 @@ var ext_trigger_crawler_form = function () { }); } - /** - * Wrap the form into a Bootstrap modal. - */ - var make_form_modal = function (form) { - const title = "Trigger the Crawler"; - const modal = $(` - <div class="modal fade" tabindex="-1" role="dialog"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" - class="btn-close" - data-bs-dismiss="modal" - aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - <h4 class="modal-title">${title}</h4> - </div> - <div class="modal-body"> - </div> - </div> - </div>`); - modal.find(".modal-body").append(form); - return modal[0]; - } - /** * Create the trigger crawler form. */ @@ -104,15 +78,7 @@ var ext_trigger_crawler_form = function () { }); $(warning_checkbox).find("input").attr("value", "TRUE"); - const scripting_caller = $(` - <form method="POST" action="/scripting"> - <input type="hidden" name="call" value="${script}"/> - <input type="hidden" name="-p0" value=""/> - <div class="form-control"> - <input type="submit" - class="form-control btn btn-primary" value="${button_name}"/> - </div> - </form>`); + const scripting_caller = form_elements.make_scripting_submission_button(script, button_name); scripting_caller.prepend(warning_checkbox).prepend(path_field); @@ -123,7 +89,7 @@ var ext_trigger_crawler_form = function () { init: init, }; -}(); +}($, form_elements); $(document).ready(function () { if ("${BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM}" === "ENABLED") { diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js index 6815acd791213c6b239a693c3c64667965c369ed..20ff4ead80d897bd8b90497c7c0f03351c0d92ff 100644 --- a/src/core/js/form_elements.js +++ b/src/core/js/form_elements.js @@ -1437,6 +1437,10 @@ var form_elements = new function () { /** * Return a select field. * + * IMPORTANT: The select picker has to be initialized by the client by + * calling ``form_elements.init_select_picker(ret, config.value)`` (see + * below and https://gitlab.com/caosdb/caosdb-webui/-/issues/208). + * * @param {SelectFieldConfig} config * @returns {HTMLElement} a select field. */ @@ -1454,7 +1458,7 @@ var form_elements = new function () { // case when this method is called and is controlled by the client. So // there is currently no other work-around than to call // init_select_picker after the form creation explicitely :( - //form_elements.init_select_picker(select[0], config.value); + // form_elements.init_select_picker(ret, config.value); return ret; } @@ -1563,6 +1567,58 @@ var form_elements = new function () { } } + /** + * Return a modal HTML element containing the given form. + * + * @param {HTMLElement} form - the form to be shown in the modal + * @param {string} title - the title of the form modal + * @param {string} explanationText - An optional paragraph shown between + * modal title and form. + */ + this.make_form_modal = function (form, title, explanationText) { + const modal = $(` + <div class="modal fade" tabindex="-1" role="dialog"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title">${title}</h4> + <button type="button" + class="btn-close" + data-bs-dismiss="modal" + aria-label="Close"> + </button> + </div> + <div class="modal-body"> + <p>${explanationText}</p> + </div> + </div> + </div>`); + + modal.find(".modal-body").append(form); + return modal[0]; + } + + /** + * Return a submission button that triggers a given server-side-script. + * + * @param {string} script - Name of the server-side script to be triggered + * @param {string} buttonName - Display name of the submission button + */ + this.make_scripting_submission_button = function (script, buttonName) { + let button_name = buttonName || "Submit"; + const scripting_caller = $(` + <form method="POST" action="/scripting"> + <input type="hidden" name="call" value="${script}"/> + <input type="hidden" name="-p0" value=""/> + <div class="form-group"> + <input type="submit" + class="form-control btn btn-primary" value="${button_name}"/> + </div> + </form>`); + + return scripting_caller + } + /** * Return an input and a label, wrapped in a div with class diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl index 264136f09828dce4e2ba7d324ff09fc14db5842c..3881e722608c6013b433a815f2db72a1896cd76e 100644 --- a/src/core/xsl/entity.xsl +++ b/src/core/xsl/entity.xsl @@ -297,6 +297,7 @@ <xsl:param name="value"/> <xsl:param name="reference"/> <xsl:param name="boolean"/> + <xsl:param name="datetime"/> <xsl:choose> <xsl:when test="normalize-space($value)!=''"> <xsl:choose> @@ -322,6 +323,14 @@ <xsl:value-of select="normalize-space($value)"/> </xsl:element> </xsl:when> + <xsl:when test="$datetime='true'"> + <xsl:element name="span"> + <xsl:attribute name="class"> + <xsl:value-of select="'caosdb-f-property-single-raw-value caosdb-property-datetime-value caosdb-f-property-datetime-value caosdb-v-property-datetime-value'"/> + </xsl:attribute> + <xsl:value-of select="$value"/> + </xsl:element> + </xsl:when> <xsl:otherwise> <xsl:element name="span"> <xsl:attribute name="class"> @@ -362,6 +371,9 @@ <xsl:with-param name="boolean"> <xsl:value-of select="'false'"/> </xsl:with-param> + <xsl:with-param name="datetime"> + <xsl:value-of select="'false'"/> + </xsl:with-param> </xsl:call-template> </xsl:for-each> <xsl:for-each select="Record|RecordType|File|Property"> @@ -375,6 +387,9 @@ <xsl:with-param name="boolean"> <xsl:value-of select="'false'"/> </xsl:with-param> + <xsl:with-param name="datetime"> + <xsl:value-of select="'false'"/> + </xsl:with-param> </xsl:call-template> </xsl:for-each> </xsl:element> @@ -398,6 +413,9 @@ <xsl:with-param name="boolean"> <xsl:value-of select="../@datatype='LIST<BOOLEAN>'"/> </xsl:with-param> + <xsl:with-param name="datetime"> + <xsl:value-of select="../@datatype='LIST<DATETIME>'"/> + </xsl:with-param> </xsl:call-template> </xsl:element> </xsl:for-each> @@ -449,6 +467,9 @@ <xsl:with-param name="boolean"> <xsl:value-of select="@datatype='BOOLEAN'"/> </xsl:with-param> + <xsl:with-param name="datetime"> + <xsl:value-of select="@datatype='DATETIME'"/> + </xsl:with-param> </xsl:call-template> </xsl:otherwise> </xsl:choose> diff --git a/src/doc/extension/module.md b/src/doc/extension/module.md index 207d3fff9d013cda657ef8c01f3cca5988622f4a..36731ee72d7a1e4be2f4624961486152579a0ff5 100644 --- a/src/doc/extension/module.md +++ b/src/doc/extension/module.md @@ -1,16 +1,15 @@ # How to add a module to CaosDB WebUI -The CaosDB WebUI is organized in modules which can easily be added and on a module basis enabled or disabled. +The CaosDB WebUI is organized in modules which can easily be added and enabled or disabled per module. -There are a few steps necessary to create a new module. +Only a few steps are necessary to create a new module. ## Create the module file -Create a new file for each new module. We have the convention, that extensions -which are optional and should stay that way and also custom extensions for -special purposes to name the file starting with `ext_`. E.g. -`ext_flight_preview.js`. +Create a new file for each new module. We have the convention that extensions which are optional +(and also custom extensions for special purposes) are saved in files starting with `ext_`, +e.g. `ext_flight_preview.js`. -This file should define one function that wraps every thing and which is +This file should define one function that wraps everything, this function is then enabled at the bottom of the file: ```js diff --git a/test/core/js/modules/ext_cosmetics.js.js b/test/core/js/modules/ext_cosmetics.js.js index 969c8297b8b5cf85d0a668d7c30e8b0f45e34d4d..f51226ee01a1607c9ba1580c3760500fdec760b1 100644 --- a/test/core/js/modules/ext_cosmetics.js.js +++ b/test/core/js/modules/ext_cosmetics.js.js @@ -1,8 +1,9 @@ /* * This file is a part of the CaosDB Project. * - * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021-2023 IndiScale GmbH <info@indiscale.com> * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2023 Daniel Hornung <d.hornung@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -36,6 +37,30 @@ QUnit.module("ext_cosmetics.js", { } }); +QUnit.test("custom datetime", function (assert) { + assert.ok(cosmetics.custom_datetime, "custom_datetime available"); + var test_cases = [ + ["1234-56-78", "1234-56-78"], + ["9876-54-32T12:34:56", "9876-54-32 12:34:56"], + ["2023-03-78T99:99:99+0800", "2023-03-78 99:99:99"], + ]; + + for (let test_case of test_cases) { + const container = $('<div></div>'); + $(document.body).append(container); + 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 = + 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."); + container.remove(); + } +}); + QUnit.test("linkify - https", function (assert) { assert.ok(cosmetics.linkify, "linkify available"); var test_cases = [