diff --git a/README.rst b/README.rst index ca2210c5..29961c66 100644 --- a/README.rst +++ b/README.rst @@ -33,6 +33,8 @@ There are three portlets/tiles available for filtering: is activated on a contenttype. See installation notes below) ``Collection Result Listing Sort`` a list of indexes where the user can sort the filtered result listing +``Filter Info`` + Displays customisable information about the current search or current context Filter Results of Collections @@ -90,6 +92,15 @@ Simply do this somewhere in your buildout:: collective.collectionfilter[geolocation] ... +Filter Info support +------------------- + +Info display (tile or portlet) can be used to +- display the current search or filters. +- as a title replacement when using mosaic with information about the current search +- an alternative form of navigation with links to reset the search to a single option +- a form of navigation on content using values of the currently viewed object to search + for other similar content Overloading GroupByCriteria --------------------------- diff --git a/base.cfg b/base.cfg index 89d11bfb..e154b19b 100644 --- a/base.cfg +++ b/base.cfg @@ -1,6 +1,16 @@ [buildout] # extensions = mr.developer versions = versions +# parts += vscode + + +[vscode] +recipe = collective.recipe.vscode +eggs = ${test:eggs} ${instance:eggs} +enable-flake8 = true +enable-black = true +generate-envfile = true + [code-analysis] directory = ${buildout:directory}/src/collective/collectionfilter diff --git a/src/collective/collectionfilter/baseviews.py b/src/collective/collectionfilter/baseviews.py index 8be003ca..14111df9 100644 --- a/src/collective/collectionfilter/baseviews.py +++ b/src/collective/collectionfilter/baseviews.py @@ -1,8 +1,13 @@ # -*- coding: utf-8 -*- +import weakref from Acquisition import aq_inner from collective.collectionfilter import PLONE_VERSION -from collective.collectionfilter.filteritems import get_filter_items -from collective.collectionfilter.filteritems import ICollectionish +from collective.collectionfilter.filteritems import ( + get_filter_items, + ICollectionish, + _build_url, + _build_option, +) from collective.collectionfilter.interfaces import IGroupByCriteria from collective.collectionfilter.query import make_query from collective.collectionfilter.utils import base_query @@ -10,12 +15,14 @@ from collective.collectionfilter.utils import safe_encode from collective.collectionfilter.utils import safe_iterable from collective.collectionfilter.vocabularies import TEXT_IDX -from plone import api +from collective.collectionfilter.vocabularies import get_conditions +from collective.collectionfilter.vocabularies import EMPTY_MARKER from plone.api.portal import get_registry_record as getrec from plone.app.uuid.utils import uuidToCatalogBrain from plone.app.uuid.utils import uuidToObject +from plone.dexterity.utils import iterSchemata from plone.i18n.normalizer.interfaces import IIDNormalizer -from plone.memoize import instance +from plone.memoize import instance, ram from plone.uuid.interfaces import IUUID from Products.CMFPlone.utils import get_top_request from Products.CMFPlone.utils import safe_unicode @@ -23,8 +30,10 @@ from zope.component import getUtility from zope.component import queryUtility from zope.i18n import translate +from zope.schema import getFieldsInOrder from zope.schema.interfaces import IVocabularyFactory - +from Products.CMFCore.Expression import Expression, getExprContext +from plone import api import json @@ -37,11 +46,15 @@ HAS_GEOLOCATION = False +def empty_ref(self): + return None + + class BaseView(object): """Abstract base filter view class.""" _collection = None - _top_request = None + _top_request = empty_ref # prevent loops @property def settings(self): @@ -74,9 +87,9 @@ def reload_url(self): @property def top_request(self): - if not self._top_request: - self._top_request = get_top_request(self.request) - return self._top_request + if self._top_request() is None: + self._top_request = weakref.ref(get_top_request(self.request)) + return self._top_request() @property def collection_uuid(self): @@ -278,6 +291,222 @@ def ajax_url(self): return ajax_url +def _exp_cachekey(method, self, target_collection, request): + return ( + target_collection, + json.dumps(request), + self.settings.view_name, + self.settings.as_links, + ) + + +def _field_title_cache_key(method, self, field_id): + return ( + field_id[0], + self.context.getTypeInfo().id + ) + + +class BaseInfoView(BaseView): + + # TODO: should just cache on request? + # @instance.memoize + # @ram.cache(_exp_cachekey) + def get_expression_context(self, collection, request_params): + count_query = {} + query = base_query(request_params) + collection_url = collection.absolute_url() + # TODO: take out the search + # TODO: match them to indexes and get proper names + # TODO: format values properly + # TODO: do we want to read out sort too? + count_query.update(query) + # TODO: delay evaluating this unless its needed + # TODO: This could be cached as same result total appears in other filter counts + catalog_results_fullcount = ICollectionish(collection).results( + make_query(count_query), request_params + ) + results = len(catalog_results_fullcount) + + # Clean up filters and values + if "collectionfilter" in query: + del query["collectionfilter"] + groupby_criteria = getUtility(IGroupByCriteria).groupby + q = [] + for group_by, value in query.items(): + if group_by not in groupby_criteria: + continue + # TODO: we actually have idx not group_by + idx = groupby_criteria[group_by]['index'] + value = safe_decode(value) + current_idx_value = safe_iterable(value) + # Set title from filter value with modifications, + # e.g. uuid to title + display_modifier = groupby_criteria[group_by].get("display_modifier", None) + titles = [] + for filter_value in current_idx_value: + title = filter_value + if filter_value is not EMPTY_MARKER and callable(display_modifier): + title = safe_decode(display_modifier(filter_value, idx)) + # TODO: still no nice title for filter indexes? Should be able to get from query builder + # TODO: we don't know if filter is AND/OR to display that detail + # TODO: do we want no follow always? + # TODO: should support clearing filter? e.g. if single value, click to remove? + # Build filter url query + query_param = urlencode( + safe_encode({group_by: filter_value}), doseq=True + ) + url = "/".join( + [ + it + for it in [ + collection_url, + self.settings.view_name, + "?" + query_param if query_param else None, + ] + if it + ] + ) + # TODO: should have option for nofollow? + if self.settings.as_links: + titles.append(u'{}'.format(url, title)) + else: + titles.append(title) + q.append((group_by, titles)) + + # Set up context for running templates + expression_context = getExprContext( + collection, + ) + expression_context.setLocal("results", results) + expression_context.setLocal("query", q) + expression_context.setLocal("search", query.get("SearchableText", "")) + return expression_context + + def info_contents(self): + request_params = self.top_request.form or {} + expression_context = self.get_expression_context( + self.collection.getObject(), request_params + ) + + parts_vocabulary_factory = getUtility( + IVocabularyFactory, "collective.collectionfilter.TemplateParts" + ) + parts_vocabulary = parts_vocabulary_factory(self.context) + + parts = [] + for template in self.settings.template_type: + text = None + try: + # This vocab term lookup will throw a LookupError if the term doesn't exist in the built-ins + template_definition_term = parts_vocabulary.getTerm(template) + exp = template_definition_term.value + # TODO: precompile templates + text = Expression(exp)(expression_context) + + if isinstance(text, list): + text = u", ".join(text) + + except LookupError: + text = template + + if text: + parts.append(text) + + line = u" ".join(parts) + # TODO: should be more generic i18n way to do this? + line = line.replace(u" ,", u",").replace(" :", ":") + return line + + def is_content_context(self): + if not self.settings.context_aware: + return False + + if self.collection.getObject() == self.context: + return False + + return True + + def get_fields_to_display(self): + fields = self.settings.context_aware_fields + # TODO: Get the friendly name for the group_by instead of the id + return [ + (field, getattr(self.context, field)) + for field in fields + if hasattr(self.context, field) + ] + + # Cache this function based on the index id and the dexterity type + @ram.cache(_field_title_cache_key) + def get_field_title(self, field): + """ + Field is a tuple, where the first index is the name of the field and the second the values for that field + Returns the friendly version of the title when possible, falling + back to the ID if a firiendly name can't be found. + """ + index_id = field[0] + + for schema in iterSchemata(context=self.context): + for field_id, field_object in getFieldsInOrder(schema=schema): + if field_id == index_id: + return field_object.title + + return index_id + + def get_field_values(self, field): + content_value = field[1] + index = field[0] + groupby_criteria = getUtility(IGroupByCriteria).groupby + request_params = self.top_request.form + request_params = safe_decode(request_params) + extra_ignores = [index, index + "_op"] + urlquery = base_query(request_params, extra_ignores) + collection = self.collection.getObject() + + # TODO: Refactor the following copied lines from collective.collectionfitler.filteritems into a function + field_value = content_value() if callable(content_value) else content_value + # decode it to unicode + field_value = safe_decode(field_value) + # Make sure it's iterable, as it's the case for e.g. the subject index. + field_values = safe_iterable(field_value) + # allow excluding or extending the field_valueue per index + groupby_modifier = groupby_criteria[index].get("groupby_modifier", None) + + if not groupby_modifier: + groupby_modifier = lambda values, cur, narrow: values # noqa: E731 + + field_values = groupby_modifier(field_values, field_value, False) + + field_data = [] + for value in field_values: + url = _build_url(collection_url=collection.absolute_url(), urlquery=urlquery, filter_value=value, current_idx_value=[], idx=index, filter_type="single") + data = _build_option(filter_value=value, url=url, current_idx_value=[value], groupby_options=groupby_criteria[index]) + field_data.append(data) + + return field_data + + @property + def is_available(self): + target_collection = self.collection + if target_collection is None: + return False + request_params = self.top_request.form or {} + expression_context = self.get_expression_context( + target_collection.getObject(), request_params + ) + + conditions = dict((k, (t, e)) for k, t, e in get_conditions()) + if not self.settings.hide_when: + return True + for cond in self.settings.hide_when: + _, exp = conditions.get(cond) + # TODO: precompile templates + res = Expression(exp)(expression_context) + if not res: + return True + return False + + if HAS_GEOLOCATION: class BaseMapsView(BaseView): diff --git a/src/collective/collectionfilter/configure.zcml b/src/collective/collectionfilter/configure.zcml index 1871ad93..aec5ba23 100644 --- a/src/collective/collectionfilter/configure.zcml +++ b/src/collective/collectionfilter/configure.zcml @@ -47,6 +47,14 @@ component=".vocabularies.SortOnIndexesVocabulary" name="collective.collectionfilter.SortOnIndexes" /> + + + + + +
Title
+ +
+
+ +
${python:view.get_field_title(field)}
+ + +
+ + ${field_value/title} + +
+
+ +
+
+ + +
+
+ diff --git a/src/collective/collectionfilter/portlets/info.py b/src/collective/collectionfilter/portlets/info.py new file mode 100644 index 00000000..58490b7e --- /dev/null +++ b/src/collective/collectionfilter/portlets/info.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile +from collective.collectionfilter import _ +from collective.collectionfilter.baseviews import BaseInfoView +from collective.collectionfilter.interfaces import ICollectionFilterInfo # noqa +from collective.collectionfilter.portlets import BasePortletRenderer +from plone.app.portlets.portlets import base +from plone.portlets.interfaces import IPortletDataProvider +from zope.interface import implementer + + +class ICollectionFilterInfoPortlet(ICollectionFilterInfo, IPortletDataProvider): # noqa + """Portlet interface based on ICollectionFilterSchema""" + + +@implementer(ICollectionFilterInfoPortlet) +class Assignment(base.Assignment): + + header = u"" + target_collection = None + view_name = None + content_selector = "#content-core" + template_type = [] + hide_when = [] + as_links = True + context_aware = False + + def __init__( + self, + header=u"", + target_collection=None, + view_name=None, + content_selector="#content-core", + template_type=[], + hide_when=[], + as_links=True, + context_aware=False, + context_aware_fields=[] + ): + self.header = header + self.target_collection = target_collection + self.view_name = view_name + self.content_selector = content_selector + self.template_type = template_type + self.hide_when = hide_when + self.as_links = as_links + self.context_aware = context_aware + self.context_aware_fields = context_aware_fields + + @property + def portlet_id(self): + """Return the portlet assignment's unique object id.""" + return id(self) + + @property + def title(self): + if self.header: + return self.header + else: + return _(u"Collection Filter Search Info") + + +class Renderer(BasePortletRenderer, BaseInfoView): + render = ViewPageTemplateFile("info.pt") + + +class AddForm(base.AddForm): + + schema = ICollectionFilterInfoPortlet + label = _(u"Add Collection Filter Info Portlet") + description = _(u"This portlet shows information about the filter selected") + + def create(self, data): + return Assignment(**data) + + +class EditForm(base.EditForm): + + schema = ICollectionFilterInfoPortlet + label = _(u"Edit Collection Filter Info Portlet") + description = _(u"This portlet shows information about the filter selected") diff --git a/src/collective/collectionfilter/portlets/profiles/default/portlets.xml b/src/collective/collectionfilter/portlets/profiles/default/portlets.xml index e5de5618..f354794a 100644 --- a/src/collective/collectionfilter/portlets/profiles/default/portlets.xml +++ b/src/collective/collectionfilter/portlets/profiles/default/portlets.xml @@ -15,4 +15,9 @@ title="Collection Filter Result Sorting" description="This portlet allows sorting of the filtered result listing." /> + diff --git a/src/collective/collectionfilter/portlets/profiles/portlets_with_maps/portlets.xml b/src/collective/collectionfilter/portlets/profiles/portlets_with_maps/portlets.xml index a14ab667..9c238b4d 100644 --- a/src/collective/collectionfilter/portlets/profiles/portlets_with_maps/portlets.xml +++ b/src/collective/collectionfilter/portlets/profiles/portlets_with_maps/portlets.xml @@ -15,6 +15,11 @@ title="Collection Filter Result Sorting" description="This portlet allows sorting of the filtered result listing." /> + header {\n font-weight: bold;\n }\n .edit-link {\n right: unset;\n left: 0;\n }\n }\n}\n\n.collectionSearch .searchContent,\n.portlet.collectionSearch .searchContent,\n.collectionFilter .filterContent,\n.portlet.collectionFilter .filterContent,\n.collectionSortOn .filterContent,\n.portlet.collectionSortOn .filterContent {\n padding: 1em;\n > ul {\n padding: 0;\n list-style: none;\n position: relative;\n > li {\n list-style-type: none;\n padding: 2px;\n\n input[type=\"radio\"], input[type=\"checkbox\"] {\n margin: 0.3em 0.5em 0 0;\n line-height: normal;\n position: absolute;\n }\n label {\n display: inline-block;\n .filterControl, .filterLabel{\n display: inline-block;\n vertical-align: top;\n }\n .filterControl {\n margin-right: 0.5em;\n }\n .filterLabel{\n -webkit-hyphens: auto;\n -moz-hyphens: auto;\n hyphens: auto;\n padding-left: 1.5em;\n }\n }\n > a {\n padding: 0;\n display: block;\n border: 0;\n position: relative;\n z-index: 1;\n &:before {\n // left: 2px;\n font-size: 1.75em;\n line-height: 0.5;\n top: 0.15em;\n left: 0.15em;\n content: \"•\";\n display: inline;\n position: absolute;\n }\n &:hover {\n text-decoration: none;\n border-bottom: 0;\n }\n > span {\n padding-left: 1.5em;\n display: inline-block;\n hyphens: auto;\n }\n }\n &:hover {\n background: transparent;\n }\n }\n\n\n li.selected a {\n font-weight: bold;\n }\n ul {\n padding-left:15px;\n list-style: none;\n }\n }\n label {\n display: block;\n margin: 0;\n }\n select {\n padding: 0 0.5em;\n }\n}\n\n@barcelonetaPath:'/Volumes/WORKSPACE2/sozmap/src/plonetheme.barceloneta/plonetheme/barceloneta/theme';@bootstrap-badges:'badges.less';@bootstrap-basic:'navbar.less';@bootstrap-button-groups:'button-groups.less';@bootstrap-buttons:'close.less';@bootstrap-dropdown:'dropdowns.less';@bootstrap-glyphicons:'glyphicons.less';@bootstrap-mixins:'mixins.less';@bootstrap-modal:'modals.less';@bootstrap-progress-bars:'progress-bars.less';@bootstrap-variables:'variables.less';@bowerPath:'/Volumes/WORKSPACE/.buildout/eggs/plone.staticresources-1.1.0-py3.7.egg/plone/staticresources/static/components/';@bundle-leaflet:'bundle-leaflet.less';@collectionfilter:'collectionfilter.less';@collectionfilter-bundle:'collectionfilter.less';@collective-venue:'styles.less';@collective-venue-bundle:'styles.less';@dropzone:'dropzone.css';@font-awesome:'font-awesome-integration.less';@icon-font-path:\"../fonts/\";@isMockup:false;@isPlone:false;@jqtree:'jqtree.css';@jquery_recurrenceinput:'jquery.recurrenceinput.css';@layouts-editor:'layouts-editor.less';@leaflet:'leaflet.css';@leaflet-awesomemarkers:'leaflet.awesome-markers.css';@leaflet-fullscreen:'leaflet.fullscreen.css';@leaflet-geosearch:'l.geosearch.css';@leaflet-locatecontrol:'L.Control.Locate.css';@leaflet-markercluster:'MarkerCluster.Default.css';@leaflet-minimap:'Control.MiniMap.min.css';@leaflet-simplemarkers:'Control.SimpleMarkers.css';@mockup-patterns-autotoc:'pattern.autotoc.less';@mockup-patterns-datatables:'pattern.datatables.less';@mockup-patterns-filemanager:'pattern.filemanager.less';@mockup-patterns-livesearch:'pattern.livesearch.less';@mockup-patterns-markspeciallinks:'pattern.markspeciallinks.less';@mockup-patterns-modal:'pattern.modal.less';@mockup-patterns-pickadate:'pattern.pickadate.less';@mockup-patterns-querystring:'pattern.querystring.less';@mockup-patterns-recurrence:'pattern.recurrence.less';@mockup-patterns-relateditems:'pattern.relateditems.less';@mockup-patterns-resourceregistry:'pattern.resourceregistry.less';@mockup-patterns-select2:'pattern.select2.less';@mockup-patterns-structure:'pattern.structure.less';@mockup-patterns-thememapper:'pattern.thememapper.less';@mockup-patterns-tinymce:'pattern.tinymce.less';@mockup-patterns-tooltip:'pattern.tooltip.less';@mockup-patterns-tree:'pattern.tree.less';@mockup-patterns-upload:'pattern.upload.less';@mockup-popover:'popover.less';@mockupPath:'/Volumes/WORKSPACE/.buildout/eggs/mockup-3.0.2-py3.7.egg/mockup/patterns/';@mockuplessPath:'/Volumes/WORKSPACE/.buildout/eggs/mockup-3.0.2-py3.7.egg/mockup/less/';@mosaic:'mosaic.pattern.less';@pat-leaflet:'pat-leaflet.css';@photoswipe:'photoswipe.css';@photoswipe-ui:'default-skin.css';@picker:'classic.css';@picker_date:'classic.date.css';@picker_time:'classic.time.css';@pikaday:'pikaday.css';@plone:'plone.less';@plone-container-lg:1170px;@plone-container-md:970px;@plone-container-sm:750px;@plone-gray-light:lighten(#000, 46.5%);@plone-gray-lighter:lighten(#000, 80%);@plone-left-toolbar:60px;@plone-left-toolbar-expanded:120px;@plone-link-color:rgba(0,123,179,1);@plone-logged-in:'plone-logged-in.less';@plone-patterns-toolbar:'pattern.toolbar.less';@plone-screen-lg-min:1200px;@plone-screen-md-max:(@plone-screen-lg-min - 1);@plone-screen-md-min:992px;@plone-screen-sm-max:(@plone-screen-md-min - 1);@plone-screen-sm-min:768px;@plone-screen-xs-max:(@plone-screen-sm-min - 1);@plone-screen-xs-min:480px;@plone-toolbar-bg:rgba(0,0,0,.9);@plone-toolbar-draft-color:rgb(250,184,42);@plone-toolbar-font-primary:Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-font-secondary:Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-internal-color:rgb(250,184,42);@plone-toolbar-internally-published-color:rgb(136,61,250);@plone-toolbar-link:rgba(0,123,179,1);@plone-toolbar-pending-color:rgb(226,231,33);@plone-toolbar-private-color:rgb(196,24,60);@plone-toolbar-published-color:rgba(0,123,179,1);@plone-toolbar-separator-color:rgba(255,255,255,.17);@plone-toolbar-submenu-bg:rgba(45,45,45,.96);@plone-toolbar-submenu-header-color:lighten(#000, 80%);@plone-toolbar-submenu-text-color:lighten(#000, 90%);@plone-toolbar-submenu-width:180px;@plone-toolbar-text-color:rgba(255,255,255,1);@plone_app_imagecropping:'bundle.less';@plone_app_imagecropping_cropper:'cropper.css';@plone_app_imagecropping_cropscaleselect:'cropscaleselect.less';@resource-plone-app-jquerytools-dateinput-js:'jquery.tools.dateinput.css';@resource-plone-app-jquerytools-js:'jquery.tools.overlay.css';@resourceregistry:'resourceregistry.less';@select2:'select2.css';@sitePath:'/';@staticPath:'/Volumes/WORKSPACE/.buildout/eggs/plone.staticresources-1.1.0-py3.7.egg/plone/staticresources/static';@thememapper:'thememapper.less';@tinymce:'content.min.css';@tinymce-default-styles:'tinymce-styles.css';@tinymce-visualblocks:'visualblocks.css';\n@barcelonetaPath: '/Volumes/WORKSPACE2/sozmap/src/plonetheme.barceloneta/plonetheme/barceloneta/theme';@bootstrap-badges: 'badges.less';@bootstrap-basic: 'navbar.less';@bootstrap-button-groups: 'button-groups.less';@bootstrap-buttons: 'close.less';@bootstrap-dropdown: 'dropdowns.less';@bootstrap-glyphicons: 'glyphicons.less';@bootstrap-mixins: 'mixins.less';@bootstrap-modal: 'modals.less';@bootstrap-progress-bars: 'progress-bars.less';@bootstrap-variables: 'variables.less';@bowerPath: '/Volumes/WORKSPACE/.buildout/eggs/plone.staticresources-1.1.0-py3.7.egg/plone/staticresources/static/components/';@bundle-leaflet: 'bundle-leaflet.less';@collectionfilter: 'collectionfilter.less';@collectionfilter-bundle: 'collectionfilter.less';@collective-venue: 'styles.less';@collective-venue-bundle: 'styles.less';@dropzone: 'dropzone.css';@font-awesome: 'font-awesome-integration.less';@icon-font-path: \"../fonts/\";@isMockup: false;@isPlone: false;@jqtree: 'jqtree.css';@jquery_recurrenceinput: 'jquery.recurrenceinput.css';@layouts-editor: 'layouts-editor.less';@leaflet: 'leaflet.css';@leaflet-awesomemarkers: 'leaflet.awesome-markers.css';@leaflet-fullscreen: 'leaflet.fullscreen.css';@leaflet-geosearch: 'l.geosearch.css';@leaflet-locatecontrol: 'L.Control.Locate.css';@leaflet-markercluster: 'MarkerCluster.Default.css';@leaflet-minimap: 'Control.MiniMap.min.css';@leaflet-simplemarkers: 'Control.SimpleMarkers.css';@mockup-patterns-autotoc: 'pattern.autotoc.less';@mockup-patterns-datatables: 'pattern.datatables.less';@mockup-patterns-filemanager: 'pattern.filemanager.less';@mockup-patterns-livesearch: 'pattern.livesearch.less';@mockup-patterns-markspeciallinks: 'pattern.markspeciallinks.less';@mockup-patterns-modal: 'pattern.modal.less';@mockup-patterns-pickadate: 'pattern.pickadate.less';@mockup-patterns-querystring: 'pattern.querystring.less';@mockup-patterns-recurrence: 'pattern.recurrence.less';@mockup-patterns-relateditems: 'pattern.relateditems.less';@mockup-patterns-resourceregistry: 'pattern.resourceregistry.less';@mockup-patterns-select2: 'pattern.select2.less';@mockup-patterns-structure: 'pattern.structure.less';@mockup-patterns-thememapper: 'pattern.thememapper.less';@mockup-patterns-tinymce: 'pattern.tinymce.less';@mockup-patterns-tooltip: 'pattern.tooltip.less';@mockup-patterns-tree: 'pattern.tree.less';@mockup-patterns-upload: 'pattern.upload.less';@mockup-popover: 'popover.less';@mockupPath: '/Volumes/WORKSPACE/.buildout/eggs/mockup-3.0.2-py3.7.egg/mockup/patterns/';@mockuplessPath: '/Volumes/WORKSPACE/.buildout/eggs/mockup-3.0.2-py3.7.egg/mockup/less/';@mosaic: 'mosaic.pattern.less';@pat-leaflet: 'pat-leaflet.css';@photoswipe: 'photoswipe.css';@photoswipe-ui: 'default-skin.css';@picker: 'classic.css';@picker_date: 'classic.date.css';@picker_time: 'classic.time.css';@pikaday: 'pikaday.css';@plone: 'plone.less';@plone-container-lg: 1170px;@plone-container-md: 970px;@plone-container-sm: 750px;@plone-gray-light: lighten(#000, 46.5%);@plone-gray-lighter: lighten(#000, 80%);@plone-left-toolbar: 60px;@plone-left-toolbar-expanded: 120px;@plone-link-color: rgba(0,123,179,1);@plone-logged-in: 'plone-logged-in.less';@plone-patterns-toolbar: 'pattern.toolbar.less';@plone-screen-lg-min: 1200px;@plone-screen-md-max: (@plone-screen-lg-min - 1);@plone-screen-md-min: 992px;@plone-screen-sm-max: (@plone-screen-md-min - 1);@plone-screen-sm-min: 768px;@plone-screen-xs-max: (@plone-screen-sm-min - 1);@plone-screen-xs-min: 480px;@plone-toolbar-bg: rgba(0,0,0,.9);@plone-toolbar-draft-color: rgb(250,184,42);@plone-toolbar-font-primary: Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-font-secondary: Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-internal-color: rgb(250,184,42);@plone-toolbar-internally-published-color: rgb(136,61,250);@plone-toolbar-link: rgba(0,123,179,1);@plone-toolbar-pending-color: rgb(226,231,33);@plone-toolbar-private-color: rgb(196,24,60);@plone-toolbar-published-color: rgba(0,123,179,1);@plone-toolbar-separator-color: rgba(255,255,255,.17);@plone-toolbar-submenu-bg: rgba(45,45,45,.96);@plone-toolbar-submenu-header-color: lighten(#000, 80%);@plone-toolbar-submenu-text-color: lighten(#000, 90%);@plone-toolbar-submenu-width: 180px;@plone-toolbar-text-color: rgba(255,255,255,1);@plone_app_imagecropping: 'bundle.less';@plone_app_imagecropping_cropper: 'cropper.css';@plone_app_imagecropping_cropscaleselect: 'cropscaleselect.less';@resource-plone-app-jquerytools-dateinput-js: 'jquery.tools.dateinput.css';@resource-plone-app-jquerytools-js: 'jquery.tools.overlay.css';@resourceregistry: 'resourceregistry.less';@select2: 'select2.css';@sitePath: '/';@staticPath: '/Volumes/WORKSPACE/.buildout/eggs/plone.staticresources-1.1.0-py3.7.egg/plone/staticresources/static';@thememapper: 'thememapper.less';@tinymce: 'content.min.css';@tinymce-default-styles: 'tinymce-styles.css';@tinymce-visualblocks: 'visualblocks.css';"]} \ No newline at end of file +{"version":3,"sources":["src/collective/collectionfilter/resources/collectionfilter.less"],"names":[],"mappings":"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","sourcesContent":[".collectionSearch,\n.portlet.collectionSearch,\n.collectionFilter,\n.portlet.collectionFilter,\n.collectionSortOn,\n.portlet.collectionSortOn {\n position: relative;\n background: white;\n .edit-link {\n position: absolute;\n right: 0;\n bottom: -0.5em;\n label {\n display: none;\n }\n }\n}\n.horizontal {\n .collectionSearch,\n .portlet.collectionSearch,\n .collectionFilter,\n .portlet.collectionFilter,\n .collectionSortOn,\n .portlet.collectionSortOn {\n display: flex !important;\n justify-content: space-between;\n align-items: center;\n & > header {\n font-weight: bold;\n }\n .edit-link {\n right: unset;\n left: 0;\n }\n }\n}\n\n.collectionSearch .searchContent,\n.portlet.collectionSearch .searchContent,\n.collectionFilter .filterContent,\n.portlet.collectionFilter .filterContent,\n.collectionSortOn .filterContent,\n.portlet.collectionSortOn .filterContent {\n padding: 1em;\n > ul {\n padding: 0;\n list-style: none;\n position: relative;\n > li {\n list-style-type: none;\n padding: 2px;\n\n input[type=\"radio\"], input[type=\"checkbox\"] {\n margin: 0.3em 0.5em 0 0;\n line-height: normal;\n position: absolute;\n }\n label {\n display: inline-block;\n .filterControl, .filterLabel{\n display: inline-block;\n vertical-align: top;\n }\n .filterControl {\n margin-right: 0.5em;\n }\n .filterLabel{\n -webkit-hyphens: auto;\n -moz-hyphens: auto;\n hyphens: auto;\n padding-left: 1.5em;\n }\n }\n > a {\n padding: 0;\n display: block;\n border: 0;\n position: relative;\n z-index: 1;\n &:before {\n // left: 2px;\n font-size: 1.75em;\n line-height: 0.5;\n top: 0.15em;\n left: 0.15em;\n content: \"•\";\n display: inline;\n position: absolute;\n }\n &:hover {\n text-decoration: none;\n border-bottom: 0;\n }\n > span {\n padding-left: 1.5em;\n display: inline-block;\n hyphens: auto;\n }\n }\n &:hover {\n background: transparent;\n }\n }\n\n\n li.selected a {\n font-weight: bold;\n }\n ul {\n padding-left:15px;\n list-style: none;\n }\n }\n label {\n display: block;\n margin: 0;\n }\n select {\n padding: 0 0.5em;\n }\n}\n\n@barcelonetaPath:'/Users/dylanjay/Projects/download-cache/eggs/plonetheme.barceloneta-2.1.10-py2.7.egg/plonetheme/barceloneta/theme';@bootstrap-badges:'badges.less';@bootstrap-basic:'navbar.less';@bootstrap-button-groups:'button-groups.less';@bootstrap-buttons:'close.less';@bootstrap-dropdown:'dropdowns.less';@bootstrap-glyphicons:'glyphicons.less';@bootstrap-mixins:'mixins.less';@bootstrap-modal:'modals.less';@bootstrap-progress-bars:'progress-bars.less';@bootstrap-variables:'variables.less';@bowerPath:'/Users/dylanjay/Projects/download-cache/eggs/plone.staticresources-1.4.2-py2.7.egg/plone/staticresources/static/components/';@collectionfilter:'collectionfilter.less';@collectionfilter-bundle:'collectionfilter.less';@dropzone:'dropzone.css';@filemanager:'filemanager.less';@icon-font-path:\"../fonts/\";@isMockup:false;@isPlone:false;@jqtree:'jqtree.css';@jquery_recurrenceinput:'jquery.recurrenceinput.css';@mockup-patterns-autotoc:'pattern.autotoc.less';@mockup-patterns-datatables:'pattern.datatables.less';@mockup-patterns-filemanager:'pattern.filemanager.less';@mockup-patterns-livesearch:'pattern.livesearch.less';@mockup-patterns-markspeciallinks:'pattern.markspeciallinks.less';@mockup-patterns-modal:'pattern.modal.less';@mockup-patterns-pickadate:'pattern.pickadate.less';@mockup-patterns-querystring:'pattern.querystring.less';@mockup-patterns-recurrence:'pattern.recurrence.less';@mockup-patterns-relateditems:'pattern.relateditems.less';@mockup-patterns-resourceregistry:'pattern.resourceregistry.less';@mockup-patterns-select2:'pattern.select2.less';@mockup-patterns-structure:'pattern.structure.less';@mockup-patterns-thememapper:'pattern.thememapper.less';@mockup-patterns-tinymce:'pattern.tinymce.less';@mockup-patterns-tooltip:'pattern.tooltip.less';@mockup-patterns-tree:'pattern.tree.less';@mockup-patterns-upload:'pattern.upload.less';@mockup-popover:'popover.less';@mockupPath:'/Users/dylanjay/Projects/download-cache/eggs/mockup-3.2.5-py2.7.egg/mockup/patterns/';@mockuplessPath:'/Users/dylanjay/Projects/download-cache/eggs/mockup-3.2.5-py2.7.egg/mockup/less/';@picker:'classic.css';@picker_date:'classic.date.css';@picker_time:'classic.time.css';@plone:'plone.less';@plone-container-lg:1170px;@plone-container-md:970px;@plone-container-sm:750px;@plone-datatables:'plone-datatables.less';@plone-editor-tools:'plone-editor-tools.less';@plone-gray-light:lighten(#000, 46.5%);@plone-gray-lighter:lighten(#000, 80%);@plone-left-toolbar:60px;@plone-left-toolbar-expanded:120px;@plone-link-color:rgba(0,123,179,1);@plone-logged-in:'plone-logged-in.less';@plone-patterns-toolbar:'pattern.toolbar.less';@plone-screen-lg-min:1200px;@plone-screen-md-max:(@plone-screen-lg-min - 1);@plone-screen-md-min:992px;@plone-screen-sm-max:(@plone-screen-md-min - 1);@plone-screen-sm-min:768px;@plone-screen-xs-max:(@plone-screen-sm-min - 1);@plone-screen-xs-min:480px;@plone-tinymce:'plone-tinymce.less';@plone-toolbar-bg:rgba(0,0,0,.9);@plone-toolbar-draft-color:rgb(250,184,42);@plone-toolbar-font-primary:Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-font-secondary:Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-internal-color:rgb(250,184,42);@plone-toolbar-internally-published-color:rgb(136,61,250);@plone-toolbar-link:rgba(0,123,179,1);@plone-toolbar-pending-color:rgb(226,231,33);@plone-toolbar-private-color:rgb(196,24,60);@plone-toolbar-published-color:rgba(0,123,179,1);@plone-toolbar-separator-color:rgba(255,255,255,.17);@plone-toolbar-submenu-bg:rgba(45,45,45,.96);@plone-toolbar-submenu-header-color:lighten(#000, 80%);@plone-toolbar-submenu-text-color:lighten(#000, 90%);@plone-toolbar-submenu-width:180px;@plone-toolbar-text-color:rgba(255,255,255,1);@resource-plone-app-jquerytools-dateinput-js:'jquery.tools.dateinput.css';@resource-plone-app-jquerytools-js:'jquery.tools.overlay.css';@resourceregistry:'resourceregistry.less';@select2:'select2.css';@sitePath:'/';@staticPath:'/Users/dylanjay/Projects/download-cache/eggs/plone.staticresources-1.4.2-py2.7.egg/plone/staticresources/static';@thememapper:'thememapper.less';@tinymce:'content.min.css';@tinymce-default-styles:'tinymce-styles.css';@tinymce-visualblocks:'visualblocks.css';\n@barcelonetaPath: '/Users/dylanjay/Projects/download-cache/eggs/plonetheme.barceloneta-2.1.10-py2.7.egg/plonetheme/barceloneta/theme';@bootstrap-badges: 'badges.less';@bootstrap-basic: 'navbar.less';@bootstrap-button-groups: 'button-groups.less';@bootstrap-buttons: 'close.less';@bootstrap-dropdown: 'dropdowns.less';@bootstrap-glyphicons: 'glyphicons.less';@bootstrap-mixins: 'mixins.less';@bootstrap-modal: 'modals.less';@bootstrap-progress-bars: 'progress-bars.less';@bootstrap-variables: 'variables.less';@bowerPath: '/Users/dylanjay/Projects/download-cache/eggs/plone.staticresources-1.4.2-py2.7.egg/plone/staticresources/static/components/';@collectionfilter: 'collectionfilter.less';@collectionfilter-bundle: 'collectionfilter.less';@dropzone: 'dropzone.css';@filemanager: 'filemanager.less';@icon-font-path: \"../fonts/\";@isMockup: false;@isPlone: false;@jqtree: 'jqtree.css';@jquery_recurrenceinput: 'jquery.recurrenceinput.css';@mockup-patterns-autotoc: 'pattern.autotoc.less';@mockup-patterns-datatables: 'pattern.datatables.less';@mockup-patterns-filemanager: 'pattern.filemanager.less';@mockup-patterns-livesearch: 'pattern.livesearch.less';@mockup-patterns-markspeciallinks: 'pattern.markspeciallinks.less';@mockup-patterns-modal: 'pattern.modal.less';@mockup-patterns-pickadate: 'pattern.pickadate.less';@mockup-patterns-querystring: 'pattern.querystring.less';@mockup-patterns-recurrence: 'pattern.recurrence.less';@mockup-patterns-relateditems: 'pattern.relateditems.less';@mockup-patterns-resourceregistry: 'pattern.resourceregistry.less';@mockup-patterns-select2: 'pattern.select2.less';@mockup-patterns-structure: 'pattern.structure.less';@mockup-patterns-thememapper: 'pattern.thememapper.less';@mockup-patterns-tinymce: 'pattern.tinymce.less';@mockup-patterns-tooltip: 'pattern.tooltip.less';@mockup-patterns-tree: 'pattern.tree.less';@mockup-patterns-upload: 'pattern.upload.less';@mockup-popover: 'popover.less';@mockupPath: '/Users/dylanjay/Projects/download-cache/eggs/mockup-3.2.5-py2.7.egg/mockup/patterns/';@mockuplessPath: '/Users/dylanjay/Projects/download-cache/eggs/mockup-3.2.5-py2.7.egg/mockup/less/';@picker: 'classic.css';@picker_date: 'classic.date.css';@picker_time: 'classic.time.css';@plone: 'plone.less';@plone-container-lg: 1170px;@plone-container-md: 970px;@plone-container-sm: 750px;@plone-datatables: 'plone-datatables.less';@plone-editor-tools: 'plone-editor-tools.less';@plone-gray-light: lighten(#000, 46.5%);@plone-gray-lighter: lighten(#000, 80%);@plone-left-toolbar: 60px;@plone-left-toolbar-expanded: 120px;@plone-link-color: rgba(0,123,179,1);@plone-logged-in: 'plone-logged-in.less';@plone-patterns-toolbar: 'pattern.toolbar.less';@plone-screen-lg-min: 1200px;@plone-screen-md-max: (@plone-screen-lg-min - 1);@plone-screen-md-min: 992px;@plone-screen-sm-max: (@plone-screen-md-min - 1);@plone-screen-sm-min: 768px;@plone-screen-xs-max: (@plone-screen-sm-min - 1);@plone-screen-xs-min: 480px;@plone-tinymce: 'plone-tinymce.less';@plone-toolbar-bg: rgba(0,0,0,.9);@plone-toolbar-draft-color: rgb(250,184,42);@plone-toolbar-font-primary: Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-font-secondary: Roboto, \"Helvetica Neue\", Helvetica, Arial, sans-serif;@plone-toolbar-internal-color: rgb(250,184,42);@plone-toolbar-internally-published-color: rgb(136,61,250);@plone-toolbar-link: rgba(0,123,179,1);@plone-toolbar-pending-color: rgb(226,231,33);@plone-toolbar-private-color: rgb(196,24,60);@plone-toolbar-published-color: rgba(0,123,179,1);@plone-toolbar-separator-color: rgba(255,255,255,.17);@plone-toolbar-submenu-bg: rgba(45,45,45,.96);@plone-toolbar-submenu-header-color: lighten(#000, 80%);@plone-toolbar-submenu-text-color: lighten(#000, 90%);@plone-toolbar-submenu-width: 180px;@plone-toolbar-text-color: rgba(255,255,255,1);@resource-plone-app-jquerytools-dateinput-js: 'jquery.tools.dateinput.css';@resource-plone-app-jquerytools-js: 'jquery.tools.overlay.css';@resourceregistry: 'resourceregistry.less';@select2: 'select2.css';@sitePath: '/';@staticPath: '/Users/dylanjay/Projects/download-cache/eggs/plone.staticresources-1.4.2-py2.7.egg/plone/staticresources/static';@thememapper: 'thememapper.less';@tinymce: 'content.min.css';@tinymce-default-styles: 'tinymce-styles.css';@tinymce-visualblocks: 'visualblocks.css';"]} \ No newline at end of file diff --git a/src/collective/collectionfilter/resources/collectionfilter-bundle-compiled.js b/src/collective/collectionfilter/resources/collectionfilter-bundle-compiled.js index 6606810b..b3b712b8 100644 --- a/src/collective/collectionfilter/resources/collectionfilter-bundle-compiled.js +++ b/src/collective/collectionfilter/resources/collectionfilter-bundle-compiled.js @@ -1,2 +1,2 @@ -define("collectionfilter",["jquery","pat-base","mockup-patterns-contentloader"],function(n,t,e){return t.extend({name:"collectionfilter",trigger:".pat-collectionfilter",parser:"mockup",contentloader:e,defaults:{collectionUUID:"",collectionURL:"",reloadURL:"",ajaxLoad:!0,contentSelector:"#content-core"},_initmap_cycles:2,_zoomed:!1,init:function(){var t;if(this.$el.unbind("collectionfilter:reload"),this.$el.on("collectionfilter:reload",function(t,e){e.noReloadSearch&&this.$el.hasClass("collectionSearch")||e.noReloadMap&&this.$el.hasClass("collectionMaps")||e.collectionUUID===this.options.collectionUUID&&this.reload(e.targetFilterURL)}.bind(this)),this.$el.hasClass("collectionSearch")&&this.options.ajaxLoad)return n('button[type="submit"]',this.$el).hide(),n("form",this.$el).on("submit",function(t){t.preventDefault()}),void n('input[name="SearchableText"]',this.$el).on("keyup",function(o){clearTimeout(t),0 ul { padding: 0; list-style: none; @@ -50,21 +51,23 @@ list-style-type: none; padding: 2px; - input[type="radio"], input[type="checkbox"] { + input[type="radio"], + input[type="checkbox"] { margin: 0.3em 0.5em 0 0; line-height: normal; position: absolute; } label { display: inline-block; - .filterControl, .filterLabel{ - display: inline-block; - vertical-align: top; + .filterControl, + .filterLabel { + display: inline-block; + vertical-align: top; } .filterControl { - margin-right: 0.5em; + margin-right: 0.5em; } - .filterLabel{ + .filterLabel { -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; @@ -102,13 +105,12 @@ } } - li.selected a { - font-weight: bold; + font-weight: bold; } ul { - padding-left:15px; - list-style: none; + padding-left: 15px; + list-style: none; } } label { @@ -119,3 +121,23 @@ padding: 0 0.5em; } } + +.pat-collectionfilter .filterInfoContent { + margin-top: 0.5em; + + .filterInfoContent__term { + @media (min-width: 480px) { + box-sizing: border-box; + float: left; + clear: left; + padding-right: 5px; + } + } + .filterInfoContent__definition { + } +} + +.sectionFilter li.selected a > span { + font-weight: bold; +} + diff --git a/src/collective/collectionfilter/tests/robot/keywords.robot b/src/collective/collectionfilter/tests/robot/keywords.robot index 8060e001..3f4ec474 100644 --- a/src/collective/collectionfilter/tests/robot/keywords.robot +++ b/src/collective/collectionfilter/tests/robot/keywords.robot @@ -309,8 +309,8 @@ My collection has a collection sorting run keyword unless ${USE_TILES} My collection has a collection sorting portlet ${sort_on} My collection has a collection info - [Arguments] ${header}="Current Filter" @{templates} ${hide_when}=${None} - run keyword if ${USE_TILES} My collection has a collection info tile ${header} @{templates} hide_when=${hide_when} + [Arguments] ${header}="Current Filter" @{templates} ${hide_when}=${None} ${as_title}=${False} + run keyword if ${USE_TILES} My collection has a collection info tile ${header} @{templates} hide_when=${hide_when} as_title=${as_title} run keyword unless ${USE_TILES} My collection has a collection info portlet ${header} @{templates} hide_when=${hide_when} I'm viewing the collection @@ -453,7 +453,7 @@ My collection has a collection search tile Save mosaic page My collection has a collection info tile - [Arguments] ${header} @{templates} ${hide_when}=${None} + [Arguments] ${header} @{templates} ${hide_when}=${None} ${as_title}=${False} Go to ${PLONE_URL}/testdoc/edit Add info tile @{templates} hide_when=${hide_when} @@ -544,13 +544,14 @@ Add search tile Add info tile - [Arguments] @{templates} ${hide_when}=${None} ${collection_name}=${None} + [Arguments] @{templates} ${hide_when}=${None} ${collection_name}=${None} ${as_title}=${False} Insert tile "Collection Filter Info" run keyword if $collection_name set relateditem formfield-collective-collectionfilter-tiles-info-target_collection ${collection_name} # Complete filter form Set Info Settings collective-collectionfilter-tiles-info @{templates} hide_when=${hide_when} - # Run Keyword by label Content Selector Input Text .contentlisting-tile + Run Keyword by label Content Selector Input Text .contentlisting-tile + run keyword if ${as_title} Set Options Display as Title Click element css=.pattern-modal-buttons #buttons-save Drag tile diff --git a/src/collective/collectionfilter/tests/robot/test_infoportlets.robot b/src/collective/collectionfilter/tests/robot/test_infoportlets.robot new file mode 100644 index 00000000..74c558c8 --- /dev/null +++ b/src/collective/collectionfilter/tests/robot/test_infoportlets.robot @@ -0,0 +1,86 @@ + +*** Settings ***************************************************************** + +Resource keywords.robot + +# Library Remote ${PLONE_URL}/RobotRemote + +Test Setup Default Setup +Test Teardown Default Teardown + + +*** Test Cases *************************************************************** + +Scenario: Info on single choice filter + Given I've got a site with a collection + and my collection has a collection filter Subject + and my collection has a collection info Current Filters value_quoted_filter + When I'm viewing the collection + and Click Input "Süper (2)" + and Should be 2 collection results + then Should be Info with text: "Süper" Subject + +Scenario: Info on multiple choice filter + Given I've got a site with a collection + and my collection has a collection info Current Filters filter_colon_value + and my collection has a collection filter Subject and checkboxes_dropdowns + When I'm viewing the collection + and Click Input "Süper (2)" + and Should be 2 collection results + and Click Input "Evänt (1)" + and Should be 1 collection results + then Should be Info with text: Subject: Süper/Evänt + +Scenario: Info on result count + Given I've got a site with a collection + and my collection has a collection info Current Filters result_count + and my collection has a collection filter Subject and checkboxes_dropdowns + When I'm viewing the collection + and Click Input "Süper (2)" + and Should be 2 collection results + then Should be Info with text: 2 + +Scenario: Info on keyword search + Given I've got a site with a collection + and my collection has a collection info Current Filters search_quoted + and my collection has a collection search + When I'm viewing the collection + and I search for "Document" + then Should be Info with text: "Document" + +Scenario: Combine info templates + Given I've got a site with a collection + and my collection has a collection info Current Filters search_for search_quoted filter_colon_value comma with result_count results + and my collection has a collection search + and my collection has a collection filter Subject and checkboxes_dropdowns + When I'm viewing the collection + and Click Input "Süper (2)" + and Should be 2 collection results + and Click Input "Evänt (1)" + and Should be 1 collection results + and I search for "Event" + Then Should be Info with text: Search for "Event" Subject: Süper/Evänt, with 1 result + +Scenario: Hide on any filter + Given I've got a site with a collection + and my collection has a collection info Current Filters result_count hide_when=any_filter + and my collection has a collection filter Subject + When I'm viewing the collection + and Should be Info with text: 3 + and Click Input "Süper (2)" + and Should be 2 collection results + then should be no Info + and Click Input "All (3)" + then Should be Info with text: 3 + and Click Input "Süper (2)" + then should be no Info + +Scenario: Display as title + Given I've got a site with a collection + and my collection has a collection info Current Filters search_for search_quoted filter_colon_value comma with result_count results as_title=${True} + and my collection has a collection filter Subject + When I'm viewing the collection + and Click Input "Süper (2)" + and Should be 2 collection results + then Should be Info with text: Subject: Süper, with 2 results + and run keyword if ${USE_TILES} Page Should Contain Element css=h1 limit=2 \ No newline at end of file diff --git a/src/collective/collectionfilter/tiles/configure.zcml b/src/collective/collectionfilter/tiles/configure.zcml index 062c1b43..f6741004 100644 --- a/src/collective/collectionfilter/tiles/configure.zcml +++ b/src/collective/collectionfilter/tiles/configure.zcml @@ -63,6 +63,18 @@ template="sorting.pt" /> + + + + + + + +

+ Title + +

+ + + diff --git a/src/collective/collectionfilter/tiles/info.py b/src/collective/collectionfilter/tiles/info.py new file mode 100644 index 00000000..10236f21 --- /dev/null +++ b/src/collective/collectionfilter/tiles/info.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from collective.collectionfilter.baseviews import BaseInfoView +from collective.collectionfilter.interfaces import ICollectionFilterInfoTile +from collective.collectionfilter.tiles import BaseFilterTile +from plone.supermodel.model import Schema +from zope.interface import implementer + + +class IInfoTile(Schema, ICollectionFilterInfoTile): + pass + + +@implementer(IInfoTile) +class InfoTile(BaseFilterTile, BaseInfoView): + """Info Tile""" diff --git a/src/collective/collectionfilter/tiles/profiles/default-geolocation/registry.xml b/src/collective/collectionfilter/tiles/profiles/default-geolocation/registry.xml index 1736d2ed..86074b02 100644 --- a/src/collective/collectionfilter/tiles/profiles/default-geolocation/registry.xml +++ b/src/collective/collectionfilter/tiles/profiles/default-geolocation/registry.xml @@ -5,6 +5,7 @@ collective.collectionfilter.tiles.filter collective.collectionfilter.tiles.search collective.collectionfilter.tiles.sortOn + collective.collectionfilter.tiles.info collective.collectionfilter.tiles.maps @@ -51,6 +52,20 @@ 10 + + collective.collectionfilter.tiles.info + Collection Filter Info + advanced + app + + false + true + false + false + 10 + + collective.collectionfilter.tiles.maps diff --git a/src/collective/collectionfilter/tiles/profiles/default/registry.xml b/src/collective/collectionfilter/tiles/profiles/default/registry.xml index 556f052e..0369ba23 100644 --- a/src/collective/collectionfilter/tiles/profiles/default/registry.xml +++ b/src/collective/collectionfilter/tiles/profiles/default/registry.xml @@ -5,6 +5,7 @@ collective.collectionfilter.tiles.filter collective.collectionfilter.tiles.search collective.collectionfilter.tiles.sortOn + collective.collectionfilter.tiles.info @@ -50,4 +51,19 @@ 10 + + collective.collectionfilter.tiles.info + Collection Filter Info + advanced + app + + false + true + false + false + 10 + + + diff --git a/src/collective/collectionfilter/tiles/profiles/uninstall/registry.xml b/src/collective/collectionfilter/tiles/profiles/uninstall/registry.xml index a4f7173c..7a4608e6 100644 --- a/src/collective/collectionfilter/tiles/profiles/uninstall/registry.xml +++ b/src/collective/collectionfilter/tiles/profiles/uninstall/registry.xml @@ -5,6 +5,7 @@ collective.collectionfilter.tiles.filter collective.collectionfilter.tiles.search collective.collectionfilter.tiles.sortOn + collective.collectionfilter.tiles.info collective.collectionfilter.tiles.maps @@ -24,6 +25,11 @@ prefix="plone.app.mosaic.app_tiles.collective_collectionfilter_tiles_sort_on" interface="plone.app.mosaic.interfaces.ITile"/> + + 0: + title = container[0].Title + # ctype = container[0].portal_type.lower() + else: + title = path.split("/")[-1] + return title + + +def path_indent(path): + level = max(0, len(path.split("/")) - 1) # Put Top level at same level as All as easy enough it distiguish + css_class = u"pathLevel{level}".format(level=level) + return css_class + + +def relative_to_absolute_path(path): + # Ensure query string only needs relative path. Internal search needs full path + return '/'.join(list(plone.api.portal.get().getPhysicalPath()) + path.split("/")) + + +def sort_path(it): + return it["url"] + + +def sort_title(it): + return it["title"].lower() + + @implementer(IGroupByCriteria) class GroupByCriteria: """Global utility for retrieving and manipulating groupby criterias. @@ -233,3 +298,105 @@ def SortOnIndexesVocabulary(context): SimpleTerm(title=_(v["title"]), value=k) for k, v in sortable_indexes.items() ] # noqa return SimpleVocabulary(items) + + +DEFAULT_TEMPLATES = OrderedDict( + [ + ("search_for", (u"Search for", u"string:Search for")), + ( + "filter_colon_value", + ( + u"{Filter}: {value}, ...", + u'python: u", ".join(u"{}: {}".format(k,u"/".join(v)) for k, v in query)', + ), + ), + ( + "value_comma", + ( + u"{value}, ...", + u'python: ", u".join(u"{}".format(v) for _,values in query for v in values)', + ), + ), + ( + "value_quoted_filter", + ( + u'"{value}" {Filter}, ...', + u"""python: u", ".join(u'"{}" {}'.format(u"/".join(v),k) for k, v in query)""", + ), + ), + ("with_keywords", (u"with keywords", u"string:with keywords")), + ( + "search_quoted", + (u'"{search}"', u"""python: u'"{}"'.format(search) if search else '' """), + ), + ("hyphen", (u" - ", u"string:-")), + ("comma", (u", ", u"string:, ")), + ("has_returned", (u"has returned", u"string:has returned")), + ("with", (u"with", u"string:with")), + ("result_count", (u"{results}", u"python:str(results)")), + ("results", (u"results", u"python: 'result' if results == 1 else 'results'")), + ( + "documents", + (u"documents", u"python: 'document' if results == 1 else 'documents'"), + ), + ] +) + + +@provider(IVocabularyFactory) +def TemplatePartsVocabulary(context): + """ + The vocabulary consists of a tuple containing the id of the template part + and a tuple within that tuple containing the title viewed by in the interface and the TAL expression for the template part. + """ + items = [ + SimpleTerm(token=k, title=v[0], value=v[1]) + for k, v in DEFAULT_TEMPLATES.items() + ] + groupby = getUtility(IGroupByCriteria).groupby + for groupby_criteria in groupby.keys(): + items.append(SimpleTerm( + token=u"value_{}".format(groupby_criteria), + title=u"{{Value of {}}}".format(_(groupby_criteria)), + value=u"""python:dict(query).get('{0}', '')""".format(groupby_criteria), + )) + items.append(SimpleTerm( + token=u"name_{}".format(groupby_criteria), + title=u"{}".format(_(groupby_criteria)), + value=u"""string:{}""".format(groupby_criteria), + )) + + return SimpleVocabulary(items) + + +def get_conditions(): + items = [ + ("any_filter", u"Any filter", "query"), + ("no_filter", u"No filter", "not:query"), + ("search", u"Keyword search", "search"), + ("no_search", u"Keyword search", "not:search"), + ("results", u"Results", "results"), + ("no_results", u"No Results", "not:results"), + ] + groupby = getUtility(IGroupByCriteria).groupby + for it in groupby.keys(): + i = ( + "filter_{}".format(it), + u"Filtered by {}".format(_(it)), + "python:'{}' in query".format(it), + ) + items.append(i) + i = ( + "no_filter_{}".format(it), + u"Not Filtered by {}".format(_(it)), + "python:'{}' not in query".format(it), + ) + items.append(i) + return items + + +@provider(IVocabularyFactory) +def InfoConditionsVocabulary(context): + + items = [SimpleTerm(title=title, value=id) for id, title, _ in get_conditions()] + return SimpleVocabulary(items) diff --git a/test-5.2.x.cfg b/test-5.2.x.cfg index 4f68bd41..dc219058 100644 --- a/test-5.2.x.cfg +++ b/test-5.2.x.cfg @@ -2,7 +2,7 @@ extends = https://raw.githubusercontent.com/collective/buildout.plonetest/master/test-5.2.x.cfg https://raw.githubusercontent.com/collective/buildout.plonetest/master/qa.cfg - https://raw.githubusercontent.com/plone/plone.app.mosaic/master/versions.cfg +# https://raw.githubusercontent.com/plone/plone.app.mosaic/master/versions.cfg base.cfg parts += @@ -22,3 +22,4 @@ extensions += #setuptools = #zc.buildout = coverage = >=3.7 +plone.formwidget.geolocation = < 3.0.0