Merge branch 'release/0.21.7'

This commit is contained in:
vnmedeiros 2024-07-01 15:47:28 -03:00
commit 28f0ec5a5f
99 changed files with 3739 additions and 574 deletions

View File

@ -55,6 +55,24 @@
flex-wrap: nowrap;
flex-direction: row;
flex: 1 0 auto; }
.wp-block-tainacan-faceted-search .items-list-placeholder .below-search-control.horizontal-filters {
flex-direction: column; }
.wp-block-tainacan-faceted-search .items-list-placeholder .below-search-control.horizontal-filters .filters {
flex-direction: row;
flex: 0 1 100%;
flex-wrap: wrap;
padding: 18px 12px 6px 12px; }
.wp-block-tainacan-faceted-search .items-list-placeholder .below-search-control.horizontal-filters .filters .fake-filters-heading {
margin-right: 90%;
top: -0.5em;
left: 0.5em; }
.wp-block-tainacan-faceted-search .items-list-placeholder .below-search-control.horizontal-filters .filters .fake-filters-heading + .fake-link {
margin-right: 95%;
margin-left: 12px; }
.wp-block-tainacan-faceted-search .items-list-placeholder .below-search-control.horizontal-filters .filters .fake-filter {
margin: 6px 12px;
display: inline-flex;
width: var(--tainacan-filters-inline-width, 272px); }
.wp-block-tainacan-faceted-search .items-list-placeholder .below-search-control .filters {
flex: 0 1 var(--tainacan-filter-menu-width-theme, 20%);
display: flex;
@ -64,7 +82,7 @@
display: flex;
flex-direction: column;
width: 80%;
margin: 5% 12%; }
margin: 5% 8%; }
.wp-block-tainacan-faceted-search .items-list-placeholder .below-search-control .filters .fake-filter .fake-text {
margin: 4px 0;
width: 35%;

File diff suppressed because one or more lines are too long

View File

@ -454,7 +454,7 @@ class REST_Items_Controller extends REST_Controller {
* @return array
* @throws \Exception
*/
private function prepare_filters_arguments ( $args, $collection_id = false ) {
private function prepare_filters_arguments ( $args, $collection_id = false, $ignore_filter_arguments = [] ) {
$filters_arguments = array();
$meta_query = isset($args['meta_query']) ? $args['meta_query'] : [];
if(isset($meta_query['value'])) $meta_query = [$meta_query];
@ -498,8 +498,7 @@ class REST_Items_Controller extends REST_Controller {
}
foreach($meta_query as $meta) {
if ( !isset($meta['key']) || !isset($meta['value']) )
if ( !isset($meta['key']) || !isset($meta['value']) || ( in_array($meta['key'], $ignore_filter_arguments) ))
continue;
$meta_id = $meta['key'];
@ -554,6 +553,7 @@ class REST_Items_Controller extends REST_Controller {
$date_format = get_option( 'date_format' ) != false ? get_option( 'date_format' ) : 'Y-m-d';
return empty($date) == false ? mysql2date($date_format, $date) : "";
}, $meta_label);
$meta_type = 'DATE';
break;
case 'item':
$meta_label = array_map(function($item_id) {
@ -643,7 +643,15 @@ class REST_Items_Controller extends REST_Controller {
if($request['collection_id']) {
$collection_id = $request['collection_id'];
}
$filters_args = $this->prepare_filters_arguments($args, $collection_id);
$metaqueries = isset($request['metaquery']) ? $request['metaquery'] : [];
$ignore_filter_arguments = array_map(
function($metaquery) { return $metaquery['key']; },
array_filter(
$metaqueries,
function($metaquery) { return isset($metaquery['key']) && isset($metaquery['secondary']) && $metaquery['secondary'] == 'true'; }
)
);
$filters_args = $this->prepare_filters_arguments($args, $collection_id, $ignore_filter_arguments);
if(isset($args['meta_query']) && !empty($args['meta_query']) && is_array($filters_args) && !empty($filters_args)) {
foreach($filters_args as $filters_arg) {
if($filters_arg['filter'] !== false) {

View File

@ -20,7 +20,8 @@ class Filter extends Entity {
$filter_type,
$filter_type_options,
$begin_with_filter_collapsed,
$display_in_repository_level_lists;
$display_in_repository_level_lists,
$description_bellow_name;
static $post_type = 'tainacan-filter';
public $enabled_for_collection = true;
@ -72,10 +73,17 @@ class Filter extends Entity {
/**
* @return mixed|null
*/
function get_description(){
function get_description() {
return $this->get_mapped_property('description');
}
/**
* @return mixed|null
*/
function get_placeholder() {
return $this->get_mapped_property('placeholder');
}
/**
* Return the filter order type
*
@ -189,6 +197,14 @@ class Filter extends Entity {
return $this->get_mapped_property('display_in_repository_level_lists');
}
/**
* Return the filter description_bellow_name
*
* @return string
*/
function get_description_bellow_name() {
return $this->get_mapped_property('description_bellow_name');
}
/**
* Define the filter name
@ -220,6 +236,16 @@ class Filter extends Entity {
$this->set_mapped_property('description', $value);
}
/**
* Define the filter placeholder
*
* @param [string] $value
* @return void
*/
function set_placeholder($value) {
$this->set_mapped_property('placeholder', $value);
}
/**
* Define the filter metadatum passing an object
*
@ -284,6 +310,16 @@ class Filter extends Entity {
$this->enabled_for_collection = $value;
}
/**
* Set filter description_bellow_name
*
* @param [string] $value If the description will be displayed below the name instead of inside a tooltip (yes/no)
* @return void
*/
function set_description_bellow_name($value) {
$this->set_mapped_property('description_bellow_name', $value);
}
/**
* {@inheritdoc }

View File

@ -697,6 +697,7 @@ class CSV extends Importer {
remove_action( 'post_updated', 'wp_save_post_revision' );
$collections = $this->get_collections();
$collection_definition = isset($collections[$collection_index]) ? $collections[$collection_index] : false;
if ( !$collection_definition || !is_array($collection_definition) || !isset($collection_definition['id']) || !isset($collection_definition['mapping']) ) {
$this->add_error_log('Collection misconfigured');
return false;
@ -712,7 +713,7 @@ class CSV extends Importer {
$Tainacan_Metadata = \Tainacan\Repositories\Metadata::get_instance();
$Tainacan_Item_Metadata = \Tainacan\Repositories\Item_Metadata::get_instance();
$Tainacan_Items = \Tainacan\Repositories\Items::get_instance();
$special_columns = false;
$itemMetadataArray = [];
@ -731,7 +732,7 @@ class CSV extends Importer {
$item = new Entities\Item();
}
if( is_numeric($this->get_transient('item_id')) ) {
if ( is_numeric($this->get_transient('item_id')) ) {
if ( $item instanceof Entities\Item && $item->get_id() == $this->get_transient('item_id') ) {
if ( ! $item->can_edit() ) {
$this->add_error_log("You don't have permission to edit item:" . $item->get_id() );
@ -748,12 +749,12 @@ class CSV extends Importer {
}
if( $this->get_transient('item_id') && $item instanceof Entities\Item && is_numeric($item->get_id()) && $item->get_id() > 0 && $this->get_transient('item_action') == 'ignore' ){
if ( $this->get_transient('item_id') && $item instanceof Entities\Item && is_numeric($item->get_id()) && $item->get_id() > 0 && $this->get_transient('item_action') == 'ignore' ){
$this->add_log('Ignoring repeated Item');
return $item;
}
if( is_array( $processed_item ) ) {
if ( is_array( $processed_item ) ) {
foreach ( $processed_item as $metadatum_source => $values ) {
if ($metadatum_source == 'special_document' ||
@ -820,7 +821,17 @@ class CSV extends Importer {
}
}
if( (!empty( $itemMetadataArray ) || $special_columns) && $collection instanceof Entities\Collection ) {
if ( !( $collection instanceof Entities\Collection ) ) {
$this->add_error_log( 'Collection not set');
return false;
}
if ( ( empty( $itemMetadataArray ) && !$special_columns ) ) {
$this->add_log( 'Found one empty value' );
return false;
}
$item->set_collection( $collection );
if ( !$updating_item ) {
if( $item->validate() ) {
@ -833,8 +844,10 @@ class CSV extends Importer {
} else {
$insertedItem = $item;
}
global $wpdb;
$wpdb->query( 'SET autocommit = 0;' );
foreach ( $itemMetadataArray as $itemMetadata ) {
if($itemMetadata instanceof Entities\Item_Metadata_Entity ) {
$itemMetadata->set_item( $insertedItem ); // *I told you
@ -881,7 +894,7 @@ class CSV extends Importer {
$insertedItem->set_status('publish' );
}
if($insertedItem->validate()) {
if ( $insertedItem->validate() ) {
$insertedItem = $Tainacan_Items->update( $insertedItem );
$this->after_inserted_item( $insertedItem, $collection_index );
} else {
@ -891,10 +904,6 @@ class CSV extends Importer {
return false;
}
return $insertedItem;
} else {
$this->add_error_log( 'Collection not set');
return false;
}
}
private function is_assoc(array $arr) {

View File

@ -132,7 +132,7 @@ class Collections extends Repository {
'title' => __( 'Enabled view modes', 'tainacan' ),
'type' => 'array',
'description' => __( 'Which visualization modes will be available for the public to choose from', 'tainacan' ),
'default' => [ 'table', 'cards' ],
'default' => [ 'table', 'cards', 'masonry' ],
'items' => [ 'type' => 'string' ],
//'validation' => v::stringType(),
],

View File

@ -110,7 +110,24 @@ class Filters extends Repository {
'description' => __( 'The max number of options to be showed in filter sidebar.', 'tainacan' ),
'validation' => '',
'default' => 4
]
],
'placeholder' => [
'map' => 'meta',
'title' => __( 'Placeholder', 'tainacan' ),
'type' => 'string',
'description' => __( 'The filter input placeholder. This is a simple message that will appear inside textual input and may indicate to the user what kind of information is expected.', 'tainacan' ),
'default' => '',
],
'description_bellow_name' => [
'map' => 'meta',
'title' => __( 'Description below name', 'tainacan' ),
'type' => 'string',
'description' => __( 'Whether the filter description should be displayed below the input label instead of inside a tooltip.', 'tainacan' ),
'on_error' => __( 'Please set the "Description below name" value as "yes" or "no"', 'tainacan' ),
'validation' => v::stringType()->in( [ 'yes', 'no' ] ), // yes or no
'enum' => [ 'yes', 'no' ],
'default' => 'no'
],
] );
}

View File

@ -37,7 +37,7 @@ class Theme_Helper {
// Replace collections permalink to post type archive if cover not enabled
add_filter('post_type_link', array($this, 'permalink_filter'), 10, 3);
// Replace single query to the page content set as cover for the colllection
// Replace single query to the page content set as cover for the collection
// Redirect to post type archive if no cover page is set
add_action('wp', array($this, 'collection_single_redirect'));
@ -512,6 +512,9 @@ class Theme_Helper {
* @type string $default_view_mode The default view mode
* @type bool $is_forced_view_mode Ignores user prefs to always render the choosen default view mode
* @type string[] $enabled_view_modes The list os enable view modes to display
* @type bool $should_not_hide_filters_on_mobile Disables the default behavior of automatically collapsing the filters inside a modal when in small screen sizes
* @type bool $display_filters_horizontally Display the filters in an horizontal panel above search control instead of a sidebar
* @type bool $hide_filter_collapses Hides the button that collapses all filters inside the filters panel
* @return string The HTML div to be used for rendering the items list vue component
*/
public function search_shortcode($args) {
@ -617,7 +620,10 @@ class Theme_Helper {
'data-start-with-filters-hidden' => true,
'data-filters-as-modal' => true,
'data-show-inline-view-mode-options' => true,
'data-show-fullscreen-with-view-modes' => true
'data-show-fullscreen-with-view-modes' => true,
'data-should-not-hide-filters-on-mobile' => true,
'data-display-filters-horizontally' => true,
'data-hide-filter-collapses' => true
]
];
@ -2479,5 +2485,23 @@ class Theme_Helper {
'requires_thumbnail' => false,
'placeholder_template' => $map_view_mode_placeholder
]);
$this->register_view_mode('mosaic', [
'label' => __('Mosaic', 'tainacan'),
'dynamic_metadata' => false,
'description' => __('A mosaic view, similar to Flickr and Google Photos, which will display images without cropping.', 'tainacan'),
'icon' => '<span class="icon"><i class="tainacan-icon tainacan-icon-viewmasonry tainacan-icon-rotate-90 tainacan-icon-1-25em"></i></span>',
'type' => 'component',
'implements_skeleton' => true,
'placeholder_template' => '<ul style="list-style: none;width: 100%; height: auto; display: flex; gap: 24px 0; flex-wrap: wrap;">' .
array_reduce( range(0,11), function($container, $i) {
$container .= '<li style="flex-grow: 1; max-width: 35%; width: ' . ($i % 2 == 0 ? rand(100, 180) : rand(90, 170)) . 'px; height: 120px ; background-color: var(--tainacan-block-gray1, #f2f2f2); margin: 0; padding: 5px;">
<div style="width: 100%;height: 100%; background-color: var(--tainacan-block-gray2, #dbdbdb);margin-bottom: 10px;"></div>
<div style="width: 100%;height: 10px; background-color: var(--tainacan-block-gray3, #a5a5a5);"></div>
</li>';
return $container;
}) .
'</ul>'
]);
}
}

View File

@ -4,7 +4,7 @@ Tags: museums, archives, GLAM, collections, repository
Requires at least: 5.9
Tested up to: 6.5
Requires PHP: 7.0
Stable tag: 0.21.6
Stable tag: 0.21.7
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-3.0.html

View File

@ -5,17 +5,17 @@ Plugin URI: https://tainacan.org/
Description: Open source, powerful and flexible repository platform for WordPress. Manage and publish you digital collections as easily as publishing a post to your blog, while having all the tools of a professional repository platform.
Author: Tainacan.org
Author URI: https://tainacan.org/
Version: 0.21.6
Version: 0.21.7
Requires at least: 5.9
Tested up to: 6.5
Requires PHP: 7.0
Stable tag: 0.21.6
Stable tag: 0.21.7
Text Domain: tainacan
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-3.0.html
*/
const TAINACAN_VERSION = '0.21.6';
const TAINACAN_VERSION = '0.21.7';
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
$TAINACAN_BASE_URL = plugins_url('', __FILE__);

View File

@ -5,6 +5,7 @@
:class="{
'tainacan-admin-mobile-app-mode': $adminOptions.mobileAppMode
}">
<template v-if="hasPermalinksStructure">
<template v-if="activeRoute == 'HomePage'">
<tainacan-header v-if="!$adminOptions.hideTainacanHeader" />
<router-view />
@ -46,6 +47,7 @@
<router-view />
</div>
</template>
</template>
</div>
</template>
@ -70,7 +72,8 @@
return {
isMenuCompressed: false,
isRepositoryLevel : true,
activeRoute: '/collections'
activeRoute: '/collections',
hasPermalinksStructure: false
}
},
computed: {
@ -105,6 +108,10 @@
}
},
created() {
this.hasPermalinksStructure = tainacan_plugin.has_permalinks_structure;
if ( this.hasPermalinksStructure ) {
this.$statusHelper.loadStatuses();
this.$userPrefs.init();
this.isMenuCompressed = (this.$route.params.collectionId != undefined);
@ -114,10 +121,31 @@
if (jQuery && jQuery( document )) {
jQuery( document ).ajaxError(this.onHeartBitError);
}
} else {
this.onPermalinksError();
}
},
methods: {
onPermalinksError() {
this.$buefy.modal.open({
component: CustomDialog,
props: {
title: this.$i18n.get('error_permalinks_label'),
message: this.$i18n.getWithVariables('error_permalinks_detail', [ '<a href="' + tainacan_plugin.admin_url + 'options-permalink.php">', '</a>' ]),
hideCancel: true,
confirmText: this.$i18n.get('label_go_to_permalinks'),
onConfirm: () => {
window.location.href = tainacan_plugin.admin_url + 'options-permalink.php';
}
},
ariaRole: 'alertdialog',
ariaModal: true,
customClass: 'tainacan-modal',
canCancel: false,
});
},
onHeartBitError(event, jqxhr, settings) {
if (settings && settings.url == '/wp-admin/admin-ajax.php') {
if (settings && settings.url == tainacan_plugin.admin_url + 'admin-ajax.php') {
this.$buefy.snackbar.open({
message: this.$i18n.get('error_connectivity'),
type: 'is-danger',

View File

@ -49,6 +49,45 @@
@focus="clearErrors('description')" />
</b-field>
<b-field
:addons="false"
:label="$i18n.getHelperTitle('filters', 'description_bellow_name')"
:type="formErrors['description_bellow_name'] != undefined ? 'is-danger' : ''"
:message="formErrors['description_bellow_name'] != undefined ? formErrors['description_bellow_name'] : ''">
&nbsp;
<b-switch
v-model="form.description_bellow_name"
size="is-small"
true-value="yes"
false-value="no"
:native-value="form.description_bellow_name == 'yes' ? 'yes' : 'no'"
name="description_bellow_name"
@update:model-value="clearErrors('description_bellow_name')">
<help-button
:title="$i18n.getHelperTitle('filters', 'description_bellow_name')"
:message="$i18n.getHelperMessage('filters', 'description_bellow_name')"
:extra-classes="isRepositoryLevel ? 'tainacan-repository-tooltip' : ''" />
</b-switch>
</b-field>
<b-field
v-if="form.filter_type_object.use_input_placeholder"
:addons="false"
:type="formErrors['placeholder'] != undefined ? 'is-danger' : ''"
:message="formErrors['placeholder'] != undefined ? formErrors['placeholder'] : ''">
<label class="label is-inline">
{{ $i18n.getHelperTitle('filters', 'placeholder') }}
<help-button
:title="$i18n.getHelperTitle('filters', 'placeholder')"
:message="$i18n.getHelperMessage('filters', 'placeholder')"
:extra-classes="isRepositoryLevel ? 'tainacan-repository-tooltip' : ''" />
</label>
<b-input
v-model="form.placeholder"
name="placeholder"
@focus="clearErrors('placeholder')" />
</b-field>
<b-field
:addons="false"
:type="formErrors['status'] != undefined ? 'is-danger' : ''"
@ -249,16 +288,22 @@ import { nextTick } from 'vue';
import { mapActions } from 'vuex';
import { formHooks } from "../../js/mixins";
import FormFilterDate from '../filter-types/date/FormDate.vue';
import FormFilterNumeric from '../filter-types/numeric/FormNumeric.vue';
import FormFilterNumericInterval from '../filter-types/numeric-interval/FormNumericInterval.vue';
import FormFilterNumericListInterval from '../filter-types/numeric-list-interval/FormNumericListInterval.vue';
import FormFilterNumericsIntersection from '../filter-types/numerics-intersection/FormNumericsIntersection.vue';
import FormFilterDatesIntersection from '../filter-types/dates-intersection/FormDatesIntersection.vue';
export default {
name: 'FilterEditionForm',
components: {
'tainacan-filter-form-date': FormFilterDate,
'tainacan-filter-form-numeric': FormFilterNumeric,
'tainacan-filter-form-numeric-interval': FormFilterNumericInterval,
'tainacan-filter-form-numeric-list-interval': FormFilterNumericListInterval
'tainacan-filter-form-numeric-list-interval': FormFilterNumericListInterval,
'tainacan-filter-form-numerics-intersection': FormFilterNumericsIntersection,
'tainacan-filter-form-dates-intersection': FormFilterDatesIntersection
},
mixins: [ formHooks ],
props: {
@ -292,6 +337,10 @@ export default {
this.formErrorMessage = this.form.formErrors != undefined ? this.form.formErrorMessage : '';
this.oldForm = JSON.parse(JSON.stringify(this.originalFilter));
if ( this.form.metadatum == undefined && this.oldForm.metadatum != undefined )
this.form.metadatum = this.oldForm.metadatum;
},
mounted() {
// Fills hook forms with it's real values
@ -316,17 +365,21 @@ export default {
'updateFilter'
]),
saveEdition(filter) {
if ((filter.filter_type_object && filter.filter_type_object.form_component) || filter.edit_form == '') {
this.isLoading = true;
if ((filter.filter_type_object && filter.filter_type_object.form_component) || filter.edit_form == '') {
for (let [key, value] of Object.entries(this.form)) {
if (key === 'begin_with_filter_collapsed' || key === 'display_in_repository_level_lists')
if (key === 'begin_with_filter_collapsed' || key === 'display_in_repository_level_lists' || key === 'description_bellow_name' )
this.form[key] = (value == 'yes' || value == true) ? 'yes' : 'no';
}
if (this.form['begin_with_filter_collapsed'] === undefined)
this.form['begin_with_filter_collapsed'] = 'no';
if (this.form['display_in_repository_level_lists'] === undefined)
this.form['display_in_repository_level_lists'] = 'no';
if (this.form['description_bellow_name'] === undefined)
this.form['description_bellow_name'] = 'no';
this.updateFilter({ filterId: filter.id, index: this.index, options: this.form })
.then(() => {
@ -355,7 +408,7 @@ export default {
let formObj = {};
for (let [key, value] of formData.entries()) {
if (key === 'begin_with_filter_collapsed' || key === 'display_in_repository_level_lists')
if (key === 'begin_with_filter_collapsed' || key === 'display_in_repository_level_lists' || key === 'description_bellow_name' )
formObj[key] = (value == 'yes' || value == true) ? 'yes' : 'no';
else
formObj[key] = value;
@ -364,9 +417,10 @@ export default {
formObj['begin_with_filter_collapsed'] = 'no';
if (formObj['display_in_repository_level_lists'] === undefined)
formObj['display_in_repository_level_lists'] = 'no';
if (formObj['description_bellow_name'] === undefined)
formObj['description_bellow_name'] = 'no';
this.fillExtraFormData(formObj);
this.isLoading = true;
this.updateFilter({ filterId: filter.id, index: this.index, options: formObj })
.then(() => {
this.form = {};
@ -418,7 +472,7 @@ export default {
-webkit-column-gap: 0;
-webkit-column-rule: none;
column-count: 2;
column-gap: 4em;
column-gap: 3em;
column-rule: none;
padding-bottom: 0.5em;

View File

@ -110,7 +110,7 @@
v-if="item.document != '' && item.document_type != 'empty'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_document_uploaded'),
@ -128,7 +128,7 @@
<span
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_button_delete_document'),

View File

@ -10,7 +10,7 @@
:loading="isLoadingOptions"
field="label"
clearable
:placeholder="(metadatumType === 'Tainacan\\Metadata_Types\\Relationship') ? $i18n.get('info_type_to_search_items') : $i18n.get('info_type_to_search_metadata')"
:placeholder="filter.placeholder ? filter.placeholder : ( (metadatumType === 'Tainacan\\Metadata_Types\\Relationship') ? $i18n.get('info_type_to_search_items') : $i18n.get('info_type_to_search_metadata') )"
check-infinite-scroll
@update:model-value="($event) => { resetPage(); search($event); }"
@select="onSelect"

View File

@ -19,10 +19,23 @@
@input="resetPage()">
<span class="check" />
<span class="control-label">
<span class="checkbox-label-text">{{ option.label }}</span>
<span
v-tooltip="{
delay: {
show: 800,
hide: 100,
},
content: option.label,
autoHide: true,
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''],
placement: 'auto-start'
}"
class="checkbox-label-text">
{{ option.label }}
</span>
<span
v-if="option.total_items != undefined"
class="has-text-gray">&nbsp;{{ "(" + option.total_items + ")" }}</span>
class="facet-item-count has-text-gray">&nbsp;{{ "(" + option.total_items + ")" }}</span>
</span>
</label>
<button
@ -242,12 +255,26 @@
display: flex;
flex-wrap: nowrap;
width: 100%;
align-items: center;
}
.checkbox-label-text {
white-space: nowrap;
white-space: wrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 1.45em;
break-inside: avoid;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.facet-item-count {
margin-left: auto;
}
.b-checkbox:hover .facet-item-count,
.b-checkbox:focus .facet-item-count {
--tainacan-info-color: var(--tainacan-input-color);
}
</style>

View File

@ -12,6 +12,7 @@ class Checkbox extends Filter_Type {
$this->set_name( __('Checkbox List', 'tainacan') );
$this->set_supported_types(['string','long_string','item', 'control']);
$this->set_component('tainacan-filter-checkbox');
$this->set_use_input_placeholder(false);
$this->set_preview_template('
<div>
<div>

View File

@ -3,7 +3,7 @@
<b-datepicker
v-model="dateInit"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="$i18n.get('label_selectbox_init')"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('instruction_select_a_date')"
editable
:trap-focus="false"
:date-formatter="(date) => dateFormatter(date)"
@ -19,6 +19,20 @@
$i18n.get('datepicker_short_friday'),
$i18n.get('datepicker_short_saturday'),
]"
:month-names="[
$i18n.get('datepicker_month_january'),
$i18n.get('datepicker_month_february'),
$i18n.get('datepicker_month_march'),
$i18n.get('datepicker_month_april'),
$i18n.get('datepicker_month_may'),
$i18n.get('datepicker_month_june'),
$i18n.get('datepicker_month_july'),
$i18n.get('datepicker_month_august'),
$i18n.get('datepicker_month_september'),
$i18n.get('datepicker_month_october'),
$i18n.get('datepicker_month_november'),
$i18n.get('datepicker_month_december')
]"
@focus="isTouched = true"
@update:model-value="($event) => { resetPage(); validadeValues($event) }" />
<p
@ -29,7 +43,7 @@
<b-datepicker
v-model="dateEnd"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="$i18n.get('label_selectbox_init')"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('instruction_select_a_date')"
editable
:trap-focus="false"
:date-formatter="(date) => dateFormatter(date)"
@ -45,6 +59,20 @@
$i18n.get('datepicker_short_friday'),
$i18n.get('datepicker_short_saturday'),
]"
:month-names="[
$i18n.get('datepicker_month_january'),
$i18n.get('datepicker_month_february'),
$i18n.get('datepicker_month_march'),
$i18n.get('datepicker_month_april'),
$i18n.get('datepicker_month_may'),
$i18n.get('datepicker_month_june'),
$i18n.get('datepicker_month_july'),
$i18n.get('datepicker_month_august'),
$i18n.get('datepicker_month_september'),
$i18n.get('datepicker_month_october'),
$i18n.get('datepicker_month_november'),
$i18n.get('datepicker_month_december')
]"
@update:model-value="validadeValues()"
@focus="isTouched = true" />
</div>

View File

@ -0,0 +1,82 @@
<template>
<div>
<b-field :addons="false">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-date', 'comparators') }}<span>&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-date', 'comparators')"
:message="$i18n.getHelperMessage('tainacan-filter-date', 'comparators')" />
</label>
<div>
<b-checkbox
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
v-model="comparators"
:native-value="comparatorKey"
:disabled="comparators.indexOf(comparatorKey) >= 0 && comparators.length <= 1"
name="date_filter_options[comparators]"
@update:model-value="emitValues()">
<span v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-checkbox>
</div>
</b-field>
</div>
</template>
<script>
export default {
props: {
modelValue: Object
},
emits: [
'update:model-value',
],
data() {
return {
comparatorsObject: Object,
comparators: Array
}
},
created() {
this.comparators = ( this.modelValue && this.modelValue.comparators ) ? this.modelValue.comparators : [ '=', '!=', '>', '>=', '<', '<=' ];
this.comparatorsObject = {
'=': {
symbol: '&#61;',
label: this.$i18n.get('is_equal_to'),
enabled: this.comparators.indexOf('=') < 0 ? 'no' : 'yes'
},
'!=': {
symbol: '&#8800;',
label: this.$i18n.get('is_not_equal_to'),
enabled: this.comparators.indexOf('!=') < 0 ? 'no' : 'yes'
},
'>': {
symbol: '&#62;',
label: this.$i18n.get('greater_than'),
enabled: this.comparators.indexOf('>') < 0 ? 'no' : 'yes'
},
'>=': {
symbol: '&#8805;',
label: this.$i18n.get('greater_than_or_equal_to'),
enabled: this.comparators.indexOf('>=') < 0 ? 'no' : 'yes'
},
'<': {
symbol: '&#60;',
label: this.$i18n.get('less_than'),
enabled: this.comparators.indexOf('<') < 0 ? 'no' : 'yes'
},
'<=': {
symbol: '&#8804;',
label: this.$i18n.get('less_than_or_equal_to'),
enabled: this.comparators.indexOf('<=') < 0 ? 'no' : 'yes'
}
};
},
methods: {
emitValues() {
this.$emit('update:model-value', { comparators: this.comparators });
}
}
}
</script>

View File

@ -1,6 +1,7 @@
<template>
<div class="date-filter-container">
<b-dropdown
v-if="filterTypeOptions.comparators.length > 1"
:mobile-modal="true"
aria-role="list"
trap-focus
@ -10,61 +11,30 @@
:aria-label="$i18n.get('label_comparator')"
class="button is-white">
<span class="icon is-small">
<i v-html="comparatorSymbol" />
<i v-html="comparatorsObject[comparator].symbol" />
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-arrowdown" />
</span>
</button>
</template>
<template
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey">
<b-dropdown-item
v-if="comparatorObject.enabled == 'yes'"
role="button"
:class="{ 'is-active': comparator == '=' }"
:value="'='"
aria-role="listitem">
&#61;&nbsp; {{ $i18n.get('is_equal_to') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '!=' }"
:value="'!='"
aria-role="listitem">
&#8800;&nbsp; {{ $i18n.get('is_not_equal_to') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '>' }"
:value="'>'"
aria-role="listitem">
&#62;&nbsp; {{ $i18n.get('after') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '>=' }"
:value="'>='"
aria-role="listitem">
&#8805;&nbsp; {{ $i18n.get('after_or_on_day') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '<' }"
:value="'<'"
aria-role="listitem">
&#60;&nbsp; {{ $i18n.get('before') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '<=' }"
:value="'<='"
aria-role="listitem">
&#8804;&nbsp; {{ $i18n.get('before_or_on_day') }}
</b-dropdown-item>
:class="{ 'is-active': comparator == comparatorKey }"
:value="comparatorKey"
aria-role="listitem"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</template>
</b-dropdown>
<b-datepicker
v-model="value"
position="is-bottom-right"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="$i18n.get('instruction_select_a_date')"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('instruction_select_a_date')"
editable
:trap-focus="false"
:date-formatter="(date) => dateFormatter(date)"
@ -111,26 +81,16 @@
emits: [
'input',
],
data(){
data() {
return {
value: null,
comparatorsObject: [],
comparator: '=', // =, !=, >, >=, <, <=
}
},
computed: {
yearsOnlyValue() {
return this.value && typeof this.value.getUTCFullYear === 'function' ? this.value.getUTCFullYear() : null
},
comparatorSymbol() {
switch(this.comparator) {
case '=': return '&#61;';
case '!=': return '&#8800;';
case '>': return '&#62;';
case '>=': return '&#8805;';
case '<': return '&#60;';
case '<=': return '&#8804;';
default: return '';
}
}
},
watch: {
@ -141,6 +101,41 @@
deep: true,
},
},
created() {
this.comparatorsObject = {
'=': {
symbol: '&#61;',
label: this.$i18n.get('is_equal_to'),
enabled: this.filterTypeOptions.comparators.indexOf('=') < 0 ? 'no' : 'yes'
},
'!=': {
symbol: '&#8800;',
label: this.$i18n.get('is_not_equal_to'),
enabled: this.filterTypeOptions.comparators.indexOf('!=') < 0 ? 'no' : 'yes'
},
'>': {
symbol: '&#62;',
label: this.$i18n.get('after'),
enabled: this.filterTypeOptions.comparators.indexOf('>') < 0 ? 'no' : 'yes'
},
'>=': {
symbol: '&#8805;',
label: this.$i18n.get('after_or_on_day'),
enabled: this.filterTypeOptions.comparators.indexOf('>=') < 0 ? 'no' : 'yes'
},
'<': {
symbol: '&#60;',
label: this.$i18n.get('before'),
enabled: this.filterTypeOptions.comparators.indexOf('<') < 0 ? 'no' : 'yes'
},
'<=': {
symbol: '&#8804;',
label: this.$i18n.get('before_or_on_day'),
enabled: this.filterTypeOptions.comparators.indexOf('<=') < 0 ? 'no' : 'yes'
},
};
this.comparator = this.filterTypeOptions.comparators[0];
},
mounted() {
this.updateSelectedValues();
},
@ -220,11 +215,19 @@
.date-filter-container {
display: flex;
height: auto;
align-items: stretch;
@supports not (contain: inline-size) {
@media screen and (min-width: 769px) and (max-width: 1500px) {
flex-wrap: wrap;
height: 60px;
}
}
@container filterscomponentslist (max-width: 170px) {
flex-wrap: wrap;
height: 60px;
}
.dropdown {
width: auto;

View File

@ -13,7 +13,11 @@ class Date extends Filter_Type {
$this->set_name( __('Date', 'tainacan') );
$this->set_supported_types(['date']);
$this->set_component('tainacan-filter-date');
$this->set_form_component('tainacan-filter-form-date');
$this->set_use_max_options(false);
$this->set_default_options([
'comparators' => [ '=', '!=', '>', '>=', '<', '<=' ]
]);
$this->set_preview_template('
<div>
<div>
@ -57,6 +61,40 @@ class Date extends Filter_Type {
');
}
/**
* @inheritdoc
*/
public function get_form_labels(){
return [
'comparators' => [
'title' => __( 'Enabled comparators', 'tainacan' ),
'description' => __( 'A list of comparators to be available in the filter, such as equal, greater than, smaller than, etc.', 'tainacan' ),
]
];
}
/**
* @param \Tainacan\Entities\Filter $filter
* @return array|bool true if is validate or array if has error
*/
public function validate_options(\Tainacan\Entities\Filter $filter) {
if ( !in_array($filter->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
if ( empty( $this->get_option('comparators') ) )
return [
'comparators' => __('"Comparators" array is required', 'tainacan')
];
if ( count( $this->get_option('comparators') ) < 1 )
return [
'comparators' => __('At least one comparator should be provided', 'tainacan')
];
return true;
}
}
class Date_Helper {

View File

@ -0,0 +1,318 @@
<template>
<div>
<b-field
:addons="false"
:type="metadataType"
:message="metadataMessage">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-dates-intersection', 'secondary_filter_metadatum_id') }}<span :class="metadataType">&nbsp;*&nbsp;</span>
<span style="font-size: 1.35em;">
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-dates-intersection', 'secondary_filter_metadatum_id')"
:message="$i18n.getHelperMessage('tainacan-filter-dates-intersection', 'secondary_filter_metadatum_id')" />
</span>
</label>
<b-select
v-model="secondDateMetadatumId"
name="dates_intersect[secondary_filter_metadatum_id]"
:placeholder="$i18n.get('instruction_select_second_date_to_compare' )"
:loading="loading"
expanded
@change="onUpdateSecondDateMetadatumId()"
@focus="clear()">
<option :value="''">
{{ $i18n.get('instruction_select_second_date_to_compare' ) }}
</option>
<option
v-for="option in metadata.filter(aMetadatum => aMetadatum.id != filter.metadatum_id )"
:key="option.id"
:value="option.id">
{{ option.name }}
</option>
</b-select>
</b-field>
<fieldset
v-if="secondDateMetadatumId"
class="intersection-explainer-section">
<legend>
<p>
<strong>{{ $i18n.get('info_intersection_explainer') }}</strong>
<span style="font-size: 1.35em;">
<help-button
:title="$i18n.get('label_comparators')"
:message="$i18n.get('info_intersection_rules')" />
</span>
</p>
</legend>
<b-field :addons="false">
<b-select
v-if="showEditFirstComparatorOptions"
v-model="firstComparator"
@update:model-value="emitValues()">
<option
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
:value="comparatorKey"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-select>
<strong
v-else
v-html="comparatorsObject[firstComparator].symbol" />
<p v-if="filter.metadatum">
&nbsp;
<em>{{ filter.metadatum.metadatum_name }}</em>
</p>
<button
v-if="!showEditFirstComparatorOptions"
class="button is-white is-pulled-right"
@click.prevent="showEditFirstComparatorOptions = true">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-edit has-text-secondary" />
</span>
</button>
<button
v-else
class="button is-white is-pulled-right"
@click.prevent="showEditFirstComparatorOptions = false">
<span
v-tooltip="{
content: $i18n.get('close'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-approved has-text-secondary" />
</span>
</button>
</b-field>
<div class="logic-divider">
<span>{{ $i18n.get('label_and') }}</span>
</div>
<b-field :addons="false">
<b-select
v-if="showEditSecondComparatorOptions"
v-model="secondComparator"
@update:model-value="emitValues()">
<option
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
:value="comparatorKey"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-select>
<strong
v-else
v-html="comparatorsObject[secondComparator].symbol" />
<p>&nbsp;<em>{{ secondDateMetadatumName }}</em></p>
<button
v-if="!showEditSecondComparatorOptions"
class="button is-white is-pulled-right"
@click.prevent="showEditSecondComparatorOptions = true">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-edit has-text-secondary" />
</span>
</button>
<button
v-else
class="button is-white is-pulled-right"
@click.prevent="showEditSecondComparatorOptions = false">
<span
v-tooltip="{
content: $i18n.get('close'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-approved has-text-secondary" />
</span>
</button>
</b-field>
</fieldset>
<!-- Much more complicated logic, will be possible if we implement #889 -->
<!-- <b-field
:addons="false"
:label="$i18n.getHelperTitle('tainacan-filter-dates-intersection', 'accept_date_interval')"
style="margin-top: 1.125rem;"
:type="errors && errors['accept_date_interval'] != undefined ? 'is-danger' : ''"
:message="errors && errors['accept_date_interval'] != undefined ? errors['accept_date_interval'] : ''">
&nbsp;
<b-switch
v-model="acceptDateInterval"
size="is-small"
:true-value="'yes'"
:false-value="'no'"
:native-value="acceptDateInterval == 'yes' ? 'yes' : 'no'"
name="accept_date_interval"
@update:model-value="emitValues()">
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-dates-intersection', 'accept_date_interval')"
:message="$i18n.getHelperMessage('tainacan-filter-dates-intersection', 'accept_date_interval')" />
</b-switch>
</b-field> -->
</div>
</template>
<script>
import { tainacanApi } from '../../../js/axios';
export default {
props: {
filter: Object,
modelValue: Object,
errors: Object
},
emits: [
'update:model-value',
],
data() {
return {
metadata: [],
loading: true,
metadataType: '',
metadataMessage: '',
secondDateMetadatumId: [Number, String],
secondDateMetadatumName: String,
firstComparator: String,
secondComparator: String,
comparatorsObject: {},
acceptDateInterval: String,
showEditFirstComparatorOptions: false,
showEditSecondComparatorOptions: false
}
},
watch: {
errors(){
if ( this.errors && this.errors.secondary_filter_metadatum_id !== '' )
this.setErrorsAttributes( 'is-danger', this.errors.secondary_filter_metadatum_id );
else
this.setErrorsAttributes( '', '' );
}
},
created() {
this.secondDateMetadatumId = this.modelValue && this.modelValue.secondary_filter_metadatum_id ? this.modelValue.secondary_filter_metadatum_id : '';
this.secondDateMetadatumName = this.modelValue && this.modelValue.secondary_filter_metadatum_name ? this.modelValue.secondary_filter_metadatum_name : '';
this.firstComparator = this.modelValue && this.modelValue.first_comparator ? this.modelValue.first_comparator : '>=';
this.secondComparator = this.modelValue && this.modelValue.second_comparator ? this.modelValue.second_comparator : '<=';
this.acceptDateInterval = this.modelValue && this.modelValue.accept_date_interval ? this.modelValue.accept_date_interval : 'no';
this.loading = true;
this.fetchMetadata();
this.comparatorsObject = {
'=': {
symbol: '&#61;',
label: this.$i18n.get('is_equal_to')
},
'!=': {
symbol: '&#8800;',
label: this.$i18n.get('is_not_equal_to')
},
'>': {
symbol: '&#62;',
label: this.$i18n.get('after')
},
'>=': {
symbol: '&#8805;',
label: this.$i18n.get('after_or_on_day')
},
'<': {
symbol: '&#60;',
label: this.$i18n.get('before')
},
'<=': {
symbol: '&#8804;',
label: this.$i18n.get('before_or_on_day')
}
};
},
methods: {
async fetchMetadata() {
let endpoint = this.filter.collection_id && this.filter.collection_id !== 'default' ? ( '/collection/' + this.filter.collection_id + '/metadata' ) : '/metadata';
endpoint += '?metaquery[0][key]=metadata_type&metaquery[0][value]=Tainacan\\Metadata_Types\\Date&nopaging=1&exclude=' + this.filter.metadatum_id;
return await tainacanApi.get(endpoint)
.then(res => {
this.loading = false;
this.metadata = res.data ? res.data : [];
})
.catch(error => {
this.loading = false;
this.$console.log(error);
});
},
onUpdateSecondDateMetadatumId() {
const selectedMetadatum = this.metadata.find( aMetadatum => aMetadatum.id == this.secondDateMetadatumId );
this.secondDateMetadatumName = selectedMetadatum ? selectedMetadatum.name : '';
this.secondDateMetadatumId = selectedMetadatum ? selectedMetadatum.id : '';
this.emitValues();
},
emitValues() {
this.$emit('update:model-value', {
first_comparator: this.firstComparator,
second_comparator: this.secondComparator,
secondary_filter_metadatum_id: this.secondDateMetadatumId,
secondary_filter_metadatum_name: this.secondDateMetadatumName,
accept_date_interval: this.acceptDateInterval
});
},
setErrorsAttributes( type, message ) {
this.metadataType = type;
this.metadataMessage = message;
},
clear(){
this.metadataType = '';
this.metadataMessage = '';
},
}
}
</script>
<style lang="scss" scoped>
.intersection-explainer-section {
margin-top: 1.25rem;
padding: 0.75em 0.75em 0.25em 0.75em;
border: 1px solid var(--tainacan-gray1);
legend {
margin: -0.75em 0 0em 0;
background-color: var(--tainacan-background-color);
padding: 5px 5px 5px 0px;
}
.field {
display: flex;
gap: 0.5em;
margin: 0 -0.5em 0.5em 0em;
align-items: center;
strong {
margin-left: 0.75em;
}
}
button {
border-radius: 100em !important;
margin-left: auto;
}
.logic-divider {
display: none;
}
}
</style>

View File

@ -0,0 +1,232 @@
<template>
<div>
<b-datepicker
v-model="dateInit"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('instruction_select_a_date')"
editable
:trap-focus="false"
:date-formatter="(date) => dateFormatter(date)"
:date-parser="(date) => dateParser(date)"
icon="calendar-today"
:years-range="[-200, 100]"
:day-names="[
$i18n.get('datepicker_short_sunday'),
$i18n.get('datepicker_short_monday'),
$i18n.get('datepicker_short_tuesday'),
$i18n.get('datepicker_short_wednesday'),
$i18n.get('datepicker_short_thursday'),
$i18n.get('datepicker_short_friday'),
$i18n.get('datepicker_short_saturday'),
]"
:month-names="[
$i18n.get('datepicker_month_january'),
$i18n.get('datepicker_month_february'),
$i18n.get('datepicker_month_march'),
$i18n.get('datepicker_month_april'),
$i18n.get('datepicker_month_may'),
$i18n.get('datepicker_month_june'),
$i18n.get('datepicker_month_july'),
$i18n.get('datepicker_month_august'),
$i18n.get('datepicker_month_september'),
$i18n.get('datepicker_month_october'),
$i18n.get('datepicker_month_november'),
$i18n.get('datepicker_month_december')
]"
@focus="isTouched = true"
@update:model-value="($event) => { resetPage(); validadeValues($event) }" />
<p
v-if="filterTypeOptions.accept_date_interval === 'yes'"
style="font-size: 0.75em; margin-bottom: 0.125em;"
class="has-text-centered is-marginless">
{{ $i18n.get('label_until') }}
</p>
<b-datepicker
v-if="filterTypeOptions.accept_date_interval === 'yes'"
v-model="dateEnd"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('instruction_select_a_date')"
editable
:trap-focus="false"
:date-formatter="(date) => dateFormatter(date)"
:date-parser="(date) => dateParser(date)"
icon="calendar-today"
:years-range="[-200, 50]"
:day-names="[
$i18n.get('datepicker_short_sunday'),
$i18n.get('datepicker_short_monday'),
$i18n.get('datepicker_short_tuesday'),
$i18n.get('datepicker_short_wednesday'),
$i18n.get('datepicker_short_thursday'),
$i18n.get('datepicker_short_friday'),
$i18n.get('datepicker_short_saturday'),
]"
:month-names="[
$i18n.get('datepicker_month_january'),
$i18n.get('datepicker_month_february'),
$i18n.get('datepicker_month_march'),
$i18n.get('datepicker_month_april'),
$i18n.get('datepicker_month_may'),
$i18n.get('datepicker_month_june'),
$i18n.get('datepicker_month_july'),
$i18n.get('datepicker_month_august'),
$i18n.get('datepicker_month_september'),
$i18n.get('datepicker_month_october'),
$i18n.get('datepicker_month_november'),
$i18n.get('datepicker_month_december')
]"
@update:model-value="validadeValues()"
@focus="isTouched = true" />
</div>
</template>
<script>
import { dateInter } from "../../../js/mixins";
import { filterTypeMixin } from '../../../js/filter-types-mixin';
import moment from 'moment';
export default {
mixins: [
dateInter,
filterTypeMixin
],
emits: [
'input',
],
data(){
return {
dateInit: undefined,
dateEnd: undefined,
isTouched: false
}
},
watch: {
isTouched( val ){
if ( val && this.dateInit === null)
this.dateInit = new Date();
if ( val && this.dateEnd === null)
this.dateEnd = new Date();
},
'query': {
handler() {
this.updateSelectedValues();
},
deep: true
}
},
mounted() {
this.updateSelectedValues();
},
methods: {
// only validate if the first value is higher than first
validadeValues: _.debounce( function (){
if ( this.dateInit === undefined )
this.dateInit = new Date();
if ( this.dateEnd === undefined )
this.dateEnd = new Date();
if ( this.filterTypeOptions.accept_date_interval === 'yes' && this.dateInit > this.dateEnd ) {
this.showErrorMessage();
return
}
this.emit();
}, 800),
showErrorMessage(){
if ( !this.isTouched ) return false;
this.$buefy.toast.open({
duration: 3000,
message: this.$i18n.get('info_error_first_value_greater'),
position: 'is-bottom',
type: 'is-danger'
})
},
dateFormatter(dateObject){
return moment(dateObject, moment.ISO_8601).format(this.dateFormat);
},
dateParser(dateString){
return moment(dateString, this.dateFormat).toDate();
},
updateSelectedValues(){
if ( !this.query || !this.query.metaquery || !Array.isArray( this.query.metaquery ) )
return false;
let index = this.query.metaquery.findIndex(newMetadatum => newMetadatum.key == this.metadatumId);
if (index >= 0) {
let metadata = this.query.metaquery[ index ];
if (metadata.value ) {
if ( Array.isArray(metadata.value) && metadata.value.length > 0 ) {
const dateValueInit = new Date(metadata.value[0].replace(/-/g, '/'));
this.dateInit = moment(dateValueInit, moment.ISO_8601).toDate();
const dateValueEnd = new Date(metadata.value[1].replace(/-/g, '/'));
this.dateEnd = moment(dateValueEnd, moment.ISO_8601).toDate();
} else {
const dateValueInit = new Date(metadata.value.replace(/-/g, '/'));
this.dateInit = moment(dateValueInit, moment.ISO_8601).toDate();
}
}
} else {
this.dateInit = null;
this.dateEnd = null;
}
},
// emit the operation for listeners
emit() {
let values = [];
if (this.dateInit === null && this.dateEnd === null) {
values = [];
} else {
let dateInit = this.dateInit.getUTCFullYear() + '-' +
('00' + (this.dateInit.getUTCMonth() + 1)).slice(-2) + '-' +
('00' + this.dateInit.getUTCDate()).slice(-2);
if ( this.dateEnd !== null ) {
let dateEnd = this.dateEnd.getUTCFullYear() + '-' +
('00' + (this.dateEnd.getUTCMonth() + 1)).slice(-2) + '-' +
('00' + this.dateEnd.getUTCDate()).slice(-2);
values = [ dateInit, dateEnd ];
} else {
values = [ dateInit ];
}
}
if ( this.filterTypeOptions.accept_date_interval !== 'yes' ) {
this.$emit('input', {
filter: 'intersection',
type: 'DATE',
compare: this.filterTypeOptions.first_comparator,
metadatum_id: this.metadatumId,
collection_id: this.collectionId,
value: values[0]
});
this.$emit('input', {
filter: 'intersection',
type: 'DATE',
compare: this.filterTypeOptions.second_comparator,
metadatum_id: this.filterTypeOptions.secondary_filter_metadatum_id,
collection_id: this.collectionId,
value: values[0],
secondary: true
});
} else {
// Much more complicated logic to be implemented in the future. See #889
}
}
}
}
</script>
<style scoped>
.field {
margin-bottom: 0.125em !important;
}
.dropdown-trigger input {
font-size: 0.75em;
}
</style>

View File

@ -0,0 +1,137 @@
<?php
namespace Tainacan\Filter_Types;
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
/**
* Class TainacanFilterType
*/
class Dates_Intersection extends Filter_Type {
function __construct(){
$this->set_name( __('Dates Intersection', 'tainacan') );
$this->set_supported_types(['date']);
$this->set_component('tainacan-filter-dates-intersection');
$this->set_form_component('tainacan-filter-form-dates-intersection');
$this->set_default_options([
'secondary_filter_metadatum_id' => '',
'secondary_filter_metadatum_name' => '',
'first_comparator' => '>=',
'second_comparator' => '<=',
'accept_date_interval' => 'no'
]);
$this->set_use_max_options(false);
$this->set_preview_template('
<div>
<div class="datepicker control is-small">
<div class="dropdown is-bottom-left is-mobile-modal">
<div role="button" class="dropdown-trigger">
<div class="control has-icons-left is-small is-clearfix">
<input type="text" autocomplete="off" placeholder=" '. __('Select a date', 'tainacan') .'" class="input is-small">
<span class="icon is-left is-small"><i class="mdi mdi-calendar-today"></i></span>
</div>
</div>
</div>
</div>
</div>
');
}
/**
* @inheritdoc
*/
public function get_form_labels(){
return [
'secondary_filter_metadatum_id' => [
'title' => __( 'Second date metadatum', 'tainacan' ),
'description' => __( 'The other metadatum to which this filter will compare values to find if there is an intersection of dates.', 'tainacan' ),
],
'secondary_filter_metadatum_name' => [
'title' => __( 'Second date metadatum', 'tainacan' ),
'description' => __( 'Label of the other metadatum to which this filter will compare values to find if there is an intersection of dates.', 'tainacan' ),
],
'first_comparator' => [
'title' => __( 'First comparator', 'tainacan' ),
'description' => __( 'Comparator to be used for checking the first metadata value.', 'tainacan' ),
],
'second_comparator' => [
'title' => __( 'Second comparator', 'tainacan' ),
'description' => __( 'Comparator to be used for checking the second metadata value.', 'tainacan' ),
],
'accept_date_interval' => [
'title' => __( 'Accept date interval', 'tainacan' ),
'description' => __( 'If checked, the filter will accept date intervals as values.', 'tainacan' ),
]
];
}
/**
* @param \Tainacan\Entities\Filter $filter
* @return array|bool true if is validate or array if has error
*/
public function validate_options(\Tainacan\Entities\Filter $filter) {
if ( !in_array($filter->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
$errors = [];
if ( empty($this->get_option('secondary_filter_metadatum_id')) )
$errors['secondary_filter_metadatum_id'] = __('The secondary date metadatum is required.','tainacan');
if ( empty($this->get_option('first_comparator')) )
$errors['first_comparator'] = __('The first comparator is required.','tainacan');
if ( empty($this->get_option('second_comparator')) )
$errors['second_comparator'] = __('The second comparator is required.','tainacan');
if ( empty($this->get_option('accept_date_interval')) )
$errors['accept_date_interval'] = __('The filter should define if it accepts date interval.','tainacan');
return count($errors) > 0 ? $errors : true;
}
}
class Dates_Intersection_Interval_Helper {
use \Tainacan\Traits\Singleton_Instance;
protected function init() {
add_filter( 'tainacan-api-items-tainacan-filter-dates-intersection-filter-arguments', [$this, 'format_filter_arguments']);
}
function format_filter_arguments( $filter_arguments ) {
if (
!isset($filter_arguments['compare']) ||
!isset($filter_arguments['label'])
) {
return $filter_arguments;
}
if (
is_array($filter_arguments['label']) &&
count($filter_arguments['label']) === 2
) {
$filter_arguments['label'] = $filter_arguments['label'][0] . ' - ' . $filter_arguments['label'][1];
}
if (
isset( $filter_arguments['filter'] ) &&
isset( $filter_arguments['filter']['metadatum'] ) &&
isset( $filter_arguments['filter']['metadatum']['metadatum_name'] )
) {
$filter_arguments['filter']['name'] = $filter_arguments['filter']['metadatum']['metadatum_name'];
}
if (
isset( $filter_arguments['filter'] ) &&
isset( $filter_arguments['filter']['filter_type_options'] ) &&
isset( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] ) &&
!empty( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] )
) {
$filter_arguments['filter']['name'] = $filter_arguments['filter']['name'] . ' - ' . $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'];
}
return $filter_arguments;
}
}
Dates_Intersection_Interval_Helper::get_instance();

View File

@ -38,10 +38,12 @@ class Filter_Type_Helper {
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Selectbox');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Autocomplete');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Date_Interval');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Dates_Intersection');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Numeric_Interval');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\TaxonomyTaginput');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\TaxonomyCheckbox');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Numeric_List_Interval');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Numerics_Intersection');
// the priority should see less than on function
// `load_admin_page()` of class `Admin` in file /src/views/class-tainacan-admin.php

View File

@ -53,8 +53,17 @@ abstract class Filter_Type {
* @var string
*/
private $preview_template = '';
/**
* Defines if the filter type should use the max options
*/
protected $use_max_options = true;
/**
* Defines if the filter type should provide a placeholder for the input field
*/
protected $use_input_placeholder = true;
public function __construct(){
add_action('register_filter_types', array(&$this, 'register_filter_type'));
}
@ -134,6 +143,7 @@ abstract class Filter_Type {
$attributes['supported_types'] = $this->get_supported_types();
$attributes['preview_template'] = $this->get_preview_template();
$attributes['use_max_options'] = $this->get_use_max_options();
$attributes['use_input_placeholder'] = $this->get_use_input_placeholder();
$attributes['form_component'] = $this->get_form_component();
return $attributes;
@ -208,6 +218,14 @@ abstract class Filter_Type {
return $this->use_max_options;
}
public function set_use_input_placeholder($use_input_placeholder) {
$this->use_input_placeholder = $use_input_placeholder;
}
public function get_use_input_placeholder() {
return $this->use_input_placeholder;
}
/**
* Gets one option from the options array.
*

View File

@ -5,6 +5,7 @@
:aria-labelledby="'filter-label-id-' + filter.id"
:aria-minus-label="$i18n.get('label_decrease')"
:aria-plus-label="$i18n.get('label_increase')"
:placeholder="filter.placeholder ? filter.placeholder : ''"
size="is-small"
:step="filterTypeOptions.step"
@update:model-value="($event) => { resetPage(); validadeValues($event) }"
@ -19,6 +20,7 @@
:aria-labelledby="'filter-label-id-' + filter.id"
:aria-minus-label="$i18n.get('label_decrease')"
:aria-plus-label="$i18n.get('label_increase')"
:placeholder="filter.placeholder ? filter.placeholder : ''"
size="is-small"
:step="filterTypeOptions.step"
@update:model-value="($event) => { resetPage(); validadeValues($event) }" />

View File

@ -3,7 +3,7 @@
<b-select
v-model="selectedInterval"
expanded
:placeholder="$i18n.get('instruction_select_a_interval')"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('instruction_select_a_interval')"
@update:model-value="($event) => { resetPage; changeInterval($event) }">
<option value="">
{{ $i18n.get('label_selectbox_init') }}...

View File

@ -13,7 +13,7 @@
<b-select
v-model="step"
name="step_options"
@update:model-value="onUpdateStep">
@update:model-value="emitValues()">
<option value="0.001">
0.001
</option>
@ -70,7 +70,7 @@
name="max_options"
type="number"
step="1"
@update:model-value="onUpdateStep" />
@update:model-value="emitValues()" />
<button
class="button is-white is-pulled-right"
@click.prevent="showEditStepOptions = false">
@ -87,6 +87,26 @@
</button>
</div>
</b-field>
<b-field :addons="false">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-numeric', 'comparators') }}<span>&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numeric', 'comparators')"
:message="$i18n.getHelperMessage('tainacan-filter-numeric', 'comparators')" />
</label>
<div>
<b-checkbox
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
v-model="comparators"
:native-value="comparatorKey"
:disabled="comparators.indexOf(comparatorKey) >= 0 && comparators.length <= 1"
name="numeric_filter_options[comparators]"
@update:model-value="emitValues()">
<span v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-checkbox>
</div>
</b-field>
</div>
</template>
@ -102,15 +122,50 @@
data() {
return {
step: [Number, String],
showEditStepOptions: false
showEditStepOptions: false,
comparatorsObject: Object,
comparators: Array
}
},
created() {
this.step = this.modelValue && this.modelValue.step ? this.modelValue.step : 1;
this.comparators = ( this.modelValue && this.modelValue.comparators ) ? this.modelValue.comparators : [ '=', '!=', '>', '>=', '<', '<=' ];
this.comparatorsObject = {
'=': {
symbol: '&#61;',
label: this.$i18n.get('is_equal_to'),
enabled: this.comparators.indexOf('=') < 0 ? 'no' : 'yes'
},
'!=': {
symbol: '&#8800;',
label: this.$i18n.get('is_not_equal_to'),
enabled: this.comparators.indexOf('!=') < 0 ? 'no' : 'yes'
},
'>': {
symbol: '&#62;',
label: this.$i18n.get('after'),
enabled: this.comparators.indexOf('>') < 0 ? 'no' : 'yes'
},
'>=': {
symbol: '&#8805;',
label: this.$i18n.get('after_or_on_day'),
enabled: this.comparators.indexOf('>=') < 0 ? 'no' : 'yes'
},
'<': {
symbol: '&#60;',
label: this.$i18n.get('before'),
enabled: this.comparators.indexOf('<') < 0 ? 'no' : 'yes'
},
'<=': {
symbol: '&#8804;',
label: this.$i18n.get('before_or_on_day'),
enabled: this.comparators.indexOf('<=') < 0 ? 'no' : 'yes'
}
};
},
methods: {
onUpdateStep(modelValue) {
this.$emit('update:model-value', { step: modelValue });
emitValues() {
this.$emit('update:model-value', { step: this.step, comparators: this.comparators });
}
}
}

View File

@ -1,6 +1,7 @@
<template>
<div class="numeric-filter-container">
<b-dropdown
v-if="filterTypeOptions.comparators.length > 1"
:mobile-modal="true"
aria-role="list"
trap-focus
@ -10,62 +11,31 @@
:aria-label="$i18n.get('label_comparator')"
class="button is-white">
<span class="icon is-small">
<i v-html="comparatorSymbol" />
<i v-html="comparatorsObject[comparator].symbol" />
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-arrowdown" />
</span>
</button>
</template>
<template
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey">
<b-dropdown-item
v-if="comparatorObject.enabled == 'yes'"
role="button"
:class="{ 'is-active': comparator == '=' }"
:value="'='"
aria-role="listitem">
&#61;&nbsp; {{ $i18n.get('is_equal_to') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '!=' }"
:value="'!='"
aria-role="listitem">
&#8800;&nbsp; {{ $i18n.get('is_not_equal_to') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '>' }"
:value="'>'"
aria-role="listitem">
&#62;&nbsp; {{ $i18n.get('greater_than') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '>=' }"
:value="'>='"
aria-role="listitem">
&#8805;&nbsp; {{ $i18n.get('greater_than_or_equal_to') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '<' }"
:value="'<'"
aria-role="listitem">
&#60;&nbsp; {{ $i18n.get('less_than') }}
</b-dropdown-item>
<b-dropdown-item
role="button"
:class="{ 'is-active': comparator == '<=' }"
:value="'<='"
aria-role="listitem">
&#8804;&nbsp; {{ $i18n.get('less_than_or_equal_to') }}
</b-dropdown-item>
:class="{ 'is-active': comparator == comparatorKey }"
:value="comparatorKey"
aria-role="listitem"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</template>
</b-dropdown>
<b-numberinput
v-model="value"
:aria-labelledby="'filter-label-id-' + filter.id"
:aria-minus-label="$i18n.get('label_decrease')"
:aria-plus-label="$i18n.get('label_increase')"
:placeholder="filter.placeholder ? filter.placeholder : ''"
size="is-small"
:step="Number(filterTypeOptions.step)"
@update:model-value="($event) => { resetPage($event); emit($event); }" />
@ -89,19 +59,6 @@
comparator: '=' // =, !=, >, >=, <, <=
}
},
computed: {
comparatorSymbol() {
switch(this.comparator) {
case '=': return '&#61;';
case '!=': return '&#8800;';
case '>': return '&#62;';
case '>=': return '&#8805;';
case '<': return '&#60;';
case '<=': return '&#8804;';
default: return '';
}
}
},
watch: {
'query': {
handler() {
@ -110,6 +67,41 @@
deep: true
}
},
created() {
this.comparatorsObject = {
'=': {
symbol: '&#61;',
label: this.$i18n.get('is_equal_to'),
enabled: this.filterTypeOptions.comparators.indexOf('=') < 0 ? 'no' : 'yes'
},
'!=': {
symbol: '&#8800;',
label: this.$i18n.get('is_not_equal_to'),
enabled: this.filterTypeOptions.comparators.indexOf('!=') < 0 ? 'no' : 'yes'
},
'>': {
symbol: '&#62;',
label: this.$i18n.get('greater_than'),
enabled: this.filterTypeOptions.comparators.indexOf('>') < 0 ? 'no' : 'yes'
},
'>=': {
symbol: '&#8805;',
label: this.$i18n.get('greater_than_or_equal_to'),
enabled: this.filterTypeOptions.comparators.indexOf('>=') < 0 ? 'no' : 'yes'
},
'<': {
symbol: '&#60;',
label: this.$i18n.get('less_than'),
enabled: this.filterTypeOptions.comparators.indexOf('<') < 0 ? 'no' : 'yes'
},
'<=': {
symbol: '&#8804;',
label: this.$i18n.get('less_than_or_equal_to'),
enabled: this.filterTypeOptions.comparators.indexOf('<=') < 0 ? 'no' : 'yes'
},
};
this.comparator = this.filterTypeOptions.comparators[0];
},
mounted() {
this.updateSelectedValues();
},
@ -165,15 +157,25 @@
height: auto;
align-items: stretch;
@supports not (contain: inline-size) {
@media screen and (min-width: 769px) and (max-width: 1500px) {
flex-wrap: wrap;
align-items: center;
height: 60px;
}
}
@container filterscomponentslist (max-width: 170px) {
flex-wrap: wrap;
height: 60px;
.dropdown {
flex-grow: 2 !important;
}
}
.dropdown {
width: auto;
flex-grow: 2;
flex-grow: 0;
.dropdown-trigger button {
padding: 2px 0.5em 2px 0.5em !important;

View File

@ -16,7 +16,8 @@ class Numeric extends Filter_Type {
$this->set_form_component('tainacan-filter-form-numeric');
$this->set_use_max_options(false);
$this->set_default_options([
'step' => 1
'step' => 1,
'comparators' => [ '=', '!=', '>', '>=', '<', '<=' ]
]);
$this->set_preview_template('
<div>
@ -77,6 +78,10 @@ class Numeric extends Filter_Type {
'step' => [
'title' => __( 'Step', 'tainacan' ),
'description' => __( 'The amount to be increased or decreased when clicking on the filter control buttons. This also defines whether the input accepts decimal numbers.', 'tainacan' ),
],
'comparators' => [
'title' => __( 'Enabled comparators', 'tainacan' ),
'description' => __( 'A list of comparators to be available in the filter, such as equal, greater than, smaller than, etc.', 'tainacan' ),
]
];
}
@ -89,13 +94,24 @@ class Numeric extends Filter_Type {
if ( !in_array($filter->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
if ( empty($this->get_option('step')) ) {
return [
$errors = [];
if ( empty($this->get_option('step')) )
$errors[] = [
'step' => __('"Step" value is required','tainacan')
];
}
return true;
if ( empty($this->get_option('comparators')) )
$errors[] = [
'comparators' => __('"Comparators" array is required', 'tainacan')
];
if ( count( $this->get_option('comparators') ) < 1 )
$errors[] = [
'comparators' => __('At least one comparator should be provided', 'tainacan')
];
return count($errors) ? $errors : true;
}
}
@ -134,6 +150,8 @@ class Numeric_Helper {
case '<=':
$filter_arguments['label'] = '&#8804; ' . $filter_arguments['label'][0];
break;
default:
$filter_arguments['label'] = $filter_arguments['label'][0];
}
}

View File

@ -0,0 +1,410 @@
<template>
<div>
<b-field :addons="false">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-numeric-interval', 'step') }}<span>&nbsp;*&nbsp;</span>
<span style="font-size: 1.35em;">
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numeric-interval', 'step')"
:message="$i18n.getHelperMessage('tainacan-filter-numeric-interval', 'step')" />
</span>
</label>
<div
v-if="!showEditStepOptions"
class="is-flex">
<b-select
v-model="step"
name="step_options"
@update:model-value="emitValues()">
<option value="0.001">
0.001
</option>
<option value="0.01">
0.01
</option>
<option value="0.1">
0.1
</option>
<option value="1">
1
</option>
<option value="2">
2
</option>
<option value="5">
5
</option>
<option value="10">
10
</option>
<option value="100">
100
</option>
<option value="1000">
1000
</option>
<option
v-if="step && ![0.001,0.01,0.1,1,2,5,10,100,1000].find( (element) => element == step )"
:value="step">
{{ step }}</option>
</b-select>
<button
class="button is-white is-pulled-right"
:aria-label="$i18n.get('edit')"
@click.prevent="showEditStepOptions = true">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-edit has-text-secondary" />
</span>
</button>
</div>
<div
v-if="showEditStepOptions"
class="is-flex">
<b-input
v-model="step"
name="max_options"
type="number"
step="1"
@update:model-value="emitValues()" />
<button
class="button is-white is-pulled-right"
@click.prevent="showEditStepOptions = false">
<span
v-tooltip="{
content: $i18n.get('close'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-close has-text-secondary" />
</span>
</button>
</div>
</b-field>
<b-field
:addons="false"
:type="metadataType"
:message="metadataMessage">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'secondary_filter_metadatum_id') }}<span :class="metadataType">&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'secondary_filter_metadatum_id')"
:message="$i18n.getHelperMessage('tainacan-filter-numerics-intersection', 'secondary_filter_metadatum_id')" />
</label>
<b-select
v-model="secondNumericMetadatumId"
name="numerics_intersect[secondary_filter_metadatum_id]"
:placeholder="$i18n.get('instruction_select_second_numeric_to_compare' )"
:loading="loading"
expanded
@change="onUpdateSecondNumericMetadatumId()"
@focus="clear()">
<option :value="''">
{{ $i18n.get('instruction_select_second_numeric_to_compare' ) }}
</option>
<option
v-for="option in metadata.filter(aMetadatum => aMetadatum.id != filter.metadatum_id )"
:key="option.id"
:value="option.id">
{{ option.name }}
</option>
</b-select>
</b-field>
<fieldset
v-if="secondNumericMetadatumId"
class="intersection-explainer-section">
<legend>
<p>
<strong>{{ $i18n.get('info_intersection_explainer') }}</strong>
<span style="font-size: 1.35em;">
<help-button
:title="$i18n.get('label_comparators')"
:message="$i18n.get('info_intersection_rules')" />
</span>
</p>
</legend>
<b-field :addons="false">
<b-select
v-if="showEditFirstComparatorOptions"
v-model="firstComparator"
@update:model-value="emitValues()">
<option
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
:value="comparatorKey"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-select>
<strong
v-else
v-html="comparatorsObject[firstComparator].symbol" />
<p v-if="filter.metadatum">
&nbsp;
<em>{{ filter.metadatum.metadatum_name }}</em>
</p>
<button
v-if="!showEditFirstComparatorOptions"
class="button is-white is-pulled-right"
@click.prevent="showEditFirstComparatorOptions = true">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-edit has-text-secondary" />
</span>
</button>
<button
v-else
class="button is-white is-pulled-right"
@click.prevent="showEditFirstComparatorOptions = false">
<span
v-tooltip="{
content: $i18n.get('close'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-approved has-text-secondary" />
</span>
</button>
</b-field>
<div class="logic-divider">
<span>{{ $i18n.get('label_and') }}</span>
</div>
<b-field :addons="false">
<b-select
v-if="showEditSecondComparatorOptions"
v-model="secondComparator"
@update:model-value="emitValues()">
<option
v-for="(comparatorObject, comparatorKey) in comparatorsObject"
:key="comparatorKey"
:value="comparatorKey"
v-html="comparatorObject.symbol + '&nbsp;' + comparatorObject.label" />
</b-select>
<strong
v-else
v-html="comparatorsObject[secondComparator].symbol" />
<p>&nbsp;<em>{{ secondNumericMetadatumName }}</em></p>
<button
v-if="!showEditSecondComparatorOptions"
class="button is-white is-pulled-right"
@click.prevent="showEditSecondComparatorOptions = true">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-edit has-text-secondary" />
</span>
</button>
<button
v-else
class="button is-white is-pulled-right"
@click.prevent="showEditSecondComparatorOptions = false">
<span
v-tooltip="{
content: $i18n.get('close'),
autoHide: true,
placement: 'bottom',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon">
<i class="tainacan-icon tainacan-icon-18px tainacan-icon-approved has-text-secondary" />
</span>
</button>
</b-field>
</fieldset>
<!-- Much more complicated logic, will be possible if we implement #889 -->
<!-- <b-field
:addons="false"
:label="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'accept_numeric_interval')"
style="margin-top: 1.125rem;"
:type="errors && errors['accept_numeric_interval'] != undefined ? 'is-danger' : ''"
:message="errors && errors['accept_numeric_interval'] != undefined ? errors['accept_numeric_interval'] : ''">
&nbsp;
<b-switch
v-model="acceptNumericInterval"
size="is-small"
:true-value="'yes'"
:false-value="'no'"
:native-value="acceptNumericInterval == 'yes' ? 'yes' : 'no'"
name="accept_numeric_interval"
@update:model-value="emitValues()">
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-numerics-intersection', 'accept_numeric_interval')"
:message="$i18n.getHelperMessage('tainacan-filter-numerics-intersection', 'accept_numeric_interval')" />
</b-switch>
</b-field> -->
</div>
</template>
<script>
import { tainacanApi } from '../../../js/axios';
export default {
props: {
filter: Object,
modelValue: Object,
errors: Object
},
emits: [
'update:model-value',
],
data() {
return {
step: [Number, String],
showEditStepOptions: false,
metadata: [],
loading: true,
metadataType: '',
metadataMessage: '',
secondNumericMetadatumId: [Number, String],
secondNumericMetadatumName: String,
firstComparator: String,
secondComparator: String,
comparatorsObject: {},
acceptNumericInterval: String,
showEditFirstComparatorOptions: false,
showEditSecondComparatorOptions: false
}
},
watch: {
errors() {
if ( this.errors && this.errors.secondary_filter_metadatum_id !== '' )
this.setErrorsAttributes( 'is-danger', this.errors.secondary_filter_metadatum_id );
else
this.setErrorsAttributes( '', '' );
}
},
created() {
this.step = this.modelValue && this.modelValue.step ? this.modelValue.step : 1;
this.secondNumericMetadatumId = this.modelValue && this.modelValue.secondary_filter_metadatum_id ? this.modelValue.secondary_filter_metadatum_id : '';
this.secondNumericMetadatumName = this.modelValue && this.modelValue.secondary_filter_metadatum_name ? this.modelValue.secondary_filter_metadatum_name : '';
this.firstComparator = this.modelValue && this.modelValue.first_comparator ? this.modelValue.first_comparator : '>=';
this.secondComparator = this.modelValue && this.modelValue.second_comparator ? this.modelValue.second_comparator : '<=';
this.acceptNumericInterval = this.modelValue && this.modelValue.accept_numeric_interval ? this.modelValue.accept_numeric_interval : 'no';
this.loading = true;
this.fetchMetadata();
this.comparatorsObject = {
'=': {
symbol: '&#61;',
label: this.$i18n.get('is_equal_to')
},
'!=': {
symbol: '&#8800;',
label: this.$i18n.get('is_not_equal_to')
},
'>': {
symbol: '&#62;',
label: this.$i18n.get('greater_than')
},
'>=': {
symbol: '&#8805;',
label: this.$i18n.get('greater_than_or_equal_to')
},
'<': {
symbol: '&#60;',
label: this.$i18n.get('less_than')
},
'<=': {
symbol: '&#8804;',
label: this.$i18n.get('less_than_or_equal_to')
}
};
},
methods: {
async fetchMetadata() {
let endpoint = this.filter.collection_id && this.filter.collection_id !== 'default' ? ( '/collection/' + this.filter.collection_id + '/metadata' ) : '/metadata';
endpoint += '?metaquery[0][key]=metadata_type&metaquery[0][value]=Tainacan\\Metadata_Types\\Numeric&nopaging=1&exclude=' + this.filter.metadatum_id;
return await tainacanApi.get(endpoint)
.then(res => {
this.loading = false;
this.metadata = res.data ? res.data : [];
})
.catch(error => {
this.loading = false;
this.$console.log(error);
});
},
onUpdateSecondNumericMetadatumId() {
const selectedMetadatum = this.metadata.find( aMetadatum => aMetadatum.id == this.secondNumericMetadatumId );
this.secondNumericMetadatumName = selectedMetadatum ? selectedMetadatum.name : '';
this.secondNumericMetadatumId = selectedMetadatum ? selectedMetadatum.id : '';
this.emitValues();
},
emitValues() {
this.$emit('update:model-value', {
step: this.step,
first_comparator: this.firstComparator,
second_comparator: this.secondComparator,
secondary_filter_metadatum_id: this.secondNumericMetadatumId,
secondary_filter_metadatum_name: this.secondNumericMetadatumName,
accept_numeric_interval: this.acceptNumericInterval
});
},
setErrorsAttributes( type, message ) {
this.metadataType = type;
this.metadataMessage = message;
},
clear(){
this.metadataType = '';
this.metadataMessage = '';
},
}
}
</script>
<style lang="scss" scoped>
.intersection-explainer-section {
margin-top: 1.25rem;
padding: 0.75em 0.75em 0.25em 0.75em;
border: 1px solid var(--tainacan-gray1);
legend {
margin: -0.75em 0 0em 0;
background-color: var(--tainacan-background-color);
padding: 5px 5px 5px 0px;
}
.field {
display: flex;
gap: 0.5em;
margin: 0 -0.5em 0.5em 0em;
align-items: center;
strong {
margin-left: 0.75em;
}
}
button {
border-radius: 100em !important;
margin-left: auto;
}
.logic-divider {
display: none;
}
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<div>
<b-numberinput
v-model="valueInit"
:aria-labelledby="'filter-label-id-' + filter.id"
:aria-minus-label="$i18n.get('label_decrease')"
:aria-plus-label="$i18n.get('label_increase')"
:placeholder="filter.placeholder ? filter.placeholder : ''"
size="is-small"
:step="filterTypeOptions.step"
@update:model-value="($event) => { resetPage(); validadeValues($event) }"
/>
<p
v-if="filterTypeOptions.accept_numeric_interval === 'yes'"
style="font-size: 0.75em; margin-bottom: 0.125em;"
class="has-text-centered is-marginless">
{{ $i18n.get('label_until') }}
</p>
<b-numberinput
v-if="filterTypeOptions.accept_numeric_interval === 'yes'"
v-model="valueEnd"
:aria-labelledby="'filter-label-id-' + filter.id"
:aria-minus-label="$i18n.get('label_decrease')"
:aria-plus-label="$i18n.get('label_increase')"
:placeholder="filter.placeholder ? filter.placeholder : ''"
size="is-small"
:step="filterTypeOptions.step"
@update:model-value="($event) => { resetPage(); validadeValues($event) }" />
</div>
</template>
<script>
import { filterTypeMixin } from '../../../js/filter-types-mixin';
export default {
mixins: [ filterTypeMixin ],
emits: [
'input',
],
data(){
return {
valueInit: null,
valueEnd: null
}
},
watch: {
'query': {
handler() {
this.updateSelectedValues();
},
deep: true
}
},
mounted() {
this.updateSelectedValues();
},
methods: {
// only validate if the first value is higher than first
validadeValues: _.debounce( function () {
if ( this.filterTypeOptions.accept_numeric_interval !== 'yes' )
this.valueEnd = this.valueInit;
if (this.valueInit == null || this.valueEnd == null )
return
if (this.valueInit.constructor == Number)
this.valueInit = this.valueInit.valueOf();
if (this.valueEnd.constructor == Number)
this.valueEnd = this.valueEnd.valueOf();
this.valueInit = parseFloat(this.valueInit);
this.valueEnd = parseFloat(this.valueEnd);
if ( isNaN(this.valueInit) || isNaN(this.valueEnd) )
return
if ( this.filterTypeOptions.accept_numeric_interval === 'yes' && this.valueInit > this.valueEnd ) {
this.showErrorMessage();
return;
}
this.emit();
}, 600),
// message for error
showErrorMessage(){
this.$buefy.toast.open({
duration: 3000,
message: this.$i18n.get('info_error_first_value_greater'),
position: 'is-bottom',
type: 'is-danger'
})
},
// emit the operation for listeners
emit() {
let values = [ this.valueInit, this.valueEnd ];
let type = ! Number.isInteger( this.valueInit ) || ! Number.isInteger( this.valueEnd ) ? 'DECIMAL(20,3)' : 'NUMERIC';
if ( this.filterTypeOptions.accept_numeric_interval !== 'yes' ) {
this.$emit('input', {
filter: 'intersection',
type: type,
compare: this.filterTypeOptions.first_comparator,
metadatum_id: this.metadatumId,
collection_id: this.collectionId,
value: values[0]
});
this.$emit('input', {
filter: 'intersection',
type: type,
compare: this.filterTypeOptions.second_comparator,
metadatum_id: this.filterTypeOptions.secondary_filter_metadatum_id,
collection_id: this.collectionId,
value: values[0],
secondary: true
});
} else {
// Much more complicated logic to be implemented in the future. See #889
}
},
updateSelectedValues(){
if ( !this.query || !this.query.metaquery || !Array.isArray( this.query.metaquery ) )
return false;
let index = this.query.metaquery.findIndex(newMetadatum => newMetadatum.key == this.metadatumId );
if ( index >= 0 ) {
let metaquery = this.query.metaquery[ index ];
if ( metaquery.value ) {
if ( Array.isArray(metaquery.value) && metaquery.value.length > 1 ) {
this.valueInit = new Number(metaquery.value[0]);
this.valueEnd = new Number(metaquery.value[1]);
} else {
this.valueInit = new Number(metaquery.value);
}
}
} else {
this.valueInit = null;
this.valueEnd = null;
}
},
}
}
</script>
<style scoped>
.field {
margin-bottom: 0.125em !important;
}
</style>

View File

@ -0,0 +1,148 @@
<?php
namespace Tainacan\Filter_Types;
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
/**
* Class TainacanFilterType
*/
class Numerics_Intersection extends Filter_Type {
function __construct(){
$this->set_name( __('Numerics Intersection', 'tainacan') );
$this->set_supported_types(['float']);
$this->set_component('tainacan-filter-numerics-intersection');
$this->set_form_component('tainacan-filter-form-numerics-intersection');
$this->set_default_options([
'step' => 1,
'secondary_filter_metadatum_id' => '',
'secondary_filter_metadatum_name' => '',
'first_comparator' => '>=',
'second_comparator' => '<=',
'accept_numeric_interval' => 'no'
]);
$this->set_use_max_options(false);
$this->set_preview_template('
<div>
<div class="b-numberinput field is-grouped">
<p class="control">
<button type="button" class="button is-primary is-small">
<span class="icon is-small">
<i class="mdi mdi-minus"></i>
</span>
</button>
</p>
<div class="control is-small is-clearfix">
<input type="number" step="0.01" class="input is-small" value="6">
</div>
<p class="control">
<button type="button" class="button is-primary is-small">
<span class="icon is-small">
<i class="mdi mdi-plus"></i>
</span>
</button>
</p>
</div>
</div>
');
}
public function get_form_labels(){
return [
'step' => [
'title' => __( 'Step', 'tainacan' ),
'description' => __( 'The amount to be increased or decreased when clicking on the filter control buttons. This also defines whether the input accepts decimal numbers.', 'tainacan' ),
],
'secondary_filter_metadatum_id' => [
'title' => __( 'Second numeric metadatum', 'tainacan' ),
'description' => __( 'The other metadatum to which this filter will compare values to find if there is an intersection of numeric values.', 'tainacan' ),
],
'secondary_filter_metadatum_name' => [
'title' => __( 'Second numeric metadatum', 'tainacan' ),
'description' => __( 'Label of the other metadatum to which this filter will compare values to find if there is an intersection of numeric values.', 'tainacan' ),
],
'first_comparator' => [
'title' => __( 'First comparator', 'tainacan' ),
'description' => __( 'Comparator to be used for checking the first metadata value.', 'tainacan' ),
],
'second_comparator' => [
'title' => __( 'Second comparator', 'tainacan' ),
'description' => __( 'Comparator to be used for checking the second metadata value.', 'tainacan' ),
],
'accept_numeric_interval' => [
'title' => __( 'Accept numeric interval', 'tainacan' ),
'description' => __( 'If checked, the filter will accept numeric intervals as values.', 'tainacan' ),
]
];
}
/**
* @param \Tainacan\Entities\Filter $filter
* @return array|bool true if is validate or array if has error
*/
public function validate_options(\Tainacan\Entities\Filter $filter) {
if ( !in_array($filter->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
$errors = [];
if ( empty($this->get_option('secondary_filter_metadatum_id')) )
$errors['secondary_filter_metadatum_id'] = __('The secondary numeric metadatum is required.','tainacan');
if ( empty($this->get_option('first_comparator')) )
$errors['first_comparator'] = __('The first comparator is required.','tainacan');
if ( empty($this->get_option('second_comparator')) )
$errors['second_comparator'] = __('The second comparator is required.','tainacan');
if ( empty($this->get_option('accept_numeric_interval')) )
$errors['accept_numeric_interval'] = __('The filter should define if it accepts a numeric interval.','tainacan');
return count($errors) > 0 ? $errors : true;
}
}
class Numerics_Intersection_Interval_Helper {
use \Tainacan\Traits\Singleton_Instance;
protected function init() {
add_filter( 'tainacan-api-items-tainacan-filter-numerics-intersection-filter-arguments', [$this, 'format_filter_arguments']);
}
function format_filter_arguments( $filter_arguments ) {
if (
!isset($filter_arguments['compare']) ||
!isset($filter_arguments['label'])
) {
return $filter_arguments;
}
if (
is_array($filter_arguments['label']) &&
count($filter_arguments['label']) === 2
) {
$filter_arguments['label'] = $filter_arguments['label'][0] . ' - ' . $filter_arguments['label'][1];
}
if (
isset( $filter_arguments['filter'] ) &&
isset( $filter_arguments['filter']['metadatum'] ) &&
isset( $filter_arguments['filter']['metadatum']['metadatum_name'] )
) {
$filter_arguments['filter']['name'] = $filter_arguments['filter']['metadatum']['metadatum_name'];
}
if (
isset( $filter_arguments['filter'] ) &&
isset( $filter_arguments['filter']['filter_type_options'] ) &&
isset( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] ) &&
!empty( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] )
) {
$filter_arguments['filter']['name'] = $filter_arguments['filter']['name'] . ' - ' . $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'];
}
return $filter_arguments;
}
}
Numerics_Intersection_Interval_Helper::get_instance();

View File

@ -6,7 +6,7 @@
v-if="!isLoadingOptions"
:model-value="selected"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="$i18n.get('label_selectbox_init')"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('label_selectbox_init')"
expanded
@update:model-value="($event) => { resetPage(); onSelect($event) }">
<option value="">

View File

@ -13,7 +13,7 @@
attached
:aria-close-label="$i18n.get('remove_value')"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="getInputPlaceholder"
:placeholder="filter.placeholder ? filter.placeholder : getInputPlaceholder"
check-infinite-scroll
@update:model-value="($event) => { resetPage(); onSelect($event) }"
@typing="search"

View File

@ -6,24 +6,13 @@
@touchstart="setFilterFocus(filter.id)"
@mousedown="setFilterFocus(filter.id)">
<b-collapse
v-if="displayFilter"
v-if="!hideCollapses && displayFilter"
v-model="singleCollapseOpen"
class="show"
animation="filter-item">
<template #trigger="props">
<button
:id="'filter-label-id-' + filter.id"
v-tooltip="{
delay: {
shown: 500,
hide: 300,
},
content: filter.name,
html: false,
autoHide: false,
placement: 'top-start',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
:for="'filter-input-id-' + filter.id"
:aria-controls="'filter-input-id-' + filter.id"
:aria-expanded="singleCollapseOpen"
@ -37,10 +26,22 @@
}"
class="tainacan-icon tainacan-icon-1-25em" />
</span>
<span class="collapse-label">{{ filter.name }}</span>
<span
class="collapse-label">
{{ filter.name }}
</span>
<help-button
v-if="filter.description_bellow_name !== 'yes' && filter.description"
:title="filter.name"
:message="filter.description" />
</button>
</template>
<div :id="'filter-input-id-' + filter.id">
<p
v-if="filter.description_bellow_name === 'yes' && filter.description"
class="filter-description-help-info">
{{ filter.description }}
</p>
<component
:is="filter.filter_type_object ? filter.filter_type_object.component : null"
:filter="filter"
@ -55,14 +56,21 @@
</div>
</b-collapse>
<div
v-if="beginWithFilterCollapsed && !displayFilter"
v-if="!hideCollapses && beginWithFilterCollapsed && !displayFilter"
class="collapse show disabled-filter">
<div class="collapse-trigger">
<button
:for="'filter-input-id-' + filter.id"
:aria-controls="'filter-input-id-' + filter.id"
class="label"
@click="displayFilter = true">
<span class="icon">
<i class="tainacan-icon tainacan-icon-arrowright tainacan-icon-1-25em" />
</span>
<span
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('instruction_click_to_load_filter'),
@ -71,17 +79,48 @@
placement: 'top-start',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
:for="'filter-input-id-' + filter.id"
:aria-controls="'filter-input-id-' + filter.id"
class="label"
@click="displayFilter = true">
<span class="icon">
<i class="tainacan-icon tainacan-icon-arrowright tainacan-icon-1-25em" />
class="collapse-label">
{{ filter.name }}
</span>
<span class="collapse-label">{{ filter.name }}</span>
<help-button
v-if="filter.description_bellow_name !== 'yes' && filter.description"
:title="filter.name"
:message="filter.description" />
</button>
</div>
</div>
<template v-if="hideCollapses">
<label
:for="'filter-input-id-' + filter.id"
class="label">
<span
class="collapse-label">
{{ filter.name }}
</span>
<help-button
v-if="filter.description_bellow_name !== 'yes' && filter.description"
:title="filter.name"
:message="filter.description" />
</label>
<div :id="'filter-input-id-' + filter.id">
<p
v-if="filter.description_bellow_name === 'yes' && filter.description"
class="filter-description-help-info">
{{ filter.description }}
</p>
<component
:is="filter.filter_type_object ? filter.filter_type_object.component : null"
:filter="filter"
:query="query"
:is-using-elastic-search="isUsingElasticSearch"
:is-repository-level="isRepositoryLevel"
:is-loading-items="isLoadingItems"
:current-collection-id="$eventBusSearch.collectionId"
:filters-as-modal="filtersAsModal"
@input="onInput"
@update-parent-collapse="onFilterUpdateParentCollapse" />
</div>
</template>
</b-field>
</template>
@ -100,8 +139,10 @@
TainacanFilterTaxonomyCheckbox: defineAsyncComponent(() => import('./taxonomy/TainacanFilterCheckbox.vue')),
TainacanFilterTaxonomyTaginput: defineAsyncComponent(() => import('./taxonomy/TainacanFilterTaginput.vue')),
TainacanFilterDateInterval: defineAsyncComponent(() => import('./date-interval/TainacanFilterDateInterval.vue')),
TainacanFilterDatesIntersection: defineAsyncComponent(() => import('./dates-intersection/TainacanFilterDatesIntersection.vue')),
TainacanFilterNumericInterval: defineAsyncComponent(() => import('./numeric-interval/TainacanFilterNumericInterval.vue')),
TainacanFilterNumericListInterval: defineAsyncComponent(() => import('./numeric-list-interval/TainacanFilterNumericListInterval.vue'))
TainacanFilterNumericListInterval: defineAsyncComponent(() => import('./numeric-list-interval/TainacanFilterNumericListInterval.vue')),
TainacanFilterNumericsIntersection: defineAsyncComponent(() => import('./numerics-intersection/TainacanFilterNumericsIntersection.vue'))
},
props: {
filter: Object,
@ -110,7 +151,8 @@
expandAll: true,
isLoadingItems: true,
filtersAsModal: Boolean,
isMobileScreen: false
isMobileScreen: false,
hideCollapses: false
},
data() {
return {
@ -128,7 +170,7 @@
watch: {
expandAll() {
this.singleCollapseOpen = this.expandAll;
if (this.expandAll)
if ( this.expandAll )
this.displayFilter = true;
},
beginWithFilterCollapsed: {
@ -203,7 +245,29 @@
.column {
padding: 0.75em 1px 0.75em 0 !important;
}
& > .label {
display: block !important;
width: 100%;
overflow-x: hidden;
text-overflow: ellipsis;
line-height: 1.4em !important;
border: none;
background-color: transparent;
color: var(--tainacan-label-color);
text-align: left;
outline: none;
padding: 0 !important;
margin: 0 0 8px 0;
.tainacan-help-tooltip-trigger {
font-size: 1.188em;
.icon {
margin-right: 0px;
margin-left: 6px;
}
}
}
.collapse {
.label {
display: inline-flex;
@ -216,6 +280,15 @@
outline: none;
padding: 0 !important;
margin: 0;
.tainacan-help-tooltip-trigger {
font-size: 1.188em;
.icon {
margin-right: 0px;
margin-left: 6px;
}
}
}
}
@ -225,6 +298,13 @@
width: 100%;
}
.filter-description-help-info {
font-size: 0.75em;
color: var(--tainacan-info-color);
margin-top: -0.25em;
margin-bottom: 0.75em;
}
.taginput-container {
border-radius: var(--tainacan-input-border-radius, 1px) !important;
box-shadow: none !important;

View File

@ -20,10 +20,23 @@
@input="resetPage">
<span class="check" />
<span class="control-label">
<span class="checkbox-label-text">{{ option.label }}</span>
<span
v-tooltip="{
delay: {
show: 800,
hide: 100,
},
content: option.label,
autoHide: true,
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''],
placement: 'auto-start'
}"
class="checkbox-label-text">
{{ option.label }}
</span>
<span
v-if="option.total_items != undefined"
class="has-text-gray">&nbsp;{{ "(" + option.total_items + ")" }}</span>
class="facet-item-count has-text-gray">&nbsp;{{ "(" + option.total_items + ")" }}</span>
</span>
</label>
<button
@ -328,12 +341,27 @@
display: flex;
flex-wrap: nowrap;
width: 100%;
align-items: center;
}
.checkbox-label-text {
white-space: nowrap;
white-space: wrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 1.45em;
break-inside: avoid;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.facet-item-count {
margin-left: auto;
}
.b-checkbox:hover .facet-item-count,
.b-checkbox:focus .facet-item-count {
--tainacan-info-color: var(--tainacan-input-color);
}
</style>

View File

@ -14,7 +14,7 @@
:aria-close-label="$i18n.get('remove_value')"
:aria-labelledby="'filter-label-id-' + filter.id"
:class="{'has-selected': selected != undefined && selected != []}"
:placeholder="$i18n.get('info_type_to_add_terms')"
:placeholder="filter.placeholder ? filter.placeholder : $i18n.get('info_type_to_add_terms')"
check-infinite-scroll
@typing="search"
@focus="($event) => { searchQuery = $event.target.value; performSearch(searchQuery) }"

View File

@ -12,6 +12,7 @@ class TaxonomyCheckbox extends Filter_Type {
$this->set_name( __('Taxonomy Checkbox List', 'tainacan') );
$this->set_supported_types(['term']);
$this->set_component('tainacan-filter-taxonomy-checkbox');
$this->set_use_input_placeholder(false);
$this->set_preview_template('
<div>
<div>

View File

@ -41,7 +41,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: activity.title,
@ -61,7 +61,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: activity.user_name,
@ -80,7 +80,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: activity.date,

View File

@ -41,7 +41,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 120,
},
content: capability.display_name,
@ -60,7 +60,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 120,
},
content: capability.description,
@ -82,7 +82,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 120,
},
content: props['complete-roles-list'],

View File

@ -204,7 +204,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: collection.name,
@ -225,7 +225,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: (collection.description != undefined && collection.description != '') ? collection.description : `<span class='has-text-gray is-italic'>` + $i18n.get('label_description_not_provided') + `</span>`,
@ -246,7 +246,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: collection.modification_date,
@ -267,7 +267,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: collection.creation_date,
@ -288,7 +288,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: collection.author_name,
@ -310,7 +310,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: getTotalItemsDetailed(collection.total_items),

View File

@ -203,7 +203,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -534,7 +534,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -663,7 +663,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.description != undefined && item.description != '' ? item.description : `<span class='has-text-gray3 is-italic'>` + $i18n.get('label_description_not_provided') + `</span>`,
@ -678,7 +678,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.author_name != undefined ? item.author_name : '',
@ -694,7 +694,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.creation_date != undefined ? parseDateToNavigatorLanguage(item.creation_date) : '',
@ -775,7 +775,7 @@
v-if="collectionId != undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item.metadata, titleItemMetadatum) : '',
@ -791,7 +791,7 @@
v-if="collectionId == undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -1063,7 +1063,7 @@
column.metadata_type_object.related_mapped_prop == 'title'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined && item.title != '' ? item.title : `<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`,
@ -1079,7 +1079,7 @@
column.metadata_type_object.related_mapped_prop == 'description'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.description != undefined && item.description != '' ? item.description : `<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`,
@ -1100,7 +1100,7 @@
column.metadatum !== 'row_description'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
popperClass: [ 'tainacan-tooltip', 'tooltip', column.metadata_type_object != undefined && column.metadata_type_object.component == 'tainacan-textarea' ? 'metadata-type-textarea' : '', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''],
@ -1127,7 +1127,7 @@
v-if="column.metadatum == 'row_author'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item[column.slug],
@ -1142,7 +1142,7 @@
v-if="column.metadatum == 'row_modification'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: parseDateToNavigatorLanguage(item[column.slug]),
@ -1157,7 +1157,7 @@
v-if="column.metadatum == 'row_creation'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: parseDateToNavigatorLanguage(item[column.slug]),
@ -1317,7 +1317,7 @@
v-if="collectionId != undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item.metadata, titleItemMetadatum) : '',
@ -1333,7 +1333,7 @@
v-if="collectionId == undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -1549,7 +1549,7 @@
v-if="collectionId != undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item.metadata, titleItemMetadatum) : '',
@ -1563,7 +1563,7 @@
v-if="collectionId == undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -1792,7 +1792,7 @@
v-if="collectionId != undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item.metadata, titleItemMetadatum) : '',
@ -1808,7 +1808,7 @@
v-if="collectionId == undefined && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -1979,6 +1979,178 @@
</l-control>
</l-map>
</div>
<!-- MOSAIC VIEW MODE -->
<ul
v-if="viewMode == 'mosaic'"
:class="{
'hide-items-selection': $adminOptions.hideItemsListSelection
}"
class="tainacan-mosaic-container">
<li
v-for="(item, index) of items"
:key="index"
:data-tainacan-item-id="item.id"
:style="{
'--tainacan-mosaic-item-width': getAcceptableWidthBasedOnRatio(item['thumbnail'], 'tainacan-large-full', 300),
'--tainacan-mosaic-item-height': $thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 300)
}">
<div
:class="{
'selected-mosaic-item': getSelectedItemChecked(item.id) == true
}"
class="tainacan-mosaic-item"
@click.left="onClickItem($event, item)"
@click.right="onRightClickItem($event, item)">
<!-- Thumbnail -->
<blur-hash-image
v-if="item.thumbnail != undefined"
class="tainacan-mosaic-item-thumbnail"
:width="$thumbHelper.getWidth(item['thumbnail'], 'tainacan-large-full', 320)"
:height="$thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 320)"
:hash="$thumbHelper.getBlurhashString(item['thumbnail'], 'tainacan-large-full')"
:src="$thumbHelper.getSrc(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:srcset="$thumbHelper.getSrcSet(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:alt="item.thumbnail_alt ? item.thumbnail_alt : $i18n.get('label_thumbnail')"
:transition-duration="500"
/>
<!-- Title -->
<div
class="metadata-title"
:style="{ 'padding-left': collectionId && !$adminOptions.hideItemsListSelection && ($adminOptions.itemsSingleSelectionMode || $adminOptions.itemsMultipleSelectionMode || (collection && collection.current_user_can_bulk_edit)) ? '1.75em' : '1.0em' }">
<!-- Checkbox -->
<!-- TODO: Remove v-if="collectionId" from this element when the bulk edit in repository is done -->
<div
v-if="collectionId && !$adminOptions.hideItemsListSelection && ($adminOptions.itemsSingleSelectionMode || $adminOptions.itemsMultipleSelectionMode || (collection && collection.current_user_can_bulk_edit))"
:class="{ 'is-selecting': isSelectingItems }"
class="mosaic-item-checkbox">
<label
tabindex="0"
:class="(!$adminOptions.itemsSingleSelectionMode ? 'b-checkbox checkbox' : 'b-radio radio') + ' is-small'">
<input
v-if="!$adminOptions.itemsSingleSelectionMode"
type="checkbox"
:checked="getSelectedItemChecked(item.id)"
@input="setSelectedItemChecked(item.id)">
<input
v-else
v-model="singleItemSelection"
type="radio"
name="item-single-selection"
:value="item.id">
<span class="check" />
<span class="control-label" />
<span class="sr-only">{{ $i18n.get('label_select_item') }}</span>
</label>
</div>
<p
v-tooltip="{
delay: {
show: 750,
hide: 100,
},
content: item.title != undefined ? item.title : '',
html: true,
placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}">
<span
v-if="isOnAllItemsTabs && $statusHelper.hasIcon(item.status)"
v-tooltip="{
content: $i18n.get('status_' + item.status),
autoHide: true,
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''],
placement: 'auto-start'
}"
class="icon has-text-gray">
<i
class="tainacan-icon tainacan-icon-1em"
:class="$statusHelper.getIcon(item.status)"
/>
</span>
{{ item.title != undefined ? item.title : '' }}
</p>
</div>
<!-- Actions -->
<div
v-if="item.current_user_can_edit && !$adminOptions.hideItemsListActionAreas"
class="actions-area"
:label="$i18n.get('label_actions')">
<a
v-if="!isOnTrash"
id="button-edit"
:aria-label="$i18n.getFrom('items','edit_item')"
@click.prevent.stop="goToItemEditPage(item)">
<span
v-tooltip="{
content: $i18n.get('edit'),
autoHide: true,
placement: 'auto',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
class="icon">
<i class="has-text-secondary tainacan-icon tainacan-icon-1-25em tainacan-icon-edit" />
</span>
</a>
<a
v-if="isOnTrash"
:aria-lavel="$i18n.get('label_button_untrash')"
@click.prevent.stop="untrashOneItem(item.id)">
<span
v-tooltip="{
content: $i18n.get('label_recover_from_trash'),
autoHide: true,
placement: 'auto',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
class="icon">
<i class="has-text-secondary tainacan-icon tainacan-icon-1-25em tainacan-icon-undo" />
</span>
</a>
<a
v-if="item.current_user_can_delete"
id="button-delete"
:aria-label="$i18n.get('label_button_delete')"
@click.prevent.stop="deleteOneItem(item.id)">
<span
v-tooltip="{
content: isOnTrash ? $i18n.get('label_delete_permanently') : $i18n.get('delete'),
autoHide: true,
placement: 'auto',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
class="icon">
<i
:class="{ 'tainacan-icon-delete': !isOnTrash, 'tainacan-icon-deleteforever': isOnTrash }"
class="has-text-secondary tainacan-icon tainacan-icon-1-25em" />
</span>
</a>
<a
v-if="!isOnTrash"
id="button-open-external"
:aria-label="$i18n.getFrom('items','view_item')"
target="_blank"
:href="item.url"
@click.stop="">
<span
v-tooltip="{
content: $i18n.get('label_item_page_on_website'),
autoHide: true,
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : ''],
placement: 'auto',
html: true
}"
class="icon">
<i class="tainacan-icon tainacan-icon-1-125em tainacan-icon-openurl" />
</span>
</a>
</div>
</div>
</li>
</ul>
</div>
</div>
</template>
@ -2674,6 +2846,12 @@ export default {
duration: 3000
});
}
},
getAcceptableWidthBasedOnRatio(thumbnail, size, defaultSize) {
const width = this.$thumbHelper.getWidth(thumbnail, size, defaultSize);
const height = this.$thumbHelper.getHeight(thumbnail, size, defaultSize);
return (width / height) > 0.7 ? width : ( height * 0.7 );
}
}
}
@ -2685,6 +2863,7 @@ export default {
@import "../../scss/_tables.scss";
@import "../../scss/_view-mode-cards.scss";
@import "../../scss/_view-mode-masonry.scss";
@import "../../scss/_view-mode-mosaic.scss";
@import "../../scss/_view-mode-grid.scss";
@import "../../scss/_view-mode-records.scss";
@import "../../scss/_view-mode-list.scss";

View File

@ -58,7 +58,7 @@
content: getPreviewTemplateContent(metadatum),
html: true,
delay: {
shown: 0,
show: 0,
hide: 100,
},
placement: 'top',
@ -123,7 +123,7 @@
content: $i18n.get('info_create_section'),
html: true,
delay: {
shown: 0,
show: 0,
hide: 100,
},
placement: 'top',

View File

@ -73,7 +73,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: bgProcess.name ? bgProcess.name : $i18n.get('label_unnamed_process'),
@ -91,7 +91,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: bgProcess.progress_label ? bgProcess.progress_label : $i18n.get('label_no_details_of_process'),
@ -111,7 +111,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: getDate(bgProcess.queued_on),
@ -136,7 +136,7 @@
v-if=" bgProcess.status === 'running' "
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_stop_process'),
@ -152,7 +152,7 @@
v-if=" ( bgProcess.status === 'finished' && !bgProcess.error_log ) || bgProcess.status === null"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_process_completed'),
@ -167,7 +167,7 @@
v-if=" bgProcess.status === 'finished-errors' || ( bgProcess.done > 0 && bgProcess.error_log && bgProcess.status === 'finished' ) "
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_process_completed_with_errors'),
@ -182,7 +182,7 @@
v-if=" bgProcess.status === 'cancelled' "
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_process_cancelled'),
@ -197,7 +197,7 @@
v-if=" bgProcess.status === 'paused' "
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_process_paused'),
@ -212,7 +212,7 @@
v-if=" bgProcess.status === 'waiting' "
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_process_waiting'),
@ -227,7 +227,7 @@
v-if=" bgProcess.status === 'waiting' "
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_delete_process'),
@ -243,7 +243,7 @@
v-if="bgProcess.status === 'errored'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_process_failed'),
@ -306,7 +306,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: getDate(bgProcess.processed_last),

View File

@ -93,7 +93,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: relatedItem.title != undefined && relatedItem.title != '' ? relatedItem.title : `<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`,
@ -117,7 +117,7 @@
<span
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('edit'),

View File

@ -131,7 +131,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: taxonomy.name,
@ -150,7 +150,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: taxonomy.description != undefined && taxonomy.description != '' ? taxonomy.description : `<span class='has-text-gray is-italic'>` + $i18n.get('label_description_not_provided') + `</span>`,
@ -169,7 +169,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: (taxonomy.collections != undefined && taxonomy.collections.length != undefined && taxonomy.collections.length > 0) ? renderListOfCollections(taxonomy.collections, taxonomy.metadata_by_collection) : $i18n.get('label_no_collections_using_taxonomy'),
@ -191,7 +191,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: getTotalTermsDetailed(taxonomy.total_terms),

View File

@ -234,7 +234,7 @@
let val = this.valueComponent;
if ((!Array.isArray(val) || val.length == 0) && this.itemMetadatum.metadatum.multiple === 'no') {
tainacanApi.patch(`/item/${this.itemMetadatum.item.id}/metadata/${this.itemMetadatum.metadatum.id}`, {
tainacanApi.put(`/item/${this.itemMetadatum.item.id}/metadata/${this.itemMetadatum.metadatum.id}`, {
values: term.id,
}).then(() => {
this.isAddingNewTermVaue = false;
@ -244,7 +244,7 @@
} else {
val = val ? val : [];
val.push( this.getComponent == ('tainacan-taxonomy-checkbox' || 'tainacan-taxonomy-radio') ? term.id : {'label': term.name, 'value': term.id} );
tainacanApi.patch(`/item/${this.itemMetadatum.item.id}/metadata/${this.itemMetadatum.metadatum.id}`, {
tainacanApi.put(`/item/${this.itemMetadatum.item.id}/metadata/${this.itemMetadatum.metadatum.id}`, {
values: val,
}).then(() => {
this.isAddingNewTermVaue = false;

View File

@ -242,7 +242,7 @@
placement: 'auto',
popperClass: ['tainacan-tooltip', 'tooltip'],
triggers: [],
shown: bulkEditionProcedures[criterion].tooltipShow
show: bulkEditionProcedures[criterion].tooltipShow
}"
class="icon">
<i class="has-text-success tainacan-icon tainacan-icon-1-25em tainacan-icon-approvedcircle" />

View File

@ -41,7 +41,7 @@
<a
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_copy_link_url'),
@ -75,7 +75,7 @@
<a
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_open_externally'),
@ -140,7 +140,7 @@
<label
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: selectedExposer.name + (exposerMapper.name != undefined ? ': ' + exposerMapper.name + ' ' + $i18n.get('label_mapper') : ''),
@ -171,7 +171,7 @@
<a
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_copy_link_url'),
@ -205,7 +205,7 @@
<a
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_open_externally'),

View File

@ -51,7 +51,7 @@
type="submit"
class="button is-success"
@click="onConfirm(); $emit('close');">
{{ $i18n.get('continue') }}
{{ confirmText ? confirmText : $i18n.get('continue') }}
</button>
</footer>
</div>
@ -66,6 +66,10 @@
title: String,
message: String,
icon: String,
confirmText: {
type: String,
default: ''
},
onConfirm: {
type: Function,
default: () => {}

View File

@ -7,7 +7,7 @@
popperClass: ['tainacan-tooltip', 'tooltip', 'tainacan-helper-tooltip', extraClasses],
html: true,
delay: {
shown: 0,
show: 0,
hide: 100,
}
}"
@ -30,8 +30,8 @@ export default {
},
computed: {
getHelperTooltipContent() {
return `<h5>` + this.title + `</h5>
<p>` + ((this.message != '' && this.message != undefined) ? this.message : this.$i18n.get('info_no_description_provided')) + `</p>`;
return ( this.title ? ( `<p style="font-weight: bold; color: var(--tainacan-secondary);">` + this.title + `</p>` ) : '' ) +
`<p style="color: var(--tainacan-info-color);">` + ((this.message != '' && this.message != undefined) ? this.message : this.$i18n.get('info_no_description_provided')) + `</p>`;
}
}
}
@ -39,7 +39,7 @@ export default {
<style lang="scss">
.tainacan-help-tooltip-trigger>a .icon {
.tainacan-help-tooltip-trigger > a .icon {
i, i::before { font-size: 0.875em !important }
}

View File

@ -5,7 +5,7 @@
<v-tooltip
:popper-class="['tainacan-tooltip', 'tooltip', 'tainacan-helper-tooltip', 'is-danger']"
delay="{
shown: 0,
show: 0,
hide: 400,
}">
<a class="help-button has-text-danger">

View File

@ -16,9 +16,9 @@
</h3>
<button
v-if="!isLoadingFilters &&
((filters.length >= 0 &&
isRepositoryLevel) || filters.length > 0)"
v-if="!hideFilterCollapses && !isLoadingFilters && (
( filters.length >= 0 && isRepositoryLevel ) || filters.length > 0
)"
aria-controls="filters-items-list"
:aria-expanded="!collapseAll"
class="link-style collapse-all"
@ -33,7 +33,7 @@
</span>
</button>
<br>
<br v-if="!hideFilterCollapses">
<filters-tags-list
v-if="filtersAsModal && hasFiltered"
@ -56,7 +56,7 @@
v-if="taxonomyFilter.length > 0 && taxonomyFiltersCollectionNames != undefined && taxonomyFiltersCollectionNames[key] != undefined"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_filters_from') + ' ' + taxonomyFiltersCollectionNames[key] + ': ',
@ -86,7 +86,8 @@
:expand-all="!collapseAll"
:is-repository-level="key == 'repository-filters'"
:filters-as-modal="filtersAsModal"
:is-mobile-screen="isMobileScreen" />
:is-mobile-screen="isMobileScreen"
:hide-collapses="hideFilterCollapses" />
</template>
<hr v-if="taxonomyFilter.length > 1">
</div>
@ -99,7 +100,7 @@
v-if="taxonomyFilter.length > 0 && taxonomyFiltersCollectionNames != undefined && taxonomyFiltersCollectionNames[key] != undefined"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_filters_from') + ' ' + taxonomyFiltersCollectionNames[key] + ': ',
@ -129,7 +130,8 @@
:expand-all="!collapseAll"
:is-repository-level="key == 'repository-filters'"
:filters-as-modal="filtersAsModal"
:is-mobile-screen="isMobileScreen" />
:is-mobile-screen="isMobileScreen"
:hide-collapses="hideFilterCollapses" />
</template>
<hr v-if="taxonomyFilter.length > 1">
</div>
@ -146,7 +148,7 @@
v-if="repositoryCollectionFilter.length > 0 && repositoryCollectionNames != undefined && repositoryCollectionNames[key] != undefined"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_filters_from') + ' ' + repositoryCollectionNames[key] + ': ',
@ -176,7 +178,8 @@
:expand-all="!collapseAll"
:is-repository-level="key == 'repository-filters'"
:filters-as-modal="filtersAsModal"
:is-mobile-screen="isMobileScreen" />
:is-mobile-screen="isMobileScreen"
:hide-collapses="hideFilterCollapses" />
</template>
<hr v-if="repositoryCollectionFilters.length > 1">
</div>
@ -189,7 +192,7 @@
v-if="repositoryCollectionFilter.length > 0 && repositoryCollectionNames != undefined && repositoryCollectionNames[key] != undefined"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_filters_from') + ' ' + repositoryCollectionNames[key] + ': ',
@ -219,7 +222,8 @@
:expand-all="!collapseAll"
:is-repository-level="key == 'repository-filters'"
:filters-as-modal="filtersAsModal"
:is-mobile-screen="isMobileScreen" />
:is-mobile-screen="isMobileScreen"
:hide-collapses="hideFilterCollapses" />
</template>
<hr v-if="repositoryCollectionFilters.length > 1">
</div>
@ -237,7 +241,8 @@
:expand-all="!collapseAll"
:is-repository-level="isRepositoryLevel"
:filters-as-modal="filtersAsModal"
:is-mobile-screen="isMobileScreen" />
:is-mobile-screen="isMobileScreen"
:hide-collapses="hideFilterCollapses" />
</template>
</div>
<section
@ -293,7 +298,8 @@
filtersAsModal: Boolean,
hasFiltered: Boolean,
isLoadingItems: Boolean,
isMobileScreen: false
isMobileScreen: false,
hideFilterCollapses: false
},
emits: [
'update-is-loading-items-state'
@ -489,6 +495,12 @@
.filters-components-list {
margin-bottom: 64px;
}
@supports (contain: inline-size) {
.filters-components-list {
container-type: inline-size;
container-name: filterscomponentslist;
}
}
.collection-name {
color: var(--tainacan-heading-color);
font-size: 0.875em;

View File

@ -113,7 +113,8 @@
taxonomy: tag.taxonomy,
metadatumName: this.getMetadatumName(tag),
metadatumId: tag.metadatumId,
argType: tag.argType
argType: tag.argType,
secondaryMetadatumId: tag.secondaryMetadatumId
});
}
} else {
@ -124,7 +125,8 @@
taxonomy: tag.taxonomy,
metadatumName: this.getMetadatumName(tag),
metadatumId: tag.metadatumId,
argType: tag.argType
argType: tag.argType,
secondaryMetadatumId: tag.secondaryMetadatumId
});
}
});
@ -168,7 +170,7 @@
...mapGetters('search',[
'getFilterTags'
]),
removeMetaQuery({ filterId, value, singleLabel, label, taxonomy, metadatumId, metadatumName, argType }) {
removeMetaQuery({ filterId, value, singleLabel, label, taxonomy, metadatumId, metadatumName, argType, secondaryMetadatumId }) {
this.$eventBusSearch.resetPageOnStore();
this.$eventBusSearch.removeMetaFromFilterTag({
filterId: filterId,
@ -178,7 +180,8 @@
taxonomy: taxonomy,
metadatumId: metadatumId,
metadatumName:metadatumName,
argType: argType
argType: argType,
secondaryMetadatumId: secondaryMetadatumId
});
},
clearAllFilters() {

View File

@ -39,7 +39,8 @@ export default {
label: filterTag.singleLabel ? filterTag.singleLabel : filterTag.label,
isMultiValue: filterTag.singleLabel ? false : true,
taxonomy: filterTag.taxonomy,
value: filterTag.value
value: filterTag.value,
secondaryMetadatumId: filterTag.secondaryMetadatumId
});
} else {
app.config.globalProperties.$store.dispatch('search/removeMetaQuery', {
@ -47,7 +48,8 @@ export default {
label: filterTag.singleLabel ? filterTag.singleLabel : filterTag.label,
isMultiValue: filterTag.singleLabel ? false : true,
metadatum_id: filterTag.metadatumId,
value: filterTag.value
value: filterTag.value,
secondaryMetadatumId: filterTag.secondaryMetadatumId
});
}
} else {

View File

@ -46,7 +46,7 @@ export const fetchProcesses = ({ commit }, {page, processesPerPage, shouldUpdate
export const updateProcess = ({ commit }, { id, status }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch(`/bg-processes/${id}/`, {
axios.tainacanApi.put(`/bg-processes/${id}/`, {
status: status,
})
.then( res => {

View File

@ -3,7 +3,7 @@ import axios from '../../../axios'
// ROLES
export const addCapabilityToRole = ({ commit }, { capabilityKey, role }) => {
return new Promise(( resolve, reject ) => {
axios.tainacanApi.patch('/roles/' + role + '?add_cap=' + capabilityKey)
axios.tainacanApi.put('/roles/' + role + '?add_cap=' + capabilityKey)
.then( res => {
let role = res.data;
commit('addCapabilityToRole', {capabilityKey, role });
@ -17,7 +17,7 @@ export const addCapabilityToRole = ({ commit }, { capabilityKey, role }) => {
export const removeCapabilityFromRole = ({ commit }, { capabilityKey, role }) => {
return new Promise(( resolve, reject ) => {
axios.tainacanApi.patch('/roles/' + role + '?remove_cap=' + capabilityKey)
axios.tainacanApi.put('/roles/' + role + '?remove_cap=' + capabilityKey)
.then( res => {
let role = res.data;
commit('removeCapabilityFromRole', {capabilityKey, role });
@ -79,7 +79,7 @@ export const updateRole = ({ commit }, role) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/roles/' + role.slug, role)
axios.tainacanApi.put('/roles/' + role.slug, role)
.then(res => {
const updatedRole = res.data
commit('setRole', updatedRole);

View File

@ -348,7 +348,7 @@ export const updateCollection = ({ commit }, {
collection
}) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/collections/' + collection_id + '?context=edit', collection).then( res => {
axios.tainacanApi.put('/collections/' + collection_id + '?context=edit', collection).then( res => {
commit('setCollection', collection);
resolve( res.data );
}).catch( error => {
@ -416,7 +416,7 @@ export const fetchAttachments = ({ commit }, collection_id) => {
export const updateThumbnail = ({ commit }, { collectionId, thumbnailId }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/collections/' + collectionId + '?context=edit', {
axios.tainacanApi.put('/collections/' + collectionId + '?context=edit', {
_thumbnail_id: thumbnailId
}).then( res => {
let collection = res.data
@ -431,7 +431,7 @@ export const updateThumbnail = ({ commit }, { collectionId, thumbnailId }) => {
export const updateHeaderImage = ({ commit }, { collectionId, headerImageId }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/collections/' + collectionId + '?context=edit', {
axios.tainacanApi.put('/collections/' + collectionId + '?context=edit', {
header_image_id: headerImageId + ''
}).then( res => {
let collection = res.data

View File

@ -27,7 +27,7 @@ export const createExporterSession = ({commit}, slug) => {
export const updateExporterSession = ({commit}, exporterSessionUpdated) => {
return new Promise(( resolve, reject ) => {
tainacanApi.patch(`/exporters/session/${exporterSessionUpdated.id}`, exporterSessionUpdated.body)
tainacanApi.put(`/exporters/session/${exporterSessionUpdated.id}`, exporterSessionUpdated.body)
.then(response => {
commit('setExporterSession');
resolve( response.data );

View File

@ -128,7 +128,7 @@ export const addTemporaryFilter = ({ commit }, filter ) => {
export const updateCollectionFiltersOrder = ({ commit }, { collectionId, filtersOrder }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/collections/' + collectionId + '/filters_order?context=edit', {
axios.tainacanApi.put('/collections/' + collectionId + '/filters_order?context=edit', {
filters_order: filtersOrder
}).then( res => {
commit('collection/setCollection', res.data, { root: true });

View File

@ -8,7 +8,7 @@ export const updateItemMetadatum = ({ commit }, { item_id, metadatum_id, values,
body['parent_meta_id'] = parent_meta_id;
return new Promise((resolve, reject) => {
axios.tainacanApi.patch(`/item/${item_id}/metadata/${metadatum_id}`, body)
axios.tainacanApi.put(`/item/${item_id}/metadata/${metadatum_id}`, body)
.then( res => {
let itemMetadatum = res.data;
commit('setSingleMetadatum', itemMetadatum);
@ -45,7 +45,7 @@ export const fetchItemMetadata = ({ commit }, item_id) => {
export const fetchCompoundFirstParentMetaId = ({ commit }, { item_id, metadatum_id }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch(`/item/${item_id}/metadata/${metadatum_id}`, { value: [] })
axios.tainacanApi.put(`/item/${item_id}/metadata/${metadatum_id}`, { value: [] })
.then( res => {
const parentMetaId = res.data.parent_meta_id;
resolve(parentMetaId);
@ -157,7 +157,7 @@ export const sendItem = ( { commit }, item) => {
export const updateItem = ({ commit }, item) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/items/' + item.id, item)
axios.tainacanApi.put('/items/' + item.id, item)
.then( res => {
commit('setItem', res.data);
commit('setLastUpdated');
@ -191,7 +191,7 @@ export const updateItemDocument = ({ commit }, { item_id, document, document_typ
params['document_options'] = document_options;
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/items/' + item_id, params).then( res => {
axios.tainacanApi.put('/items/' + item_id, params).then( res => {
let item = res.data;
commit('setItem', item);
@ -246,7 +246,7 @@ export const sendAttachment = ( { commit }, { item_id, file }) => {
export const removeAttachmentFromItem = ( { commit }, attachmentId) => {
commit('cleanAttachment');
return new Promise(( resolve, reject ) => {
axios.wpApi.patch('/media/' + attachmentId, {
axios.wpApi.put('/media/' + attachmentId, {
post: 0
})
.then( res => {
@ -306,7 +306,7 @@ export const fetchAttachments = ({ commit }, { page, attachmentsPerPage, itemId,
export const updateThumbnail = ({ commit }, { itemId, thumbnailId, thumbnailAlt }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/items/' + itemId, {
axios.tainacanApi.put('/items/' + itemId, {
_thumbnail_id: thumbnailId
}).then( res => {
let item = res.data
@ -322,7 +322,7 @@ export const updateThumbnail = ({ commit }, { itemId, thumbnailId, thumbnailAlt
export const updateThumbnailAlt = ({ commit }, { thumbnailId, thumbnailAlt }) => {
return new Promise((resolve, reject) => {
axios.wpApi.patch('/media/' + thumbnailId + '?force=true', {
axios.wpApi.put('/media/' + thumbnailId + '?force=true', {
alt_text: thumbnailAlt
}).then( res => {
let thumbnail = res.data;

View File

@ -202,7 +202,7 @@ export const cleanMetadata = ({commit}) => {
export const updateCollectionMetadataOrder = ({ commit }, { collectionId, metadataOrder, metadataSectionId }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/collections/' + collectionId + '/metadata_section/' + metadataSectionId + '/metadata_order?context=edit', {
axios.tainacanApi.put('/collections/' + collectionId + '/metadata_section/' + metadataSectionId + '/metadata_order?context=edit', {
metadata_order: metadataOrder
}).then(res => {
commit('collection/setCollection', res.data, { root: true });
@ -390,7 +390,7 @@ export const updateMetadataSections = ({commit}, metadataSections) => {
export const updateCollectionMetadataSectionsOrder = ({ commit }, {collectionId, metadataSectionsOrder }) => {
return new Promise((resolve, reject) => {
axios.tainacanApi.patch('/collections/' + collectionId + '/metadata_section_order?context=edit', {
axios.tainacanApi.put('/collections/' + collectionId + '/metadata_section_order?context=edit', {
metadata_section_order: metadataSectionsOrder
}).then(res => {
commit('collection/setCollection', res.data, { root: true });

View File

@ -21,52 +21,39 @@ export const setAdvancedSearchQuery = (state, advancedSearchQuery) => {
export const addMetaQuery = ( state, filter ) => {
state.postquery.metaquery = ( ! state.postquery.metaquery || state.postquery.metaquery.length == undefined ) ? [] : state.postquery.metaquery;
let metaquery = {
key: filter.metadatum_id,
value: filter.value
}
if ( filter.compare )
metaquery.compare = filter.compare;
if ( filter.type )
metaquery.type = filter.type;
if ( filter.secondary )
metaquery.secondary = filter.secondary;
let index = state.postquery.metaquery.findIndex( item => item.key === filter.metadatum_id);
if ( index >= 0 ) {
Object.assign(
state.postquery.metaquery,
{
[index]: {
key: filter.metadatum_id,
value: filter.value,
compare: filter.compare,
type: filter.type
}
}
);
} else {
state.postquery.metaquery.push({
key: filter.metadatum_id,
value: filter.value,
compare: filter.compare,
type: filter.type
});
}
if ( index >= 0 )
Object.assign( state.postquery.metaquery, { [index]: metaquery } );
else
state.postquery.metaquery.push(metaquery);
};
export const addTaxQuery = ( state, filter ) => {
state.postquery.taxquery = ( ! state.postquery.taxquery || state.postquery.taxquery.length == undefined ) ? [] : state.postquery.taxquery;
let index = state.postquery.taxquery.findIndex( item => item.taxonomy === filter.taxonomy);
let taxquery = {
taxonomy: filter.taxonomy,
terms: filter.terms
}
if ( filter.compare )
taxquery.compare = filter.compare;
if ( index >= 0 ) {
Object.assign(
state.postquery.taxquery,
{
[index]: {
taxonomy: filter.taxonomy,
terms: filter.terms,
compare: filter.compare
}
}
);
} else {
state.postquery.taxquery.push({
taxonomy: filter.taxonomy,
terms: filter.terms,
compare: filter.compare
});
}
let index = state.postquery.taxquery.findIndex( item => item.taxonomy === filter.taxonomy);
if ( index >= 0 )
Object.assign( state.postquery.taxquery, { [index]: taxquery } );
else
state.postquery.taxquery.push(taxquery);
};
export const addFetchOnly = ( state, metadatum ) => {
@ -104,13 +91,27 @@ export const removeMetaQuery = ( state, filter ) => {
let index = state.postquery.metaquery.findIndex( item => item.key == filter.metadatum_id);
if (index >= 0) {
if ( index >= 0 ) {
if (!filter.isMultiValue && Array.isArray(state.postquery.metaquery[index].value) && state.postquery.metaquery[index].value.length > 1) {
let otherIndex = state.postquery.metaquery[index].value.findIndex(item => item == filter.value);
if (otherIndex >= 0)
if ( otherIndex >= 0 )
state.postquery.metaquery[index].value.splice(otherIndex, 1)
} else
state.postquery.metaquery.splice(index, 1);
// Handles removing metaqueries from secondary filter metadata
if ( filter.secondaryMetadatumId ) {
let secondaryIndex = state.postquery.metaquery.findIndex( item => item.key == filter.secondaryMetadatumId);
if ( secondaryIndex >= 0 ) {
if ( !filter.isMultiValue && Array.isArray(state.postquery.metaquery[secondaryIndex].value) && state.postquery.metaquery[secondaryIndex].value.length > 1 ) {
let otherSecondaryIndex = state.postquery.metaquery[secondaryIndex].value.findIndex(item => item == filter.value);
if ( otherSecondaryIndex >= 0 )
state.postquery.metaquery[secondaryIndex].value.splice(otherSecondaryIndex, 1)
} else
state.postquery.metaquery.splice(secondaryIndex, 1);
}
}
}
};
@ -201,7 +202,8 @@ export const setFilterTags = ( state, filterArguments ) => {
) ? aFilterArgument.metadatum.metadata_type_object.options.taxonomy : '',
argType: aFilterArgument.arg_type ? aFilterArgument.arg_type : '',
metadatumId: (aFilterArgument.filter && aFilterArgument.metadatum.metadatum_id) ? aFilterArgument.metadatum.metadatum_id : (aFilterArgument.metadatum.id || ''),
metadatumName: (aFilterArgument.filter && aFilterArgument.filter.name) ? aFilterArgument.filter.name : (aFilterArgument.metadatum.name || '')
metadatumName: (aFilterArgument.filter && aFilterArgument.filter.name) ? aFilterArgument.filter.name : (aFilterArgument.metadatum.name || ''),
secondaryMetadatumId: (aFilterArgument.filter && aFilterArgument.filter.filter_type_options && aFilterArgument.filter.filter_type_options.secondary_filter_metadatum_id) ? aFilterArgument.filter.filter_type_options.secondary_filter_metadatum_id : '',
}
});
state.filter_tags = filterTags;

View File

@ -33,7 +33,7 @@ export const deleteTaxonomy = ({ commit }, { taxonomyId, isPermanently }) => {
export const updateTaxonomy = ({ commit }, taxonomy) => {
return new Promise(( resolve, reject ) => {
axios.tainacanApi.patch(`/taxonomies/${taxonomy.taxonomyId}`, taxonomy)
axios.tainacanApi.put(`/taxonomies/${taxonomy.taxonomyId}`, taxonomy)
.then( res => {
let taxonomy = res.data;
commit('setTaxonomy', taxonomy);
@ -191,7 +191,7 @@ export const updateTerm = ({}, { taxonomyId, term, itemId, metadatumId }) => {
term['metadatum_id'] = metadatumId;
return new Promise(( resolve, reject ) => {
axios.tainacanApi.patch(`/taxonomy/${taxonomyId}/terms/${term.id}`, term)
axios.tainacanApi.put(`/taxonomy/${taxonomyId}/terms/${term.id}`, term)
.then( res => {
const updatedTerm = res.data;
resolve( updatedTerm );
@ -254,7 +254,7 @@ export const changeTermsParent = ({}, { taxonomyId, newParentTerm, terms, parent
query += `&include=${terms}`;
return new Promise(( resolve, reject ) => {
axios.tainacanApi.patch(`/taxonomy/${taxonomyId}/terms/newparent/${newParentTerm}?${query}`)
axios.tainacanApi.put(`/taxonomy/${taxonomyId}/terms/newparent/${newParentTerm}?${query}`)
.then(res => {
const terms = res.data;
resolve( terms );

View File

@ -976,7 +976,7 @@ export default {
}
&:not(.available-metadata-area){
margin-right: var(--tainacan-one-column);
margin-right: 30px;
flex-grow: 2;
@media screen and (max-width: 769px) {

View File

@ -33,7 +33,7 @@
id="filter-menu-compress-button"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: !isFiltersModalActive ? $i18n.get('label_show_filters') : $i18n.get('label_hide_filters'),
@ -223,16 +223,16 @@
ref="displayedMetadataDropdown"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: (totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry') ? (adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry') ? $i18n.get('info_current_view_mode_metadata_not_allowed') : $i18n.get('info_cant_select_metadata_without_items') : '',
content: (totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry' || adminViewMode == 'mosaic') ? (adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry' || adminViewMode == 'mosaic') ? $i18n.get('info_current_view_mode_metadata_not_allowed') : $i18n.get('info_cant_select_metadata_without_items') : '',
autoHide: false,
placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip', isRepositoryLevel ? 'tainacan-repository-tooltip' : '']
}"
:mobile-modal="true"
:disabled="totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry'"
:disabled="totalItems <= 0 || adminViewMode == 'grid'|| adminViewMode == 'cards' || adminViewMode == 'masonry' || adminViewMode == 'mosaic'"
class="show metadata-options-dropdown"
aria-role="list"
trap-focus>
@ -383,12 +383,15 @@
<span class="view-mode-icon icon is-small gray-icon">
<i
v-if="adminViewMode !== 'map'"
:class="{'tainacan-icon-viewtable' : ( adminViewMode == 'table' || adminViewMode == undefined),
:class="{
'tainacan-icon-viewtable' : ( adminViewMode == 'table' || adminViewMode == undefined),
'tainacan-icon-viewcards' : adminViewMode == 'cards',
'tainacan-icon-viewminiature' : adminViewMode == 'grid',
'tainacan-icon-viewrecords' : adminViewMode == 'records',
'tainacan-icon-viewlist' : adminViewMode == 'list',
'tainacan-icon-viewmasonry' : adminViewMode == 'masonry' }"
'tainacan-icon-viewmasonry' : adminViewMode == 'masonry' || adminViewMode == 'mosaic',
'tainacan-icon-rotate-90' : adminViewMode == 'mosaic'
}"
class="tainacan-icon tainacan-icon-1-25em" />
<svg
v-else
@ -426,6 +429,17 @@
</span>
<span>{{ $i18n.get('label_cards') }}</span>
</b-dropdown-item>
<b-dropdown-item
aria-controls="items-list-results"
role="button"
:class="{ 'is-active': adminViewMode == 'mosaic' }"
:value="'mosaic'"
aria-role="listitem">
<span class="icon gray-icon">
<i class="tainacan-icon tainacan-icon-viewmasonry tainacan-icon-rotate-90" />
</span>
<span>{{ $i18n.get('label_mosaic') }}</span>
</b-dropdown-item>
<b-dropdown-item
v-if="!collection || (collection && collection.hide_items_thumbnail_on_lists != 'yes')"
aria-controls="items-list-results"
@ -773,7 +787,7 @@
},
adminViewMode() {
const currentAdminViewMode = this.getAdminViewMode();
return ['table', 'cards', 'records', 'grid', 'masonry', 'list', 'map'].indexOf(currentAdminViewMode) >= 0 ? currentAdminViewMode : 'table';
return ['table', 'cards', 'records', 'grid', 'masonry', 'list', 'map', 'mosaic'].indexOf(currentAdminViewMode) >= 0 ? currentAdminViewMode : 'table';
},
orderByName() {
const metadatumName = this.$orderByHelper.getOrderByMetadatumName({
@ -994,6 +1008,7 @@
existingViewMode == 'list' ||
existingViewMode == 'grid' ||
existingViewMode == 'masonry'||
existingViewMode == 'mosaic'||
existingViewMode == 'map')
this.$eventBusSearch.setInitialAdminViewMode(this.$userPrefs.get(prefsAdminViewMode));
else

View File

@ -74,6 +74,9 @@
// Filter Menu Width when not a modal
--tainacan-filter-menu-width-theme: #{$filter-menu-width-theme};
// Each filter width when in horizontal panel
--tainacan-filters-inline-width: #{$filters-inline-width};
// One column is 1/24% and it is used a lot in the horizontal space distribution
--tainacan-one-column: #{$one-column};

View File

@ -36,9 +36,15 @@
}
}
&.horizontal-filters {
.modal-content {
padding: calc(var(--tainacan-container-padding) - 12px) var(--tainacan-one-column) var(--tainacan-container-padding) var(--tainacan-one-column);
}
}
@media screen and (max-width: 768px) {
&:not(.filters-menu-modal) {
&.modal:not(.filters-menu-modal) {
.modal-content {
background-color: var(--tainacan-background-color);
margin: 0 12% 0 0;

View File

@ -316,7 +316,8 @@
.help-tooltip-header {
padding: 0.8em 0.8em 0em 0.8em;
h5 {
h5,
p {
font-size: 0.875em !important;
font-weight: bold;
color: var(--tainacan-blue5);
@ -335,7 +336,7 @@
overflow: visible !important;
max-height: 100% !important;
line-height: normal;
color: var(--tainacan-blue5);
color: var(--tainacan-info-color);
}
}
}

View File

@ -145,6 +145,7 @@ $sidebar-width: 3.0em;
$subheader-height: 2.35em;
$filter-menu-width: 16.666666667%;
$filter-menu-width-theme: 20%;
$filters-inline-width: 272px;
$page-height: calc(100vh - 5.35em);
// Overall Pages padding:

View File

@ -140,7 +140,7 @@
.metadata-title {
flex-shrink: 0;
padding: 0.6em 7em 0.5em 2.75em;
min-height: 40px;
min-height: 1.5em;
position: relative;
font-size: 1em !important;
text-overflow: ellipsis;
@ -187,6 +187,11 @@
display: flex;
margin: 0 !important;
&:hover,
&:focus {
background: var(--tainacan-item-hover-background-color);
}
.list-metadata {
padding: 0.75em 1.375em;
flex: 1;

View File

@ -27,6 +27,7 @@
:deep(img) {
height: auto;
max-width: 100%;
}
&:hover {
@ -111,7 +112,7 @@
.metadata-title {
flex-shrink: 0;
padding: 0.5em 7em 0.5em 2.75em;
min-height: 40px;
min-height: 1.5em;
position: relative;
font-size: 1em !important;
text-overflow: ellipsis;

View File

@ -264,6 +264,7 @@
z-index: 1;
}
.metadata-title {
box-sizing: border-box;
flex-shrink: 0;
padding: 0.25em 1.125em;
font-size: 1.0em !important;

View File

@ -110,6 +110,7 @@
:deep(img) {
height: auto;
max-width: 100%;
}
&:hover:not(.skeleton) {
@ -191,7 +192,7 @@
flex-shrink: 0;
margin: 0px 6px 0px 24px;
padding: 8px 1em;
min-height: 41px;
min-height: calc(1em + 8px);
cursor: pointer;
position: relative;

View File

@ -0,0 +1,223 @@
.tainacan-mosaic-container,
.tainacan-mosaic-container--skeleton {
--tainacan-mosaic-view-mode-gap: 12px;
--tainacan-mosaic-view-mode-min-height: 180px;
display: flex;
flex-wrap: wrap;
grid-gap: calc(2em + 18px) var(--tainacan-mosaic-view-mode-gap, 12px);
list-style: none;
height: auto;
min-height: 0px; /* While most view modes set this to 50vh, this causes a bug for this one if there are few items to display */
margin: 0;
margin-bottom: calc(2em + 18px + var(--tainacan-container-padding));
padding: 0;
list-style: none;
animation-name: appear;
animation-duration: 0.5s;
&::after {
content: " ";
flex-grow: 1000000000;
}
& > li,
& > .skeleton {
flex-grow: calc(var(--tainacan-mosaic-item-width, 300) * (100000 / var(--tainacan-mosaic-item-height, 300)));
flex-basis: calc(var(--tainacan-mosaic-view-mode-min-height, 180) * (var(--tainacan-mosaic-item-width, 300) / var(--tainacan-mosaic-item-height, 300)));
aspect-ratio: var(--tainacan-mosaic-item-width, 300) / var(--tainacan-mosaic-item-height, 300);
position: relative;
margin: 0 !important;
padding: 0 !important;
:deep(img) {
position: absolute;
width: auto;
height: 100%;
}
:deep(canvas.child) {
aspect-ratio: var(--tainacan-mosaic-item-width, 300) / var(--tainacan-mosaic-item-height, 300);
}
&:hover:not(.skeleton) {
background-color: var(--tainacan-item-heading-hover-background-color);
}
&.selected-mosaic-item:not(.skeleton) {
background-color: var(--tainacan-turquoise1);
}
&:not(.skeleton) {
background-color: var(--tainacan-item-background-color);
}
.tainacan-mosaic-item {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.mosaic-item-checkbox {
position: absolute;
margin-top: -2px;
margin-left: -1em;
z-index: 9;
}
.actions-area {
position: relative;
float: right;
width: 100%;
height: 0px;
display: flex;
justify-content: flex-end;
visibility: hidden;
overflow: hidden;
opacity: 0.0;
padding: 8px;
margin-top: -25px;
background-color: var(--tainacan-item-heading-hover-background-color);
transition: visibility 0.2s ease, opacity 0.3s ease, height 0.2s ease, margin-top 0.1s ease;
a {
margin-left: 8px;
opacity: 0;
transition: opacity 0.3s ease-in;
}
}
&:hover,
&:focus,
&:focus-within,
&:focus-visible,
& > a:focus,
& > a:focus-within,
& > a:focus-visible {
background: var(--tainacan-item-hover-background-color);
.actions-area {
visibility: visible;
opacity: 1.0;
height: 42px;
margin-top: 0px;
background-color: var(--tainacan-item-heading-hover-background-color);
a {
opacity: 1;
}
}
}
& > .selected-mosaic-item {
background-color: var(--tainacan-turquoise1);
.actions-area,
.metadata-title {
background-color: var(--tainacan-turquoise1) !important;
}
}
.tainacan-mosaic-item-thumbnail {
background-size: cover;
background-color: var(--tainacan-item-background-color);
background-blend-mode: multiply;
border-radius: 0px;
min-width: 100%;
display: flex;
justify-content: center;
&:hover {
cursor: pointer;
}
}
.metadata-title {
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
margin: 0;
padding: 8px 1em;
min-height: calc(1em + 8px);
width: 100%;
cursor: pointer;
position: absolute;
top: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
p {
font-size: 0.875em !important;
color: var(--tainacan-heading-color) !important;
text-align: left !important;
margin-bottom: 0 !important;
line-height: 1.5em;
word-break: break-word;
margin: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.slideshow-icon {
color: var(--tainacan-info-color);
position: absolute;
right: 5px;
top: 8px;
transform: scale(0.0);
transition: transform 0.2s ease;
}
.icon:not(.slideshow-icon) {
float: left;
margin-top: -1px;
}
}
&:hover,
&:focus,
&:focus-within,
&:focus-visible,
& > a:focus,
& > a:focus-within,
& > a:focus-visible {
.metadata-title {
background: var(--tainacan-item-heading-hover-background-color);
.slideshow-icon {
transform: scale(1.0);
}
}
}
}
&.hide-items-selection {
.tainacan-mosaic-item {
&:hover { background-color: transparent; cursor: default; }
&:hover .tainacan-mosaic-item-thumbnail { cursor: default; }
}
}
&.has-title-inside {
gap: var(--tainacan-mosaic-view-mode-gap, 12px);
& > li {
.metadata-title {
top: unset;
bottom: 0;
white-space: wrap;
overflow: visible;
text-overflow: none;
p {
white-space: wrap;
overflow: visible;
text-overflow: none;
}
}
&:not(:hover):not(:focus):not(:focus-within):not(:focus-visible),
& > a:not(:hover):not(:focus):not(:focus-within):not(:focus-visible) {
.metadata-title {
display: none;
}
}
}
}
}

View File

@ -75,6 +75,7 @@
:deep(img) {
height: auto;
max-width: 100%;
}
&:hover {
@ -150,7 +151,7 @@
flex-shrink: 0;
padding: 0.5em 7em 0.5em 2.75em;
font-size: 1.0em !important;
min-height: 40px;
min-height: 1.5em;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;

View File

@ -354,7 +354,8 @@ class Admin {
'wp_elasticpress' => \Tainacan\Elastic_Press::get_instance()->is_active(),
'item_submission_captcha_site_key' => get_option("tnc_option_recaptch_site_key"),
'tainacan_enable_core_metadata_on_advanced_search' => ( !defined('TAINACAN_DISABLE_CORE_METADATA_ON_ADVANCED_SEARCH') || false === TAINACAN_DISABLE_CORE_METADATA_ON_ADVANCED_SEARCH ),
'tainacan_enable_relationship_metaquery' => ( defined('TAINACAN_ENABLE_RELATIONSHIP_METAQUERY') && true === TAINACAN_ENABLE_RELATIONSHIP_METAQUERY )
'tainacan_enable_relationship_metaquery' => ( defined('TAINACAN_ENABLE_RELATIONSHIP_METAQUERY') && true === TAINACAN_ENABLE_RELATIONSHIP_METAQUERY ),
'has_permalinks_structure' => get_option('permalink_structure') !== ''
];
$maps = [

View File

@ -28,7 +28,7 @@ export default (element) => {
let registeredViewModes =
( tainacan_blocks && tainacan_blocks.registered_view_modes && tainacan_blocks.registered_view_modes.length ) ?
tainacan_blocks.registered_view_modes :
[ 'table', 'cards', 'records', 'masonry', 'list', 'map' ];
[ 'table', 'cards', 'records', 'masonry', 'mosaic', 'list', 'map'];
// At first, we consider that all registered view modes are included.
let possibleViewModes = registeredViewModes.filter((aViewMode) => aViewMode === 'slideshow');

View File

@ -212,6 +212,22 @@
"collectionOrderByType": {
"type": "string",
"default": ""
},
"shouldNotHideFiltersOnMobile": {
"type": "boolean",
"default": false
},
"displayFiltersHorizontally": {
"type": "boolean",
"default": false
},
"hideFilterCollapses": {
"type": "boolean",
"default": false
},
"filtersInlineWidth": {
"type": "number",
"default": 272
}
},
"supports": {

View File

@ -1,6 +1,327 @@
const { useBlockProps } = wp.blockEditor;
export default [
/* Deprecated due to the creation of horizontal questions */
{
"attributes": {
"termId": {
"type": "string",
"default": null
},
"taxonomyId": {
"type": "string",
"default": null
},
"collectionId": {
"type": "string",
"default": null
},
"defaultViewMode": {
"type": "string",
"default": "masonry"
},
"enabledViewModes": {
"type": "array",
"default": []
},
"collectionDefaultViewMode": {
"type": "string",
"default": "masonry"
},
"collectionEnabledViewModes": {
"type": "array",
"default": []
},
"hideFilters": {
"type": "boolean",
"default": false
},
"hideHideFiltersButton": {
"type": "boolean",
"default": false
},
"hideSearch": {
"type": "boolean",
"default": false
},
"hideAdvancedSearch": {
"type": "boolean",
"default": false
},
"hideDisplayedMetadataButton": {
"type": "boolean",
"default": false
},
"hideSortingArea": {
"type": "boolean",
"default": false
},
"hideSortByButton": {
"type": "boolean",
"default": false
},
"hideItemsThumbnail": {
"type": "boolean",
"default": false
},
"hideExposersButton": {
"type": "boolean",
"default": false
},
"hideItemsPerPageButton": {
"type": "boolean",
"default": false
},
"defaultItemsPerPage": {
"type": "number",
"default": 12
},
"hideGoToPageButton": {
"type": "boolean",
"default": false
},
"hidePaginationArea": {
"type": "boolean",
"default": false
},
"showFiltersButtonInsideSearchControl": {
"type": "boolean",
"default": false
},
"startWithFiltersHidden": {
"type": "boolean",
"default": false
},
"filtersAsModal": {
"type": "boolean",
"default": false
},
"showInlineViewModeOptions": {
"type": "boolean",
"default": false
},
"showFullscreenWithViewModes": {
"type": "boolean",
"default": false
},
"listType": {
"type": "string",
"default": ""
},
"isCollectionModalOpen": {
"type": "boolean",
"default": false
},
"isTermModalOpen": {
"type": "boolean",
"default": false
},
"backgroundColor": {
"type": "string",
"default": "#ffffff"
},
"baseFontSize": {
"type": "number",
"default": 16
},
"filtersAreaWidth": {
"type": "number",
"default": 20
},
"inputColor": {
"type": "string",
"default": "#1d1d1d"
},
"inputBackgroundColor": {
"type": "string",
"default": "#ffffff"
},
"inputBorderColor": {
"type": "string",
"default": "#dbdbdb"
},
"labelColor": {
"type": "string",
"default": "#373839"
},
"infoColor": {
"type": "string",
"default": "#505253"
},
"headingColor": {
"type": "string",
"default": "#000000"
},
"skeletonColor": {
"type": "string",
"default": "#eeeeee"
},
"itemBackgroundColor": {
"type": "string",
"default": "#ffffff"
},
"itemHoverBackgroundColor": {
"type": "string",
"default": "#f2f2f2"
},
"itemHeadingHoverBackgroundColor": {
"type": "string",
"default": "#dbdbdb"
},
"primaryColor": {
"type": "string",
"default": "#d9eced"
},
"secondaryColor": {
"type": "string",
"default": "#187181"
},
"order": {
"type": "string",
"default": "ASC"
},
"orderBy": {
"type": "string",
"default": "date"
},
"orderByMeta": {
"type": "string",
"default": ""
},
"orderByType": {
"type": "string",
"default": ""
},
"collectionOrderBy": {
"type": "string",
"default": "date"
},
"collectionOrderByMeta": {
"type": "string",
"default": ""
},
"collectionOrderByType": {
"type": "string",
"default": ""
}
},
save: function({ attributes }) {
const {
termId,
taxonomyId,
collectionId,
defaultViewMode,
enabledViewModes,
collectionDefaultViewMode,
collectionEnabledViewModes,
hideDisplayedMetadataButton,
hideSortingArea,
hideFilters,
hideHideFiltersButton,
hideSearch,
hideAdvancedSearch,
hideSortByButton,
hideItemsThumbnail,
hidePaginationArea,
hideExposersButton,
hideItemsPerPageButton,
defaultItemsPerPage,
hideGoToPageButton,
showFiltersButtonInsideSearchControl,
startWithFiltersHidden,
filtersAsModal,
showInlineViewModeOptions,
showFullscreenWithViewModes,
listType,
backgroundColor,
baseFontSize,
filtersAreaWidth,
inputColor,
inputBackgroundColor,
inputBorderColor,
labelColor,
infoColor,
headingColor,
skeletonColor,
itemBackgroundColor,
itemHoverBackgroundColor,
itemHeadingHoverBackgroundColor,
primaryColor,
secondaryColor,
order,
orderBy,
orderByMeta,
orderByType,
collectionOrderBy,
collectionOrderByMeta,
collectionOrderByType
} = attributes;
let updatedListType = '' + listType;
if (updatedListType === '' && collectionId)
updatedListType = 'collection';
else if (updatedListType === '' && termId && taxonomyId)
updatedListType = 'term';
// Gets attributes such as style, that are automatically added by the editor hook
const blockProps = useBlockProps.save();
return <div
style={{
'font-size': baseFontSize + 'px',
'--tainacan-base-font-size': baseFontSize + 'px',
'--tainacan-background-color': backgroundColor,
'--tainacan-filter-menu-width-theme': filtersAreaWidth + '%',
'--tainacan-input-color': inputColor,
'--tainacan-input-background-color': inputBackgroundColor,
'--tainacan-input-border-color': inputBorderColor,
'--tainacan-label-color': labelColor,
'--tainacan-info-color': infoColor,
'--tainacan-heading-color': headingColor,
'--tainacan-skeleton-color': skeletonColor,
'--tainacan-item-background-color': itemBackgroundColor,
'--tainacan-item-hover-background-color': itemHoverBackgroundColor,
'--tainacan-item-heading-hover-background-color': itemHeadingHoverBackgroundColor,
'--tainacan-primary': primaryColor,
'--tainacan-secondary': secondaryColor
}}
{ ...blockProps }>
<main
id="tainacan-items-page"
data-module="faceted-search"
data-term-id={ updatedListType == 'term' ? termId : null }
data-taxonomy={ updatedListType == 'term' ? 'tnc_tax_' + taxonomyId : null }
data-collection-id={ updatedListType == 'collection' ? collectionId : null }
data-default-view-mode={ defaultViewMode != 'none' ? defaultViewMode : (updatedListType == 'collection' ? collectionDefaultViewMode : (hideItemsThumbnail ? 'table' : 'masonry') ) }
data-is-forced-view-mode={ defaultViewMode == 'none' ? 'true' : 'false' }
data-enabled-view-modes={ enabledViewModes ? enabledViewModes.toString() : '' }
data-hide-filters = { hideFilters.toString() }
data-hide-hide-filters-button= { hideHideFiltersButton.toString() }
data-hide-search = { hideSearch.toString() }
data-hide-advanced-search = { hideAdvancedSearch.toString() }
data-hide-displayed-metadata-button = { hideDisplayedMetadataButton.toString() }
data-hide-pagination-area = { hidePaginationArea.toString() }
data-hide-sorting-area = { hideSortingArea.toString() }
data-hide-items-thumbnail = { hideItemsThumbnail ? hideItemsThumbnail.toString() : 'false' }
data-hide-sort-by-button = { hideSortByButton.toString() }
data-hide-exposers-button = { hideExposersButton.toString() }
data-hide-items-per-page-button = { hideItemsPerPageButton.toString() }
data-default-items-per-page = { defaultItemsPerPage }
data-hide-go-to-page-button = { hideGoToPageButton.toString() }
data-show-filters-button-inside-search-control = { showFiltersButtonInsideSearchControl.toString() }
data-start-with-filters-hidden = { startWithFiltersHidden.toString() }
data-filters-as-modal = { filtersAsModal.toString() }
data-show-inline-view-mode-options = { showInlineViewModeOptions.toString() }
data-show-fullscreen-with-view-modes = { showFullscreenWithViewModes.toString() }
data-default-order = { order ? order : 'ASC' }
data-default-orderby = { updatedListType == 'collection' ? (collectionOrderBy ? collectionOrderBy : 'date') : (orderBy ? orderBy : 'date') }
data-default-orderby-meta = { updatedListType == 'collection' ? (collectionOrderByMeta ? collectionOrderByMeta : '') : (orderByMeta ? orderByMeta : '') }
data-default-orderby-type = { updatedListType == 'collection' ? (collectionOrderByType ? collectionOrderByType : '') : (orderByType ? orderByType : '') } >
</main>
</div>
}
},
/* Deprecated during Vue 3 migration to prepend attributes with data- */
{
"attributes": {

View File

@ -76,7 +76,11 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
orderByType,
collectionOrderBy,
collectionOrderByMeta,
collectionOrderByType
collectionOrderByType,
shouldNotHideFiltersOnMobile,
displayFiltersHorizontally,
hideFilterCollapses,
filtersInlineWidth
} = attributes;
// Gets blocks props from hook
@ -89,7 +93,7 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
if ( enabledViewModes === null || !enabledViewModes.length )
enabledViewModes = Object.keys(tainacan_plugin.registered_view_modes);
console.log('edit', collectionOrderByMeta);
const fontSizes = [
{
name: __( 'Tiny', 'tainacan' ),
@ -497,6 +501,16 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
setAttributes({ startWithFiltersHidden: isChecked });
}
}
/>
<ToggleControl
label={__('Should not hide filters even on mobile', 'tainacan')}
help={ shouldNotHideFiltersOnMobile || shouldNotHideFiltersOnMobile === undefined ? __('Toggle to keep filters area visible even on small screen sizes.', 'tainacan') : __('Automatically hide filters area on small screen sizes inside a modal', 'tainacan') }
checked={ shouldNotHideFiltersOnMobile && !filtersAsModal }
onChange={ ( isChecked ) => {
shouldNotHideFiltersOnMobile = isChecked;
setAttributes({ shouldNotHideFiltersOnMobile: isChecked });
}
}
/>
<ToggleControl
label={__('Filters as a Modal', 'tainacan')}
@ -508,6 +522,26 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
}
}
/>
<ToggleControl
label={ __('Display filters horizontally', 'tainacan') }
help={ displayFiltersHorizontally ? __( 'Toggle to not show filters in an horizontal pane above the search control.', 'tainacan') : __( 'Toggle to show filters in an horizontal pane above the search control instead of a vertical sidebar. This layout fits better with select and textual input filters.', 'tainacan') }
checked={ displayFiltersHorizontally }
onChange={ ( isChecked ) => {
displayFiltersHorizontally = isChecked;
setAttributes({ displayFiltersHorizontally: isChecked });
}
}
/>
<ToggleControl
label={ __('Hide filter collapses button', 'tainacan') }
help={ __('Toggle to not display each filter label as a collapsable button. This is suggested when you have a small amount of filters.', 'tainacan') }
checked={ hideFilterCollapses }
onChange={ ( isChecked ) => {
hideFilterCollapses = isChecked;
setAttributes({ hideFilterCollapses: isChecked });
}
}
/>
</PanelBody>
<PanelBody
@ -569,6 +603,7 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
setAttributes( { baseFontSize: newFontSize } );
} }
/>
{ !displayFiltersHorizontally && !filtersAsModal ?
<RangeControl
label={ __('Filters Area Width (%)', 'tainacan') }
value={ filtersAreaWidth }
@ -576,6 +611,16 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
min={ 10 }
max={ 40 }
/>
: null }
{ displayFiltersHorizontally ?
<RangeControl
label={ __('Filters Inline Width (px)', 'tainacan') }
value={ filtersInlineWidth }
onChange={ ( width ) => setAttributes( { filtersInlineWidth: width } ) }
min={ 100 }
max={ 800 }
/>
: null }
</PanelBody>
<PanelBody
title={__('Colors', 'tainacan')}
@ -815,6 +860,7 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
style={{
'--tainacan-background-color': backgroundColor,
'--tainacan-filter-menu-width-theme': filtersAreaWidth + '%',
'--tainacan-filters-inline-width': filtersInlineWidth + 'px',
'--tainacan-input-color': inputColor,
'--tainacan-input-background-color': inputBackgroundColor,
'--tainacan-input-border-color': inputBorderColor,
@ -866,7 +912,7 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
!hideExposersButton ? <span className="fake-button"><div className="fake-icon"></div><div className="fake-text"></div></span> : null
}
</div>
<div className="below-search-control">
<div className={ 'below-search-control' + (displayFiltersHorizontally ? ' horizontal-filters' : '') }>
{ !showFiltersButtonInsideSearchControl & !hideHideFiltersButton && !hideFilters ? <span className="fake-hide-button"><div className="fake-icon"></div></span> : null }
{
!hideFilters && !filtersAsModal && !startWithFiltersHidden ?
@ -876,6 +922,7 @@ export default function({ attributes, setAttributes, isSelected, clientId }) {
}}
className="filters">
<div className="fake-filters-heading"></div>
{ !hideFilterCollapses ? <span className="fake-link"></span> : null }
{ Array(2).fill().map( () => {
return <div className="fake-filter">
<span className="fake-text"></span>

View File

@ -49,7 +49,11 @@ export default function({ attributes }) {
orderByType,
collectionOrderBy,
collectionOrderByMeta,
collectionOrderByType
collectionOrderByType,
shouldNotHideFiltersOnMobile,
displayFiltersHorizontally,
hideFilterCollapses,
filtersInlineWidth
} = attributes;
let updatedListType = '' + listType;
@ -58,7 +62,7 @@ export default function({ attributes }) {
updatedListType = 'collection';
else if (updatedListType === '' && termId && taxonomyId)
updatedListType = 'term';
console.log('save', updatedListType, collectionOrderByMeta)
// Gets attributes such as style, that are automatically added by the editor hook
const blockProps = useBlockProps.save();
@ -68,6 +72,7 @@ export default function({ attributes }) {
'--tainacan-base-font-size': baseFontSize + 'px',
'--tainacan-background-color': backgroundColor,
'--tainacan-filter-menu-width-theme': filtersAreaWidth + '%',
'--tainacan-filters-inline-width': filtersInlineWidth + 'px',
'--tainacan-input-color': inputColor,
'--tainacan-input-background-color': inputBackgroundColor,
'--tainacan-input-border-color': inputBorderColor,
@ -112,7 +117,10 @@ export default function({ attributes }) {
data-default-order = { order ? order : 'ASC' }
data-default-orderby = { updatedListType == 'collection' ? (collectionOrderBy ? collectionOrderBy : 'date') : (orderBy ? orderBy : 'date') }
data-default-orderby-meta = { updatedListType == 'collection' ? (collectionOrderByMeta ? collectionOrderByMeta : '') : (orderByMeta ? orderByMeta : '') }
data-default-orderby-type = { updatedListType == 'collection' ? (collectionOrderByType ? collectionOrderByType : '') : (orderByType ? orderByType : '') } >
data-default-orderby-type = { updatedListType == 'collection' ? (collectionOrderByType ? collectionOrderByType : '') : (orderByType ? orderByType : '') }
data-should-not-hide-filters-on-mobile = { shouldNotHideFiltersOnMobile ? shouldNotHideFiltersOnMobile.toString() : 'false' }
data-hide-filter-collapses = { hideFilterCollapses ? hideFilterCollapses.toString() : 'false' }
data-display-filters-horizontally = { displayFiltersHorizontally ? displayFiltersHorizontally.toString() : 'false' } >
</main>
</div>
};

View File

@ -56,6 +56,33 @@
flex-direction: row;
flex: 1 0 auto;
&.horizontal-filters {
flex-direction: column;
.filters {
flex-direction: row;
flex: 0 1 100%;
flex-wrap: wrap;
padding: 18px 12px 6px 12px;
.fake-filters-heading {
margin-right: 90%;
top: -0.5em;
left: 0.5em;
}
.fake-filters-heading + .fake-link {
margin-right: 95%;
margin-left: 12px;
}
.fake-filter {
margin: 6px 12px;
display: inline-flex;
width: var(--tainacan-filters-inline-width, 272px);
}
}
}
.filters {
flex: 0 1 var(--tainacan-filter-menu-width-theme, 20%);
display: flex;
@ -66,7 +93,8 @@
display: flex;
flex-direction: column;
width: 80%;
margin: 5% 12%;
margin: 5% 8%;
.fake-text {
margin: 4px 0;
width: 35%;

View File

@ -43,7 +43,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : '',
@ -57,7 +57,7 @@
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),
@ -92,7 +92,7 @@
<p
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : '',

View File

@ -43,7 +43,7 @@
v-if="collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item, titleItemMetadatum) : '',
@ -57,7 +57,7 @@
v-if="!collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -71,7 +71,7 @@
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),

View File

@ -43,7 +43,7 @@
v-if="collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item, titleItemMetadatum) : '',
@ -57,7 +57,7 @@
v-if="!collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -84,7 +84,7 @@
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),
@ -273,7 +273,7 @@
v-if="collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item, titleItemMetadatum) : '',
@ -287,7 +287,7 @@
v-if="!collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -301,7 +301,7 @@
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),

View File

@ -51,7 +51,7 @@
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),

View File

@ -0,0 +1,137 @@
<template>
<div class="table-container">
<div class="table-wrapper">
<!-- Empty result placeholder, rendered in the parent component -->
<slot />
<!-- SKELETON LOADING -->
<div
v-if="isLoading"
class="tainacan-mosaic-container--skeleton">
<div
v-for="item in 12"
:key="item"
:style="{
'--tainacan-mosaic-item-width': randomHeightForMosaicItem(),
'--tainacan-mosaic-item-height': randomHeightForMosaicItem()
}"
class="skeleton" />
</div>
<!-- MOSAIC VIEW MODE -->
<ul
v-if="!isLoading"
class="tainacan-mosaic-container">
<li
v-for="(item, index) of items"
:key="index"
:style="{
'--tainacan-mosaic-item-width': getAcceptableWidthBasedOnRatio(item['thumbnail'], 'tainacan-large-full', 300),
'--tainacan-mosaic-item-height': $thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 300)
}"
:data-tainacan-item-id="item.id"
:aria-setsize="totalItems"
:aria-posinset="getPosInSet(index)">
<a
class="tainacan-mosaic-item"
:href="getItemLink(item.url, index)">
<!-- JS-side hook for extra content -->
<div
v-if="hasBeforeHook()"
class="faceted-search-hook faceted-search-hook-item-before"
v-html="getBeforeHook(item)" />
<!-- Thumbnail -->
<blur-hash-image
v-if="item.thumbnail != undefined"
class="tainacan-mosaic-item-thumbnail"
:width="$thumbHelper.getWidth(item['thumbnail'], 'tainacan-large-full', 320)"
:height="$thumbHelper.getHeight(item['thumbnail'], 'tainacan-large-full', 320)"
:hash="$thumbHelper.getBlurhashString(item['thumbnail'], 'tainacan-large-full')"
:src="$thumbHelper.getSrc(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:srcset="$thumbHelper.getSrcSet(item['thumbnail'], 'tainacan-large-full', item.document_mimetype)"
:alt="item.thumbnail_alt ? item.thumbnail_alt : $i18n.get('label_thumbnail')"
:transition-duration="500"
/>
<!-- Title -->
<div
class="metadata-title"
:style="isSlideshowViewModeEnabled ? 'padding-right: 2rem;' : ''">
<p
v-tooltip="{
delay: {
show: 750,
hide: 100,
},
content: item.title != undefined ? item.title : '',
html: true,
placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
v-html="item.title != undefined ? item.title : ''" />
<span
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),
placement: 'auto-start',
popperClass: ['tainacan-tooltip', 'tooltip']
}"
class="icon slideshow-icon"
@click.prevent="starSlideshowFromHere(index)">
<i class="tainacan-icon tainacan-icon-viewgallery tainacan-icon-1-125em" />
</span>
</div>
<!-- JS-side hook for extra content -->
<div
v-if="hasAfterHook()"
class="faceted-search-hook faceted-search-hook-item-after"
v-html="getAfterHook(item)" />
</a>
</li>
</ul>
</div>
</div>
</template>
<script>
import { viewModesMixin } from '../js/view-modes-mixin.js';
export default {
name: 'ViewModeMosaic',
mixins: [
viewModesMixin
],
methods: {
randomHeightForMosaicItem() {
let min = 120;
let max = 280;
return Math.floor(Math.random()*(max-min+1)+min);
},
getAcceptableWidthBasedOnRatio(thumbnail, size, defaultSize) {
const width = this.$thumbHelper.getWidth(thumbnail, size, defaultSize);
const height = this.$thumbHelper.getHeight(thumbnail, size, defaultSize);
return (width / height) > 0.7 ? width : ( height * 0.7 );
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../../../admin/scss/_view-mode-mosaic.scss";
</style>

View File

@ -46,7 +46,7 @@
v-if="collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.metadata != undefined ? renderMetadata(item, titleItemMetadatum) : '',
@ -60,7 +60,7 @@
v-if="!collectionId && titleItemMetadatum"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined ? item.title : (`<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`),
@ -74,7 +74,7 @@
v-if="isSlideshowViewModeEnabled"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),

View File

@ -9,7 +9,7 @@
id="slides-help-button"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_slides_help'),
@ -29,7 +29,7 @@
id="metedata-panel-button"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: isMetadataCompressed ? $i18n.get('label_show_metadata') : $i18n.get('label_hide_metadata'),
@ -50,7 +50,7 @@
id="item-page-button"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('label_item_page'),
@ -70,7 +70,7 @@
id="close-fullscren-button"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: $i18n.get('close'),
@ -90,7 +90,7 @@
id="metadata-compress-button"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: isMetadataCompressed ? $i18n.get('label_show_metadata') : $i18n.get('label_hide_metadata'),
@ -325,7 +325,7 @@
<span
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: metadatum.name,

View File

@ -136,7 +136,7 @@
column.metadata_type_object.related_mapped_prop == 'title'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.title != undefined && item.title != '' ? item.title : `<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`,
@ -153,7 +153,7 @@
column.metadata_type_object.related_mapped_prop == 'description'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: item.description != undefined && item.description != '' ? item.description : `<span class='has-text-gray3 is-italic'>` + $i18n.get('label_value_not_provided') + `</span>`,
@ -173,7 +173,7 @@
column.metadatum !== 'row_description'"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
popperClass: [ 'tainacan-tooltip', 'tooltip', column.metadata_type_object != undefined && column.metadata_type_object.component == 'tainacan-textarea' ? 'metadata-type-textarea' : '' ],
@ -204,7 +204,7 @@
<span
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 100,
},
content: $i18n.get('label_see_on_fullscreen'),

View File

@ -82,8 +82,8 @@ export const viewModesMixin = {
// Inserts information necessary for item by item navigation on single pages
this.queries['pos'] = ((this.queries['paged'] - 1) * this.queries['perpage']) + index;
this.queries['source_list'] = this.termId ? 'term' : (!this.collectionId || this.collectionId == 'default' ? 'repository' : 'collection');
if ( this.$route && this.$route.path )
this.queries['ref'] = this.$route.path;
if ( this.$route && this.$route.href && this.$route.href.split('?') && this.$route.href.split('?').length )
this.queries['ref'] = this.$route.href;
return itemUrl + '?' + qs.stringify(this.queries);
}
return itemUrl;

View File

@ -94,6 +94,10 @@
bottom: 0;
transition: top ease-in 0.75s, bottom ease-in 0.75s, position ease-in 0.75s;
&.horizontal-filters {
max-width: 100%;
}
@media screen and (max-width: 768px) {
padding: 0;
z-index: 99999;
@ -125,6 +129,12 @@
.modal-close {
display: none;
}
&.horizontal-filters {
-webkit-flex: 0 1 100%;
-ms-flex: 0 1 100%;
flex: 0 1 100%;
}
}
}
@ -156,19 +166,66 @@
}
}
&.has-horizontal-filters {
.items-list-area {
max-width: 100%;
}
#filters-modal.horizontal-filters {
overflow: visible;
.modal-content,
.filters-components-list {
overflow: visible;
width: 100%;
max-width: 100% !important;
margin: 0;
box-sizing: border-box;
}
.filters-components-list {
margin-bottom: 0;
& > div {
display: inline-block;
vertical-align: text-top;
}
.filter-item-forms {
width: 100%;
min-width: var(--tainacan-filters-inline-width, 272px);
break-inside: avoid;
display: inline-block;
vertical-align: text-top;
margin-bottom: 1rem;
}
@media screen and (min-width: 769px) {
& > div:not(:last-child) {
margin-right: 2.25em;
}
.filter-item-forms {
width: var(--tainacan-filters-inline-width, 272px);
}
.filter-item-forms:not(:last-child) {
margin-right: 2.25em;
}
}
}
}
}
@media screen and (max-width: 768px) {
&.is-filters-menu-open {
.items-list-area {
max-width: 100%;
}
.filters-menu {
float: none;
max-width: 100%;
}
.items-list-area {
width: 100%;
}
#filter-menu-compress-button {
display: none;
max-width: 100%;
}
.filters-menu {

View File

@ -32,6 +32,7 @@ import ThemeSearch from './theme.vue';
// Remaining imports
import store from '../../../admin/js/store/store';
import HelpButton from '../../../admin/components/other/help-button.vue';
import routerTheme from './theme-search/js/theme-router.js';
import eventBusSearch from '../../../admin/js/event-bus-search';
import {
@ -76,7 +77,7 @@ export default (element) => {
const registeredViewModes =
( tainacan_plugin && tainacan_plugin.registered_view_modes && tainacan_plugin.registered_view_modes.length ) ?
tainacan_plugin.registered_view_modes :
[ 'table', 'cards', 'records', 'masonry', 'slideshow', 'list', 'map' ];
[ 'table', 'cards', 'records', 'masonry', 'mosaic', 'slideshow', 'list', 'map'];
// At first, we consider that all registered view modes are included.
let possibleViewModes = registeredViewModes;
@ -130,7 +131,10 @@ export default (element) => {
startWithFiltersHidden: isParameterTrue(getDataAttribute(blockElement, 'start-with-filters-hidden')),
filtersAsModal: isParameterTrue(getDataAttribute(blockElement, 'filters-as-modal')),
showInlineViewModeOptions: isParameterTrue(getDataAttribute(blockElement, 'show-inline-view-mode-options')),
showFullscreenWithViewModes: isParameterTrue(getDataAttribute(blockElement, 'show-fullscreen-with-view-modes'))
showFullscreenWithViewModes: isParameterTrue(getDataAttribute(blockElement, 'show-fullscreen-with-view-modes')),
shouldNotHideFiltersOnMobile: isParameterTrue(getDataAttribute(blockElement, 'should-not-hide-filters-on-mobile')),
displayFiltersHorizontally: isParameterTrue(getDataAttribute(blockElement, 'display-filters-horizontally')),
hideFilterCollapses: isParameterTrue(getDataAttribute(blockElement, 'hide-filter-collapses')),
}),
});
@ -188,6 +192,7 @@ export default (element) => {
VueItemsList.use(AxiosErrorHandlerPlugin);
VueItemsList.use(ConsolePlugin, {visual: false});
VueItemsList.use(AdminOptionsHelperPlugin, blockElement.dataset['options']);
VueItemsList.component('help-button', HelpButton);
/* Registers Extra Vue Components passed to the window.tainacan_extra_components */
if (typeof window.tainacan_extra_components != "undefined") {

View File

@ -31,7 +31,7 @@
id="filter-menu-compress-button"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: !isFiltersModalActive ? $i18n.get('label_show_filters') : $i18n.get('label_hide_filters'),
@ -41,12 +41,19 @@
}"
aria-controls="filters-modal"
:aria-expanded="isFiltersModalActive"
:class="hideHideFiltersButton ? 'is-hidden-tablet' : ''"
:class="{
'is-hidden-tablet': !shouldNotHideFiltersOnMobile && hideHideFiltersButton,
'is-hidden': shouldNotHideFiltersOnMobile && hideHideFiltersButton
}"
:aria-label="!isFiltersModalActive ? $i18n.get('label_show_filters') : $i18n.get('label_hide_filters')"
@click="isFiltersModalActive = !isFiltersModalActive">
<span class="icon">
<i
:class="{ 'tainacan-icon-arrowleft' : isFiltersModalActive, 'tainacan-icon-arrowright' : !isFiltersModalActive }"
:class="{
'tainacan-icon-arrowdown': isFiltersModalActive && displayFiltersHorizontally,
'tainacan-icon-arrowleft': isFiltersModalActive && !displayFiltersHorizontally,
'tainacan-icon-arrowright' : !isFiltersModalActive
}"
class="tainacan-icon tainacan-icon-1-25em" />
</span>
<span class="text is-hidden-tablet">{{ $i18n.get('filters') }}</span>
@ -149,7 +156,7 @@
ref="displayedMetadataDropdown"
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: totalItems <= 0 ? $i18n.get('info_cant_select_metadata_without_items') : '',
@ -341,7 +348,7 @@
v-else
v-tooltip="{
delay: {
shown: 500,
show: 500,
hide: 300,
},
content: registeredViewModes[viewModeOption].label,
@ -406,7 +413,45 @@
<!-- SIDEBAR WITH FILTERS -->
<template v-if="!hideFilters">
<template v-if="!filtersAsModal && shouldNotHideFiltersOnMobile">
<div
v-if="isFiltersModalActive"
id="filters-modal"
ref="filters-modal"
role="region"
:class="'tainacan-modal tainacan-form filters-menu' + (displayFiltersHorizontally ? ' horizontal-filters' : '')">
<div class="animation-content modal-content">
<!-- JS-side hook for extra form content -->
<div
v-if="hooks['filters_before']"
class="faceted-search-hook faceted-search-hook-filters-before"
v-html="hooks['filters_before']" />
<filters-items-list
id="filters-items-list"
:is-loading-items="isLoadingItems"
:taxonomy="taxonomy"
:collection-id="collectionId + ''"
:is-repository-level="isRepositoryLevel"
:filters-as-modal="false"
:has-filtered="hasFiltered"
:is-mobile-screen="isMobileScreen"
:hide-filter-collapses="hideFilterCollapses"
@update-is-loading-items-state="(state) => isLoadingItems = state" />
<!-- JS-side hook for extra form content -->
<div
v-if="hooks['filters_after']"
class="faceted-search-hook faceted-search-hook-filters-after"
v-html="hooks['filters_after']" />
</div>
</div>
</template>
<b-modal
v-else
id="filters-modal"
ref="filters-modal"
v-model="isFiltersModalActive"
@ -415,7 +460,7 @@
:auto-focus="filtersAsModal"
:trap-focus="filtersAsModal"
full-screen
:custom-class="'tainacan-modal tainacan-form filters-menu' + (filtersAsModal ? ' filters-menu-modal' : '')"
:custom-class="'tainacan-modal tainacan-form filters-menu' + (filtersAsModal ? ' filters-menu-modal' : '') + (displayFiltersHorizontally ? ' horizontal-filters' : '')"
:can-cancel="hideHideFiltersButton || !filtersAsModal ? ['x', 'outside'] : ['x', 'escape', 'outside']"
:close-button-aria-label="$i18n.get('close')">
@ -438,6 +483,7 @@
:filters-as-modal="filtersAsModal"
:has-filtered="hasFiltered"
:is-mobile-screen="isMobileScreen"
:hide-filter-collapses="hideFilterCollapses"
@update-is-loading-items-state="(state) => isLoadingItems = state" />
<!-- JS-side hook for extra form content -->
@ -737,7 +783,10 @@
startWithFiltersHidden: false,
filtersAsModal: false,
showInlineViewModeOptions: false,
showFullscreenWithViewModes: false
showFullscreenWithViewModes: false,
shouldNotHideFiltersOnMobile: false,
displayFiltersHorizontally: false,
hideFilterCollapses: false,
},
data() {
return {
@ -790,6 +839,7 @@
}),
wrapperClasses() {
return {
'has-horizontal-filters': this.displayFiltersHorizontally,
'is-filters-menu-open': !this.hideFilters && this.isFiltersModalActive && !this.openAdvancedSearch,
'is-filters-menu-fixed-at-top': this.isFiltersListFixedAtTop,
'is-filters-menu-fixed-at-bottom': this.isFiltersListFixedAtBottom,
@ -973,7 +1023,7 @@
this.hasAnOpenAlert = true;
},
isFiltersModalActive() {
if (this.isFiltersModalActive) {
if ( this.isFiltersModalActive ) {
setTimeout(() => {
if (this.filtersAsModal && this.$refs['filters-modal'] && this.$refs['filters-modal'].focus)
this.$refs['filters-modal'].focus();
@ -1028,6 +1078,12 @@
}
}
// If any default items per page is set, apply it
// 12 is already the default value, se we don't set this value
if (this.defaultItemsPerPage != undefined && this.defaultItemsPerPage !== 12 ) {
this.$eventBusSearch.setItemsPerPage(this.defaultItemsPerPage, true);
}
for (let key in currentQuery) {
if (currentQuery[key] == 'true')
currentQuery[key] = true;
@ -1123,15 +1179,12 @@
}
}
// If any default items per page is set, apply it
if (this.defaultItemsPerPage)
this.$eventBusSearch.setItemsPerPage(this.defaultItemsPerPage, true);
// Watches window resize to adjust filter's top position and compression on mobile
if (!this.hideFilters) {
if ( !this.hideFilters && !this.shouldNotHideFiltersOnMobile ) {
this.hideFiltersOnMobile();
window.addEventListener('resize', this.hideFiltersOnMobile);
} else {
this.isFiltersModalActive = !this.startWithFiltersHidden;
}
// Uses Intersection Observer o see if the top of the list is on screen and fix filters list position
@ -1589,7 +1642,7 @@
// Component
this.$emitter.off();
// Window
if (!this.hideFilters)
if ( !this.hideFilters && !this.shouldNotHideFiltersOnMobile )
window.removeEventListener('resize', this.hideFiltersOnMobile);
// $root
if (!this.hideAdvancedSearch)
@ -1925,7 +1978,7 @@
}
&:last-child {
margin-right: auto;
margin-right: 0;
}
.label {

View File

@ -7,6 +7,7 @@ import iconUrl from 'leaflet/dist/images/marker-icon.png';
import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
import shadowUrl from 'leaflet/dist/images/marker-shadow.png';
// Defines custom marker icons
delete TainacanLeaflet.Icon.Default.prototype._getIconUrl;
TainacanLeaflet.Icon.Default.mergeOptions({
iconRetinaUrl: iconRetinaUrl,
@ -14,6 +15,48 @@ TainacanLeaflet.Icon.Default.mergeOptions({
shadowUrl: shadowUrl
});
// Observes the visibility of the map container to resize the map when it becomes visible
const mapObserverOptions = {
root: null, // use the viewport
rootMargin: '0px',
threshold: 0.1 // 10% of the element is visible
};
// The mapObserver repeats part of the initialization logic to prevent the map from looking broke
// when it becomes visible after being hidden, for example inside section tabs
const mapObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
if (
entry &&
entry.target.id &&
window.tainacan_leaflet_maps &&
window.tainacan_leaflet_maps[entry.target.id]
) {
const element = entry.target;
const children = element.children ? element.children : [];
if ( !children.length )
return;
const coordinates = [];
for (let i = 0; i < children.length; i++) {
if ( children[i].hasAttribute('data-latitude') && children[i].hasAttribute('data-longitude') )
coordinates.push([children[i].getAttribute('data-latitude'), children[i].getAttribute('data-longitude')]);
}
if ( !coordinates.length )
return;
const maximum_zoom = element.hasAttribute('data-maximum_zoom') ? element.getAttribute('data-maximum_zoom') : 12;
window.tainacan_leaflet_maps[element.id].invalidateSize(true);
window.tainacan_leaflet_maps[element.id].flyToBounds(coordinates, { maxZoom: maximum_zoom, animate: false });
}
}
});
}, mapObserverOptions);
/* Loads and instantiates map components passed to data-module="geocoordinate-item-metadatum"*/
export default (element) => {
if (element && element.id) {
@ -56,5 +99,11 @@ export default (element) => {
});
tainacanMap.flyToBounds(coordinates, { maxZoom: maximum_zoom });
mapObserver.observe(element);
// Stores referenced to the leaflet instances to manipulate them via the window object inside the observer
window.tainacan_leaflet_maps = typeof window.tainacan_leaflet_maps != "undefined" ? window.tainacan_leaflet_maps : {};
window.tainacan_leaflet_maps[element.id] = tainacanMap;
}
};

View File

@ -336,6 +336,7 @@ return apply_filters( 'tainacan-i18n', [
'label_grid' => __( 'Thumbnails', 'tainacan' ),
'label_table' => __( 'Table', 'tainacan' ),
'label_cards' => __( 'Cards', 'tainacan' ),
'label_mosaic' => __( 'Mosaic', 'tainacan' ),
/* translators: The 'records' view mode, in the sense of a catalog file */
'label_records' => __( 'Records', 'tainacan' ),
'label_masonry' => __( 'Masonry', 'tainacan' ),
@ -529,6 +530,7 @@ return apply_filters( 'tainacan-i18n', [
'label_view_all_%s_collections' => __( 'View all %s collections', 'tainacan' ),
'label_view_collections_list' => __( 'View collections list', 'tainacan' ),
'label_comparator' => __( 'Comparator', 'tainacan' ),
'label_comparators' => __( 'Comparators', 'tainacan' ),
'label_table_of_items' => __( 'Table of Items', 'tainacan' ),
'label_create_another_item' => __( 'Create another item', 'tainacan' ),
'label_recent_collections' => __( 'Recent Collections', 'tainacan' ),
@ -707,6 +709,7 @@ return apply_filters( 'tainacan-i18n', [
'label_item_submission_options' => __( 'Item submission options', 'tainacan' ),
'label_metadata_related_features' => __( 'Metadata related features', 'tainacan' ),
'label_preview' => __( 'Preview', 'tainacan' ),
'label_go_to_permalinks' => __( 'Go to permalinks', 'tainacan' ),
// Instructions. More complex sentences to guide user and placeholders
'instruction_delete_selected_collections' => __( 'Delete selected collections', 'tainacan' ),
@ -788,6 +791,8 @@ return apply_filters( 'tainacan-i18n', [
'instruction_click_to_add_a_point' => __( 'Drag to reposition or click to insert a marker', 'tainacan' ),
'instruction_select_geocoordinate_metadatum' => __( 'Select a geocoordinate metadatum', 'tainacan' ),
'instruction_multiple_terms_insertion' => __( 'Type or paste here a list of names using a separator to create multiple terms at once.', 'tainacan' ),
'instruction_select_second_date_to_compare' => __( 'Select the second date metadatum', 'tainacan' ),
'instruction_select_second_numeric_to_compare' => __( 'Select the second numeric metadatum', 'tainacan' ),
// Info. Other feedback to user.
'info_items_tab_all' => __( 'Every item, except by those sent to trash.', 'tainacan' ),
@ -1064,6 +1069,8 @@ return apply_filters( 'tainacan-i18n', [
'info_terms_creation_failed_due_to_value_%s' => __( 'Terms creation failed due to value: %s.', 'tainacan' ),
'info_terms_creation_failed_due_to_values_%s' => __( 'Terms creation failed due to values: %s.', 'tainacan' ),
'info_autodraft_updated' => __( 'Autodraft updated. Please create the item to keep your changes.', 'tainacan' ),
'info_intersection_explainer' => __( 'Will show items if the selected value is:', 'tainacan' ),
'info_intersection_rules' => __( 'The value must match both rules to appear in the filter.', 'tainacan' ),
/* Activity actions */
'action_update-metadata-value' => __( 'Item Metadata Value Updates', 'tainacan'),
@ -1112,6 +1119,7 @@ return apply_filters( 'tainacan-i18n', [
/* Errors displayed on the interface bottom notifications */
'error_connectivity_label' => __('Connectivity issue', 'tainacan'),
'error_connectivity' => __('It is possible that you are disconnected or the server is not working properly.', 'tainacan'),
'error_permalinks_label' => __('Permalinks issue', 'tainacan'),
'error_400' => __('Some request went wrong due to invalid syntax.', 'tainacan'),
'error_401' => __('You must authenticate to access this information. Try logging in again on the WordPress Admin panel.', 'tainacan'),
'error_403' => __('It seems that you are not allowed to access this content.', 'tainacan'),
@ -1124,6 +1132,7 @@ return apply_filters( 'tainacan-i18n', [
'error_511' => __('You must authenticate to get access this information. Try logging in again on the WordPress Admin panel.', 'tainacan'),
'error_other' => __('Something went wrong here. You may want to try again or contact the Administrator.', 'tainacan'),
'error_connectivity_detail' => __('The WordPress Heartbit API sends requests periodically to the server to update some information. The latest request failed for some reason. It can be the case of a lost connection or bad communication between the browser and the server.', 'tainacan'),
'error_permalinks_detail' => __( 'Tainacan requires your Permalink settings to be configured. Please visit %sPermalink settings%s and define it to an option such as "postname".', 'tainacan' ),
'error_400_detail' => __('The server could not understand the request due to invalid syntax. This is possibly an issue with Tainacan and should be reported to its developers.', 'tainacan'),
'error_401_detail' => __('You must authenticate to get access this information. Even if you have access to the Tainacan admin panel, it may be the case that your session cookies were lost. Try reloading the page or logging again on the WordPress Admin panel.', 'tainacan'),
'error_403_detail' => __('It seems that you are not allowed to access this content. Your user might have a role with insufficient capabilities. If that is not the case, check if you are correctly logged in on the WordPress Admin panel.', 'tainacan'),

View File

@ -97,7 +97,7 @@ class Filters extends TAINACAN_UnitTestCase {
$Tainacan_Filters = \Tainacan\Repositories\Filters::get_instance();
$all_filter_types = $Tainacan_Filters->fetch_filter_types();
$this->assertEquals( 11, count( $all_filter_types ) );
$this->assertEquals( 13, count( $all_filter_types ) );
$float_filters = $Tainacan_Filters->fetch_supported_filter_types('float');
$this->assertTrue( count( $float_filters ) > 0 );