diff --git a/api/v1/openapi.yaml b/api/v1/openapi.yaml index bb7aa0ca5..c435c06cc 100644 --- a/api/v1/openapi.yaml +++ b/api/v1/openapi.yaml @@ -47,6 +47,12 @@ paths: $ref: "./openapi/paths/application-newID.yaml" /facets/{facet}: $ref: "./openapi/paths/facets.yaml" + /timeline: + $ref: "./openapi/paths/timeline.yaml" + /timeline/{docType}: + $ref: "./openapi/paths/timeline-docType.yaml" + /timeline/{docType}/{docID}: + $ref: "./openapi/paths/timeline-docType-docID.yaml" components: schemas: Document: diff --git a/api/v1/openapi/paths/timeline-docType-docID.yaml b/api/v1/openapi/paths/timeline-docType-docID.yaml new file mode 100644 index 000000000..0556c0fa6 --- /dev/null +++ b/api/v1/openapi/paths/timeline-docType-docID.yaml @@ -0,0 +1,40 @@ +# /timeline/docType/docID path +get: + tags: + - Facets + summary: Returns timeline for a specific document type + description: | + This endpoint returns a timeline object according to + https://cdn.tei-publisher.com/@2.23.2/dist/api.html#pb-timeline.0 + for a specific WeGA document type related to the given document identifier + (i.e. the works, letters, or writings associated with the given person or organization). + parameters: + - name: docType + in: path + description: The WeGA document type + required: true + style: form + explode: false + schema: + $ref: '../schemas/docTypes.yaml' + - name: docID + in: path + description: The document identifier to search for + required: true + schema: + type: string + default: A002068 + responses: + 200: + description: A timeline object according to https://cdn.tei-publisher.com/@2.23.2/dist/api.html#pb-timeline.0 + headers: + totalrecordcount: + description: The total size of the result set + schema: + type: integer + content: + application/json: + schema: + type: object + default: + $ref: '../responses/unexpectedError.yaml' diff --git a/api/v1/openapi/paths/timeline-docType.yaml b/api/v1/openapi/paths/timeline-docType.yaml new file mode 100644 index 000000000..ace277b8b --- /dev/null +++ b/api/v1/openapi/paths/timeline-docType.yaml @@ -0,0 +1,31 @@ +# /timeline/docType path +get: + tags: + - Facets + summary: Returns timeline for a specific document type + description: | + This endpoint returns a timeline object according to + https://cdn.tei-publisher.com/@2.23.2/dist/api.html#pb-timeline.0 for a specific WeGA document type + parameters: + - name: docType + in: path + description: The WeGA document type + required: true + style: form + explode: false + schema: + $ref: '../schemas/docTypes.yaml' + responses: + 200: + description: A timeline object according to https://cdn.tei-publisher.com/@2.23.2/dist/api.html#pb-timeline.0 + headers: + totalrecordcount: + description: The total size of the result set + schema: + type: integer + content: + application/json: + schema: + type: object + default: + $ref: '../responses/unexpectedError.yaml' diff --git a/api/v1/openapi/paths/timeline.yaml b/api/v1/openapi/paths/timeline.yaml new file mode 100644 index 000000000..2f45ea685 --- /dev/null +++ b/api/v1/openapi/paths/timeline.yaml @@ -0,0 +1,22 @@ +# /timeline path +get: + tags: + - Facets + summary: Returns timeline + description: | + This endpoint returns a timeline object according to + https://cdn.tei-publisher.com/@2.23.2/dist/api.html#pb-timeline.0 + responses: + 200: + description: A timeline object according to https://cdn.tei-publisher.com/@2.23.2/dist/api.html#pb-timeline.0 + headers: + totalrecordcount: + description: The total size of the result set + schema: + type: integer + content: + application/json: + schema: + type: object + default: + $ref: '../responses/unexpectedError.yaml' diff --git a/modules/api.xqm b/modules/api.xqm index f3b8b4dfc..fd406b3b6 100644 --- a/modules/api.xqm +++ b/modules/api.xqm @@ -290,6 +290,38 @@ declare function api:facets($model as map(*)) as map(*) { ) }; +(:~ + : Return a timeline object for driving the TEIPublisher pb-timeline component + : see https://cdn.tei-publisher.com/@2.23.2/dist/api.html#pb-timeline.0 + :) +declare function api:timeline($model as map(*)) as map(*) { + let $docID := + if($model?docID) + then $model?docID + else 'indices' + let $documents := + for $docType in api:resolve-docTypes($model) + return search:results(, map { 'docID' : $docID }, $docType)?search-results + (:if(empty(($model?start, $model?end))) + then core:getOrCreateColl($docType, $docID, true()) + else wdt:lookup($docType, core:getOrCreateColl($docType, $docID, true()))?filter-by-date($model?start, $model?end) :) + let $dates := + map:merge( + for $doc in $documents + group by $date := query:get-normalized-date($doc) + let $key := + if(exists($date)) then $date + else '?' + return + map:entry($key, count($doc)) + ) + return + map { + 'totalRecordCount': count(map:keys($dates)), + 'results': $dates + } +}; + (:~ : Search WeGA entities (persons, places, works) by name or title respectively :) @@ -764,6 +796,24 @@ declare function api:validate-toDate($model as map(*)) as map(*)? { else error($api:INVALID_PARAMETER, 'Unsupported date format given: "' || $model('toDate') || '". Should be YYYY-MM-DD.') }; +(:~ + : Check parameter start +~:) +declare function api:validate-start($model as map(*)) as map(*)? { + if($model('start') castable as xs:date) then (map:put($model, 'fromDate', $model('start')) => map:remove('start')) + else if($model?start ='') then () (: an empty string is simply dropped :) + else error($api:INVALID_PARAMETER, 'Unsupported date format given: "' || $model('start') || '". Should be YYYY-MM-DD.') +}; + +(:~ + : Check parameter end +~:) +declare function api:validate-end($model as map(*)) as map(*)? { + if($model('end') castable as xs:date) then (map:put($model, 'toDate', $model('end')) => map:remove('end')) + else if($model?end ='') then () (: an empty string is simply dropped :) + else error($api:INVALID_PARAMETER, 'Unsupported date format given: "' || $model('end') || '". Should be YYYY-MM-DD.') +}; + (:~ : Check parameter date ~:) diff --git a/modules/app.xqm b/modules/app.xqm index 5833eba5f..17a493ac5 100644 --- a/modules/app.xqm +++ b/modules/app.xqm @@ -493,22 +493,6 @@ declare } }; -(:~ - : set the maximum dates for the IonRangeSlider -~:) -declare - %templates:default("fromDate", "") - %templates:default("toDate", "") - function app:set-slider-range($node as node(), $model as map(*), $fromDate as xs:string, $toDate as xs:string) as element(xhtml:input) { - element {node-name($node)} { - $node/@*, - attribute data-min-slider {if($model('oldFromDate') castable as xs:date) then $model('oldFromDate') else $model('earliestDate')}, - attribute data-max-slider {if($model('oldToDate') castable as xs:date) then $model('oldToDate') else $model('latestDate')}, - attribute data-from-slider {if($fromDate castable as xs:date) then $fromDate else $model('earliestDate')}, - attribute data-to-slider {if($toDate castable as xs:date) then $toDate else $model('latestDate')} - } -}; - declare function app:set-facet-checkbox($node as node(), $model as map(*), $key as xs:string) as element(xhtml:input) { element {node-name($node)} { $node/@*, @@ -2110,3 +2094,33 @@ declare function app:init-custom-switch($node as node(), $model as map(*)) as el $node/* } }; + +(:~ + : Construct the pb-timeline custom element, + : i.e. update the `@start-date` and `@end-date` attributes + : as well as the `@url` attribute with the API URL. + :) +declare function app:pb-timeline($node as node(), $model as map(*)) as element(xhtml:pb-timeline) { + let $api-base := config:api-base($model?openapi) + let $docType := + if(count($model?docType) eq 1 and $model?docType = $search:wega-docTypes) + then $model?docType + else () + let $docID := + if($model?docID = 'indices') + then () + else $model?docID + let $url := $api-base || str:join-path-elements(('/timeline', $docType, $docID)) + return + element {node-name($node)} { + $node/@* except $node/@start-date except $node/@end-date except $node/@url, + attribute url {$url}, + if($model?filters?fromDate castable as xs:date) + then attribute start-date {$model?filters?fromDate} + else (), + if($model?filters?toDate castable as xs:date) + then attribute end-date {$model?filters?toDate} + else (), + $node/* + } +}; diff --git a/modules/search.xqm b/modules/search.xqm index 9bba98613..2802b41c0 100644 --- a/modules/search.xqm +++ b/modules/search.xqm @@ -44,7 +44,9 @@ declare variable $search:valid-params := ( 'undated', 'orderby', 'orderdir', - 'orgs' + 'orgs', + 'start', + 'end' ); (:~ diff --git a/package.json b/package.json index 9bae22cfa..9334da040 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "flip": "nnattawat/flip", "fullcalendar": "^6.1.19", "highlight.js": "^11.11.1", - "ion-rangeslider": "^2", "jquery": "^3.5.0", "jquery-ui": "^1.14.2", "jquery-ui-themes": "^1.12.0", @@ -58,7 +57,6 @@ "fittextjs", "flip/dist", "fullcalendar", - "ion-rangeslider/{css,img,js}", "jquery/dist/", "jquery-ui", "jquery-ui-themes", diff --git a/resources/js/init.js b/resources/js/init.js index c03ee0ccd..6dc50ed71 100644 --- a/resources/js/init.js +++ b/resources/js/init.js @@ -139,47 +139,6 @@ function formatFacet (facet) { return facet; } -$.fn.rangeSlider = function () -{ - this.ionRangeSlider({ - min: +moment($(this).attr('data-min-slider')), - max: +moment($(this).attr('data-max-slider')), - from: +moment($(this).attr('data-from-slider')), - to: +moment($(this).attr('data-to-slider')), - grid: true, - skin: "flat", - step: 100, - force_edges: true, - type: "double", - //force_edges: true, - grid_num: 3, - keyboard: true, - prettify: function (num) { - const lang = getLanguage(), - m = moment(num).locale(lang); - let format; - if(lang === 'de') { format = "D. MMM YYYY"} - else { format = "MMM D, YYYY" } - return m.format(format); - }, - onFinish: function (data) { - /* Get active facets to append as URL params */ - const params = active_facets(), - newFrom = moment(data.from).locale("de").format("YYYY-MM-DD"), - newTo = moment(data.to).locale("de").format("YYYY-MM-DD"); - - /* - * Overwrite date params with new values from the slider - */ - params.sliderDates.fromDate = newFrom; - params.sliderDates.toDate = newTo; - params.sliderDates.oldFromDate = moment(data.min).locale("de").format("YYYY-MM-DD"); - params.sliderDates.oldToDate = moment(data.max).locale("de").format("YYYY-MM-DD"); - updatePage(params); - } - }); -}; - $.fn.obfuscateEMail = function () { if($(this).length === 0) {} else { @@ -715,22 +674,6 @@ function active_facets() { if(params.facets[facet] === undefined) { params.facets[facet] = [] } params.facets[facet].push(value); }) - /* Get date values from range slider */ - if($('.rangeSlider:visible').length) { - slider = $('.rangeSlider:visible'); - from=slider.attr('data-from-slider'); - to=slider.attr('data-to-slider'); - min=slider.attr('data-min-slider'); - max=slider.attr('data-max-slider'); - if(from > min) { - params.sliderDates.fromDate = from; - params.sliderDates.oldFromDate = min; - } - if(to < max) { - params.sliderDates.toDate = to; - params.sliderDates.oldToDate = max; - } - } /* get values from checkboxes for docTypes at search page * as well as for other checkboxes on list pages like 'revealed' or 'undated' */ @@ -781,9 +724,6 @@ $('.allFilter select').facets(); /* Initialise select2 plugin for dropdown on start page */ $('.prettyselect').prettyselect(); -/* Initialise range slider for index pages */ -$('.allFilter:visible .rangeSlider').rangeSlider(); - $('h1').h1FitText(); @@ -839,7 +779,6 @@ function ajaxCall(container,url,callback) { else { /* update facets */ $('.allFilter:visible select').facets(); - $('.allFilter:visible .rangeSlider').rangeSlider(); /* Listen for click events on pagination */ $('.page-link:visible').on('click', function() { @@ -1313,7 +1252,8 @@ function init_fullcalendar(initialDate, lang) { }; -const backToTopBtn = document.getElementById('backToTop'); +const backToTopBtn = document.getElementById('backToTop'), + pbTimelineElem = document.getElementById("pb-timeline"); if (backToTopBtn) { window.addEventListener('scroll', function () { @@ -1331,3 +1271,76 @@ if (backToTopBtn) { // Initialize code highlighting document.querySelectorAll('.prettyprint code').forEach(el => {hljs.highlightElement(el)}) + +function facetsToString(facets) { + const params = new URLSearchParams( + Object.entries(facets).flatMap(([key, value]) => + Array.isArray(value) ? value.map(v => [key, v]) : [[key, value]] + ) + ).toString(); + return params +} + +function pbTimelineChangeHandler(ev) { + //console.log(ev); + let params = active_facets(), + endDateFixed; + // fix endDate which is always provided as the first day of the last period by pb-timeline + if (ev.detail.scope) { + if (ev.detail.scope.includes('Y')) { + endDateFixed = moment(ev.detail.endDateStr).endOf("year").format("YYYY-MM-DD") + } else if (ev.detail.scope.includes('M')) { + endDateFixed = moment(ev.detail.endDateStr).endOf("month").format("YYYY-MM-DD") + } else if (ev.detail.scope.includes('W')) { + endDateFixed = moment(ev.detail.endDateStr).endOf("week").format("YYYY-MM-DD") + } else { + endDateFixed = ev.detail.endDateStr + } + params.sliderDates.fromDate = ev.detail.startDateStr; + params.sliderDates.toDate = endDateFixed; + } + else { + params.facets.undated = true; + } + updatePage(params); +} + +function pbTimelineResetHandler(ev) { + const undatedCheckbox = document.getElementById('undated'); + if(undatedCheckbox) { + undatedCheckbox.checked = false; // uncheck hidden checkbox + } + const params = active_facets(); + params.sliderDates.fromDate = ''; // set to empty string + params.sliderDates.toDate = ''; + updatePage(params); +} + +function pbTimelinePresendHandler(ev) { + const facets = active_facets().facets; + delete facets.limit; // limit is not needed and not supported by the `/timline` endpoint + const params = facetsToString(facets) + if (params !== "") { + ev.detail.options.url = ev.detail.options.url + "&" + params; + } +} + +if(pbTimelineElem) { + /* + * Add event listener for changes to the timeline, i.e. selecting a date range. + */ + pbTimelineElem.addEventListener('pb-timeline-daterange-changed', pbTimelineChangeHandler) + + pbTimelineElem.addEventListener('pb-timeline-date-changed', pbTimelineChangeHandler) + + /* + * Add event listener for resetting the timeline selection, i.e. hitting the big X on the timeline. + */ + pbTimelineElem.addEventListener('pb-timeline-reset-selection', pbTimelineResetHandler) + + /* + * Add event listener for intercepting AJAX requests sent by the timeline web component. + * Here, we rewrite the default URL parameters (start and end) and add additional URL parameters for facets + */ + pbTimelineElem.addEventListener('iron-ajax-presend', pbTimelinePresendHandler) +} diff --git a/resources/sass/components/_ion-rangeslider.scss b/resources/sass/components/_ion-rangeslider.scss deleted file mode 100644 index 12c5fa0fb..000000000 --- a/resources/sass/components/_ion-rangeslider.scss +++ /dev/null @@ -1,17 +0,0 @@ -/* - * overrides for ion-rangeslider - */ - -.irs--flat .irs-from, .irs--flat .irs-to, .irs--flat .irs-single, .irs--flat .irs-bar { - background-color: $primary !important; -} -.irs--flat .irs-from::before, .irs--flat .irs-to::before, .irs--flat .irs-single::before { - border-top-color: $primary !important; -} -.irs--flat .irs-handle > i:first-child { - background-color: $primary !important; -} - -span.irs span { - border-radius: 0!important; -} \ No newline at end of file diff --git a/resources/sass/main.scss b/resources/sass/main.scss index 157f700b7..87a0442bf 100644 --- a/resources/sass/main.scss +++ b/resources/sass/main.scss @@ -70,7 +70,6 @@ @import "components/greedynav"; // deactivated csLink, see https://github.com/Edirom/WeGA-WebApp/issues/453 //@import "components/csLink"; -@import "components/ion-rangeslider"; @import "components/tabs"; @import "components/popover"; @import "components/breadcrumbs"; diff --git a/templates/ajax/biblio.html b/templates/ajax/biblio.html index 93bc9607f..4951bdd80 100644 --- a/templates/ajax/biblio.html +++ b/templates/ajax/biblio.html @@ -7,12 +7,6 @@

-

chronology

- -
- - -

authors

- - -
+
diff --git a/templates/ajax/diaries.html b/templates/ajax/diaries.html index f08627a72..f7fc24485 100644 --- a/templates/ajax/diaries.html +++ b/templates/ajax/diaries.html @@ -7,8 +7,6 @@

filter

-

chronology

-

persons_mentioned

+

authors

-

persons_mentioned

- +

sources-meta-cat

diff --git a/templates/ajax/writings.html b/templates/ajax/writings.html index cc95d98e4..2890dd4eb 100644 --- a/templates/ajax/writings.html +++ b/templates/ajax/writings.html @@ -3,13 +3,7 @@

filter

-

chronology

- - -
- - -
+

authors