diff --git a/cmsplugin_cascade/bootstrap4/buttons.py b/cmsplugin_cascade/bootstrap4/buttons.py index 02a13a8aa..97411f986 100644 --- a/cmsplugin_cascade/bootstrap4/buttons.py +++ b/cmsplugin_cascade/bootstrap4/buttons.py @@ -69,7 +69,7 @@ def get_instance(cls): class BootstrapButtonMixin(IconPluginMixin): require_parent = True - parent_classes = ('BootstrapColumnPlugin', 'SimpleWrapperPlugin',) + parent_classes = ('BootstrapColumnPlugin', 'SimpleWrapperPlugin', 'NavbarNavItemsPlugin') render_template = 'cascade/bootstrap4/button.html' allow_children = False default_css_class = 'btn' @@ -188,6 +188,8 @@ def get_form(self, request, obj=None, **kwargs): @classmethod def get_css_classes(cls, obj): css_classes = cls.super(BootstrapButtonPlugin, cls).get_css_classes(obj) + if obj.parent.plugin_type == 'NavbarNavItemsPlugin': + css_classes.insert(0,'nav-link text-left') if obj.glossary.get('stretched_link'): css_classes.append('stretched_link') return css_classes diff --git a/cmsplugin_cascade/bootstrap4/image.py b/cmsplugin_cascade/bootstrap4/image.py index 8e42812df..d1231dd71 100644 --- a/cmsplugin_cascade/bootstrap4/image.py +++ b/cmsplugin_cascade/bootstrap4/image.py @@ -107,6 +107,7 @@ def render(self, context, instance, placeholder): try: tags = get_image_tags(instance) except Exception as exc: + tags = None logger.warning("Unable generate image tags. Reason: {}".format(exc)) tags = tags if tags else {} if 'extra_styles' in tags: diff --git a/cmsplugin_cascade/bootstrap4/mixins.py b/cmsplugin_cascade/bootstrap4/mixins.py index 8187359b1..55df39c5a 100644 --- a/cmsplugin_cascade/bootstrap4/mixins.py +++ b/cmsplugin_cascade/bootstrap4/mixins.py @@ -144,6 +144,73 @@ def paddings(cls): return glossary_fields @property + def flex_directions(cls): + glossary_fields = [] + choices_format = [ + ('flex-{}row', _("horizontal")), + ('flex-{}row-reverse', _("horizontal reverse")), + ('flex-{}column', _("Vertical")), + ('flex-{}column-reverse', _("Vertical reverse")), + ] + for bp in Breakpoint.range(Breakpoint.xs, Breakpoint.xl): + if bp == Breakpoint.xs: + choices = [(c.format(''), l ) for c, l in choices_format] + choices.insert(0, ('', _("No Flex Directions"))) + else: + choices = [(c.format(bp.name + '-'), l) for c, l in choices_format ] + choices.insert(0, ('', _("Inherit from above"))) + glossary_fields.append(GlossaryField( + widgets.Select(choices=choices), + label=format_lazy(_("Flex Directions for {breakpoint}"), breakpoint=bp.label), + name='Flex_{}'.format(bp.name), + initial='' + )) + return glossary_fields + + @property + def display_propertys(cls): + glossary_fields = [] + choices_format = [ + ('d-{}{}', _("horizontal")), + ] + notation = ['none', 'inline', 'inline-block', 'block', 'table', 'table-cell', 'table-row', 'flex', 'inline-flex'] + for bp in Breakpoint.range(Breakpoint.xs, Breakpoint.xl): + if bp == Breakpoint.xs: + choices = [(c.format('', n), c.format('', n)) for c, l in choices_format for n in notation] + choices.insert(0, ('', _("No Flex Directions"))) + else: + choices = [(c.format(bp.name + '-', n), + c.format('', n)) for c, l in choices_format for n in notation] + choices.insert(0, ('', _("Inherit from above"))) + glossary_fields.append(GlossaryField( + widgets.Select(choices=choices), + label=format_lazy(_("Flex Directions for {breakpoint}"), breakpoint=bp.label), + name='Flex_{}'.format(bp.name), + )) + return glossary_fields + + @property + def justify_content(cls): + glossary_fields = [] + choices_format = [ + ('justify-content-{}{}', _("Justify Content")), + ] + notation = [ 'start', 'end', 'center', 'between', 'around'] + for bp in Breakpoint.range(Breakpoint.xs, Breakpoint.xl): + if bp == Breakpoint.xs: + choices = [(c.format('', n), c.format('', n)) for c, l in choices_format for n in notation] + choices.insert(0, ('', _("No Justify content"))) + else: + choices = [(c.format(bp.name + '-', n), + c.format(bp.name + '-', n)) for c, l in choices_format for n in notation] + choices.insert(0, ('', _("Inherit from above"))) + glossary_fields.append(GlossaryField( + widgets.Select(choices=choices), + label=format_lazy(_("Justify Content for {breakpoint}"), breakpoint=bp.label), + name='Justify_content_{}'.format(bp.name), + )) + return glossary_fields + def floats(cls): glossary_fields = [] choices_format = [ diff --git a/cmsplugin_cascade/bootstrap4/navbar.py b/cmsplugin_cascade/bootstrap4/navbar.py new file mode 100644 index 000000000..a658663f1 --- /dev/null +++ b/cmsplugin_cascade/bootstrap4/navbar.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +try: + from html.parser import HTMLParser # py3 +except ImportError: + from HTMLParser import HTMLParser # py2 + +from django.forms import widgets, ModelChoiceField +from django.utils.html import format_html +from django.utils.translation import ungettext_lazy, ugettext_lazy as _ +from django.forms.models import ModelForm + +from cms.plugin_pool import plugin_pool +from filer.models.imagemodels import Image + +from cmsplugin_cascade import app_settings +from cmsplugin_cascade.fields import GlossaryField + +from cmsplugin_cascade.bootstrap4.plugin_base import BootstrapPluginBase + +from cmsplugin_cascade.image import ImageAnnotationMixin, ImagePropertyMixin, ImageFormMixin +from cmsplugin_cascade.bootstrap4.image import get_image_tags +from cmsplugin_cascade.bootstrap4.picture import BootstrapPicturePlugin, get_picture_elements +from cmsplugin_cascade.bootstrap4.container import ContainerBreakpointsWidget, ContainerGridMixin, get_widget_choices +from cmsplugin_cascade.widgets import MultipleCascadingSizeWidget, CascadingSizeWidget +from cmsplugin_cascade.bootstrap4.grid import Breakpoint + +from cmsplugin_cascade.link.config import LinkPluginBase, LinkElementMixin, LinkForm +from . import grid + +import logging +logger = logging.getLogger('cascade') + + +class NavbarPluginForm(ImageFormMixin, ModelForm): + """ + Form class to validate the JumbotronPlugin. + """ + image_file = ModelChoiceField(queryset=Image.objects.all(), required=False, label=_("Image")) + + def clean_glossary(self): + glossary = super(NavbarPluginForm, self).clean_glossary() + return glossary + + +class NavbarGridMixin(object): + + def get_grid_instance(self): + fluid = self.glossary.get('fluid', False) + try: + breakpoints = [getattr(grid.Breakpoint, bp) for bp in self.glossary['breakpoints']] + except KeyError: + breakpoints = [bp for bp in grid.Breakpoint] + if fluid: + bounds = dict((bp, grid.fluid_bounds[bp]) for bp in breakpoints) + else: + bounds = dict((bp, grid.default_bounds[bp]) for bp in breakpoints) + return grid.Bootstrap4Container(bounds=bounds) + + +class NavbarPlugin(BootstrapPluginBase): + name = _("Navbar") + model_mixins = ( NavbarGridMixin,) + default_css_class = 'navbar' + default_css_attributes = ('options',) + require_parent = False + parent_classes = None + render_template = 'cascade/bootstrap4/navbar.html' + glossary_variables = ['container_max_widths', 'media_queries'] + ring_plugin = 'JumbotronPlugin' + OPTION_NAV_COLLAPSE = [(s, s) for s in [ "inherit", "navbar-expand","navbar-expand-sm", "navbar-expand-md","navbar-expand-lg", "navbar-expand-xl"] ] + OPTION_NAV_COLOR = [(s, s) for s in [ "navbar-light", "navbar-dark"]] + OPTION_NAV_BG_COLOR = [ "bg-primary", "bg-secondary","bg-success", "bg-danger", "bg-warning", "bg-info" ,"bg-light", "bg-dark" , "bg-white", "bg-transparent"] + OPTION_NAV_BG_GRADIENT = [ "bg-gradient-primary", "bg-gradient-secondary", "bg-gradient-success", "bg-gradient-danger", "bg-gradient-warning", "bg-gradient-info", "bg-gradient-light", "bg-gradient-dark"] + OPTION_NAV_BG_MIX = OPTION_NAV_BG_COLOR + OPTION_NAV_BG_GRADIENT + OPTION_NAV_PLACEMENTS=["inherit", "fixed-top" , "fixed-bottom" , "sticky-top"] + + container_glossary_fields = ( + GlossaryField( + ContainerBreakpointsWidget(choices=get_widget_choices()), + label=_("Available Breakpoints"), + name='breakpoints', + initial=app_settings.CMSPLUGIN_CASCADE['bootstrap4']['default_bounds'].keys(), + help_text=_("Supported display widths for Bootstrap's grid system.") + ), + GlossaryField( + MultipleCascadingSizeWidget([bp.name for bp in Breakpoint], allowed_units=['px', '%'], required=False), + label=_("Adapt Picture Heights"), + name='container_max_heights', + initial={'xs': '100%', 'sm': '100%', 'md': '100%', 'lg': '100%', 'xl': '100%'}, + help_text=_("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.") + ), + GlossaryField( + widgets.CheckboxSelectMultiple(choices=BootstrapPicturePlugin.RESIZE_OPTIONS), + label=_("Resize Options"), + name='resize_options', + initial=['crop', 'subject_location', 'high_resolution'], + help_text=_("Options to use when resizing the image.") + ), + ) + + navbar_collapse = GlossaryField( + widgets.Select(choices=OPTION_NAV_COLLAPSE), + label=_('navbar collapse'), + name='navbar_classes collapse', + ) + + navbar_color = GlossaryField( + widgets.Select(choices=OPTION_NAV_COLOR), + label=_('navbar color'), + name='navbar_color', + ) + + navbar_bg_color= GlossaryField( + widgets.Select(choices=[(s, s) for s in OPTION_NAV_BG_MIX ]), + label=_('navbar-bg'), + name='navbar_navbg', + ) + + navbar_placement= GlossaryField( + widgets.Select(choices= [(s, s) for s in OPTION_NAV_PLACEMENTS]), + label=_('navbar-place'), + name='navbar_place', + ) + + @classmethod + def get_css_classes(cls, obj): + css_classes = super(NavbarPlugin, cls).get_css_classes(obj) + navbar_collapse = obj.glossary.get('navbar_collapse', '') + navbar_color = obj.glossary.get('navbar_color', '') + navbar_bg_color = obj.glossary.get('navbar_bg_color', '') + navbar_placement = obj.glossary.get('placement', '') + if navbar_collapse != 'inherit': + css_classes.append(navbar_collapse) + if navbar_color != 'inherit': + css_classes.append(navbar_color) + if navbar_bg_color != 'inherit': + css_classes.append(navbar_bg_color) + if navbar_placement != 'inherit': + css_classes.append(navbar_placement ) + return css_classes + + def get_form(self, request, obj=None, **kwargs): + if self.get_parent_instance(request, obj) is None: + # we only ask for breakpoints, if the jumbotron is the root of the placeholder + kwargs.update(glossary_fields=list(self.container_glossary_fields)) + kwargs['glossary_fields'].extend(self.glossary_fields) + form = super(NavbarPlugin, self).get_form(request, obj, **kwargs) + return form + + + @classmethod + def get_identifier(cls, obj): + identifier = super(NavbarPlugin, cls).get_identifier(obj) + glossary = obj.get_complete_glossary() + css_classes_without_default = obj.css_classes.replace( cls.default_css_class , '' , 1) + return format_html('
{0}{1}
', + identifier, css_classes_without_default) + +plugin_pool.register_plugin(NavbarPlugin) + + +class NavbarLinksItemsPlugin(BootstrapPluginBase): + name = _("Nav Links Items") + chojust=[ "inherit", "justify-content-start","justify-content-end", "justify-content-center", "justify-content-between", "justify-content-around" ] + chomrml=[ "inherit", "mr-auto", "ml-auto" ] + choflex=[ "flex-row", "flex-wrap"] + default_css_class = '' + parent_classes = ['NavbarPlugin'] + render_template = 'cascade/bootstrap4/navbar_links.html' + + jus= GlossaryField( + widgets.Select(choices= [(s, s) for s in chojust]), + label=_('navbar-place'), + name='navbar_place', + help_text=_("Adjust interval place s."), + ) + + + navflex= GlossaryField( + widgets.Select(choices= [(s, s) for s in choflex]), + label=_('navbar-place'), + name='navbar_place', + help_text=_("Adjust interval place s."), + ) + + mrml= GlossaryField( + widgets.Select(choices= [(s, s) for s in chomrml]), + label=_('navbar-place'), + name='navbar_place', + help_text=_("Adjust interval place UL"), + ) + +plugin_pool.register_plugin(NavbarLinksItemsPlugin) + + +class NavbarBrandPlugin(BootstrapPluginBase, LinkPluginBase,): + name = _("Nav brand") + parent_classes = ['NavbarPlugin'] + model_mixins = (LinkElementMixin,) + render_template = 'cascade/bootstrap4/navbar_brand.html' + fields = list(LinkPluginBase.fields) + +plugin_pool.register_plugin(NavbarBrandPlugin) + + +class NavbarBrandImagePluginForm(ImageFormMixin, ModelForm): + """ + Form class to validate the NavbarBrandImage. + """ + image_file = ModelChoiceField(queryset=Image.objects.all(), required=False, label=_("Image")) + + def clean_glossary(self): + glossary = super(NavbarBrandImagePluginForm , self).clean_glossary() + return glossary + + +class NavbarBrandImagePlugin(ImageAnnotationMixin, BootstrapPluginBase, ): + name = _("Nav brand Image") + model_mixins = (ImagePropertyMixin,) + form = NavbarBrandImagePluginForm + parent_classes = ['NavbarBrandPlugin'] + allow_children = True + alien_child_classes = True + html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'} + raw_id_fields = ['image_file'] + fields = [ 'glossary','image_file', ] + SIZE_CHOICES = ('auto', 'width/height', 'cover', 'contain') + + RESIZE_OPTIONS = [ + ('upscale', _("Upscale image")), + ('crop', _("Crop image")), + ('subject_location', _("With subject location")), + ('high_resolution', _("Optimized for Retina")), +] + render_template = 'cascade/bootstrap4/navbar_brand_image.html' + + image_width_fixed = GlossaryField( + CascadingSizeWidget(allowed_units=['px'], required=False), + label=_("Fixed Image Width"), + help_text=_("Set a fixed image width in pixels."), + ) + + image_height = GlossaryField( + CascadingSizeWidget(allowed_units=['px', '%'], required=False), + label=_("Adapt Image Height"), + help_text=_("Set a fixed height in pixels, or percent relative to the image width."), + ) + + resize_options = GlossaryField( + widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS), + label=_("Resize Options"), + help_text=_("Options to use when resizing the image."), + initial=['subject_location', 'high_resolution'], + ) + + def get_form(self, request, obj=None, **kwargs): + if self.get_parent_instance(request, obj) is None: + # we only ask for breakpoints, if the jumbotron is the root of the placeholder + kwargs.update(glossary_fields=list(self.container_glossary_fields)) + kwargs['glossary_fields'].extend(self.glossary_fields) + form = super(NavbarBrandImagePlugin, self).get_form(request, obj, **kwargs) + return form + + def render(self, context, instance, placeholder): + tags = get_image_tags(instance) + try: + tags = get_image_tags(instance) + except Exception as exc: + logger.warning("Unable generate image tags. Reason: {}".format(exc)) + tags = tags if tags else {} + if 'extra_styles' in tags: + extra_styles = tags.pop('extra_styles') + inline_styles = instance.glossary.get('inline_styles', {}) + inline_styles.update(extra_styles) + instance.glossary['inline_styles'] = inline_styles + context.update(dict(instance=instance, placeholder=placeholder, **tags)) + return context + +plugin_pool.register_plugin(NavbarBrandImagePlugin) + + +class NavbarCollapsePlugin(BootstrapPluginBase): + name = _("Nav Collapse") + parent_classes = ['NavbarPlugin'] + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar_collapse.html' + default_css_class = 'collapse navbar-collapse' + + @classmethod + def get_css_classes(cls, obj): + css_classes = super(NavbarCollapsePlugin, cls).get_css_classes(obj) + return css_classes + + @classmethod + def get_identifier(cls, obj): + identifier = super(NavbarCollapsePlugin, cls).get_identifier(obj) + glossary = obj.get_complete_glossary() + css_classes_without_default = obj.css_classes.replace( cls.default_css_class , '' , 1) + return format_html('
{0}{1}
', + identifier, css_classes_without_default) + +plugin_pool.register_plugin(NavbarCollapsePlugin) + + +class NavbarNavListPlugin(BootstrapPluginBase): + name = _("Nav list") + parent_classes = ['NavbarPlugin', 'NavbarCollapsePlugin' ] + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar_nav_list.html' + default_css_class = 'navbar-nav' + + @classmethod + def get_css_classes(cls, obj): + css_classes = super(NavbarNavListPlugin, cls).get_css_classes(obj) + return css_classes + + @classmethod + def get_identifier(cls, obj): + identifier = super(NavbarNavListPlugin, cls).get_identifier(obj) + glossary = obj.get_complete_glossary() + if hasattr(cls,'default_css_class'): + css_classes_without_default = obj.css_classes.replace( cls.default_css_class , '' , 1) + else: + css_classes_without_default = obj.css_classes + return format_html('
{0}{1}
', + identifier, css_classes_without_default ) + +plugin_pool.register_plugin(NavbarNavListPlugin) + + +class NavbarNavItemsMainMemuPlugin(BootstrapPluginBase): + name = _("Nav items main menu") + parent_classes = ['NavbarNavListPlugin'] + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar_nav_items_links.html' + +plugin_pool.register_plugin(NavbarNavItemsMainMemuPlugin) + + +class NavbarNavItemsPlugin(BootstrapPluginBase): + default_css_class = 'nav-item' + name = _("Nav item") + parent_classes = ['NavbarNavListPlugin'] + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar_nav_item.html' + +plugin_pool.register_plugin(NavbarNavItemsPlugin) + + +class NavbarNavLinkPlugin(BootstrapPluginBase): + + name = _("Nav Link") + parent_classes = ['NavbarNavItemsPlugin'] + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar_nav_link.html' + +plugin_pool.register_plugin(NavbarNavLinkPlugin) + + +class NavbarToogler(BootstrapPluginBase): + name = _("Nav toogler") + default_css_class = '' + parent_classes = ['NavbarPlugin'] + render_template = 'cascade/bootstrap4/navbar_toogler.html' + +plugin_pool.register_plugin(NavbarToogler) diff --git a/cmsplugin_cascade/bootstrap4/settings.py b/cmsplugin_cascade/bootstrap4/settings.py index 713b8c743..ee3e972e4 100644 --- a/cmsplugin_cascade/bootstrap4/settings.py +++ b/cmsplugin_cascade/bootstrap4/settings.py @@ -12,7 +12,7 @@ CASCADE_PLUGINS = ['accordion', 'buttons', 'card', 'carousel', 'container', 'embeds', 'image', 'jumbotron', - 'picture', 'tabs'] + 'picture', 'tabs', 'navbar'] if 'cms_bootstrap' in settings.INSTALLED_APPS: CASCADE_PLUGINS.append('secondary_menu') @@ -58,7 +58,12 @@ def set_defaults(config): config['plugins_with_extra_mixins'].setdefault('HorizontalRulePlugin', BootstrapUtilities( BootstrapUtilities.margins, )) - + config['plugins_with_extra_mixins'].setdefault('NavbarNavListPlugin', BootstrapUtilities( + BootstrapUtilities.flex_directions, BootstrapUtilities.margins, BootstrapUtilities.display_propertys + )) + config['plugins_with_extra_mixins'].setdefault('NavbarCollapsePlugin', BootstrapUtilities( + BootstrapUtilities.justify_content + )) config['plugins_with_extra_fields'].setdefault('BootstrapJumbotronPlugin', PluginExtraFieldsConfig( inline_styles={ 'extra_fields:Paddings': ['margin-top', 'margin-bottom', 'padding-top', 'padding-bottom'], diff --git a/cmsplugin_cascade/icon/cms_plugins.py b/cmsplugin_cascade/icon/cms_plugins.py index 642ca991a..7376a6474 100644 --- a/cmsplugin_cascade/icon/cms_plugins.py +++ b/cmsplugin_cascade/icon/cms_plugins.py @@ -30,6 +30,7 @@ class SimpleIconPlugin(IconPluginMixin, LinkPluginBase): model_mixins = (LinkElementMixin,) fields = list(LinkPluginBase.fields) ring_plugin = 'IconPlugin' + raw_id_fields = LinkPluginBase.raw_id_fields icon_font = GlossaryField( widgets.Select(), @@ -51,6 +52,13 @@ def get_form(self, request, obj=None, **kwargs): kwargs.update(form=VoluntaryLinkForm.get_form_class()) return super(SimpleIconPlugin, self).get_form(request, obj, **kwargs) + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super(SimpleIconPlugin, cls).get_css_classes(obj) + if obj.parent.plugin_type == 'NavbarNavItemsPlugin': + css_classes.insert(0,'nav-link navbar-text') + return css_classes + def render(self, context, instance, placeholder): context = super(SimpleIconPlugin, self).render(context, instance, placeholder) icon_font = self.get_icon_font(instance) @@ -58,6 +66,10 @@ def render(self, context, instance, placeholder): if icon_font and symbol: font_attr = 'class="{}{}"'.format(icon_font.config_data.get('css_prefix_text', 'icon-'), symbol) context['icon_font_attrs'] = mark_safe(font_attr) + link_attributes = LinkPluginBase.get_html_tag_attributes(instance) + link_html_tag_attributes = format_html_join(' ', '{0}="{1}"', + [(attr, val) for attr, val in link_attributes.items() if val]) + context['link_html_tag_attributes'] = link_html_tag_attributes return context plugin_pool.register_plugin(SimpleIconPlugin) @@ -179,6 +191,10 @@ def render(self, context, instance, placeholder): format_html_join('', '{0}:{1};', [(k, v) for k, v in styles.items()]))) context['icon_font_attrs'] = mark_safe(' '.join(attrs)) + link_attributes = LinkPluginBase.get_html_tag_attributes(instance) + link_html_tag_attributes = format_html_join(' ', '{0}="{1}"', + [(attr, val) for attr, val in link_attributes.items() if val]) + context['link_html_tag_attributes'] = link_html_tag_attributes return context plugin_pool.register_plugin(FramedIconPlugin) diff --git a/cmsplugin_cascade/link/cms_plugins.py b/cmsplugin_cascade/link/cms_plugins.py index 7c5128297..f96f9b933 100644 --- a/cmsplugin_cascade/link/cms_plugins.py +++ b/cmsplugin_cascade/link/cms_plugins.py @@ -26,6 +26,13 @@ class Media: def get_identifier(cls, obj): return mark_safe(obj.glossary.get('link_content', '')) + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super(TextLinkPlugin, cls).get_css_classes(obj) + if obj.parent.plugin_type == 'TextPlugin' and obj.parent.parent.plugin_type == 'NavbarNavItemsPlugin' : + css_classes.insert(0,'nav-link navbar-text') + return css_classes + def get_form(self, request, obj=None, **kwargs): link_content = CharField(required=True, label=_("Link Content"), # replace auto-generated id so that CKEditor automatically transfers the text into this input field diff --git a/cmsplugin_cascade/static/cascade/clipboards_catalog/navbars/bootstrap_navbar_example_1.json b/cmsplugin_cascade/static/cascade/clipboards_catalog/navbars/bootstrap_navbar_example_1.json new file mode 100644 index 000000000..e2ac07ad0 --- /dev/null +++ b/cmsplugin_cascade/static/cascade/clipboards_catalog/navbars/bootstrap_navbar_example_1.json @@ -0,0 +1,205 @@ +{ + "plugins":[ + [ + "NavbarPlugin", + { + "glossary":{ + "breakpoints":[ + "xs", + "sm", + "md", + "lg", + "xl" + ], + "container_max_heights":{ + "xs":"100%", + "sm":"100%", + "md":"100%", + "lg":"100%", + "xl":"100%" + }, + "resize_options":[ + "crop", + "subject_location", + "high_resolution" + ], + "navbar_collapse":"navbar-expand-lg", + "navbar_color":"navbar-dark", + "navbar_bg_color":"bg-dark", + "navbar_placement":"fixed-top", + "hide_plugin":"" + }, + "pk":79 + }, + [ + [ + "NavbarBrandPlugin", + { + "glossary":{ + "target":"", + "title":"", + "hide_plugin":"on", + "link":{ + "type":"cmspage", + "model":"cms.Page", + "pk":2, + "section":"" + } + }, + "pk":90 + }, + [ + [ + "NavbarBrandImagePlugin", + { + "glossary":{ + "image_title":"dd", + "alt_tag":"d", + "image_width_fixed":"20px", + "image_height":"", + "resize_options":[ + "crop", + "subject_location", + "high_resolution" + ], + "hide_plugin":"", + "image":{ + "pk":1, + "model":"filer.Image", + "width":250, + "height":305, + "exif_orientation":1 + } + }, + "pk":91 + }, + [] + ] + ] + ], + [ + "NavbarToogler", + { + "glossary":{ + "hide_plugin":"" + }, + "pk":80 + }, + [] + ], + [ + "NavbarCollapsePlugin", + { + "glossary":{ + "Justify_content_xs":"", + "Justify_content_sm":"", + "Justify_content_md":"justify-content-md-center", + "Justify_content_lg":"", + "hide_plugin":"" + }, + "pk":81 + }, + [ + [ + "NavbarNavListPlugin", + { + "glossary":{ + "Flex_xs":"", + "Flex_sm":"", + "Flex_md":"", + "Flex_lg":"", + "margins_xs":"", + "margins_sm":"", + "margins_md":"", + "margins_lg":"", + "hide_plugin":"" + }, + "pk":82 + }, + [ + [ + "NavbarNavItemsMainMemuPlugin", + { + "glossary":{ + "hide_plugin":"" + }, + "pk":83 + }, + [] + ], + [ + "NavbarNavItemsPlugin", + { + "glossary":{ + "hide_plugin":"" + }, + "pk":84 + }, + [ + [ + "SimpleIconPlugin", + { + "glossary":{ + "icon_font":"1", + "symbol":"smile", + "target":"_blank", + "title":"smile", + "hide_plugin":"", + "link":{ + "type":"cmspage", + "model":"cms.Page", + "pk":2, + "section":"" + } + }, + "pk":85 + }, + [] + ] + ] + ], + [ + "NavbarNavItemsPlugin", + { + "glossary":{ + "hide_plugin":"" + }, + "pk":88 + }, + [ + [ + "BootstrapButtonPlugin", + { + "glossary":{ + "button_type":"btn-link", + "button_size":"", + "button_options":[], + "target":"", + "title":"Link extern", + "stretched_link":"", + "icon_align":"icon-left", + "icon_font":"1", + "symbol":"firefox", + "hide_plugin":"", + "link":{ + "type":"cmspage", + "model":"cms.Page", + "pk":2, + "section":"" + }, + "link_content":"Link extern" + }, + "pk":89 + }, + [] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] +} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar.html new file mode 100644 index 000000000..f8041a9d6 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar.html @@ -0,0 +1,14 @@ +{% load bootstrap_tags cms_tags %} +{% block extra-styles %}{% endblock %} +{% block extra-scripts %}{% endblock %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %} + diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand.html new file mode 100644 index 000000000..6e241b5f4 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand.html @@ -0,0 +1,11 @@ +{% extends "cascade/link/link-base.html" %} +{% load bootstrap_tags cms_tags %} +{% block link_link %}{% with instance_link=instance|default:"#" %} +{% block navbar-brand %}{% for child in instance.child_plugin_instances %} +{{ block.super }} +{% render_plugin child %} +{% endfor %} +{% endblock %} +{% endwith %} +{% endblock %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %}{% block link_content %}{{ instance.content }}{% endblock %}{% endwith %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand_image.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand_image.html new file mode 100644 index 000000000..a1d526c58 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand_image.html @@ -0,0 +1,21 @@ +{% load l10n thumbnail %} +{% localize off %}{% spaceless %} +{% with css_classes=instance.css_classes inline_styles=instance.inline_styles %} +{% if instance.image %} {% if not instance.image %}{% endif %} + {% else %} +{% thumbnail instance.image srcset.size crop=srcset.crop upscale=srcset.upscale subject_location=instance.image.subject_location as thumb %} + {% if not sizes %} width="{{ thumb.width }}" height="{{ thumb.height }}"{% endif %} src="{{ instance.image.url }}"/>{% if not instance.image %}{% endif %} + {% endif %} + {% endwith %} +{% endspaceless %}{% endlocalize %} + diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_collapse.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_collapse.html new file mode 100644 index 000000000..7e149103c --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_collapse.html @@ -0,0 +1,7 @@ +{% load bootstrap_tags cms_tags %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %} + +{% endwith %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_links.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_links.html new file mode 100644 index 000000000..dd5416f12 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_links.html @@ -0,0 +1,4 @@ +{% load bootstrap_tags cms_tags %} + diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_item.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_item.html new file mode 100644 index 000000000..e7507beb5 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_item.html @@ -0,0 +1,17 @@ +{% load static bootstrap_tags cms_tags sekizai_tags sass_tags %} +{% with css_classes=instance.css_classes inline_styles=instance.inline_styles %} +{% block navbar-nav-li %}
  • +{% for child in instance.child_plugin_instances %} +{% render_plugin child %} +{% endfor %} +
  • +{% endblock %} +{% endwith %} +{% addtoblock "css" %} + +{% endaddtoblock %} + diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_items_links.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_items_links.html new file mode 100644 index 000000000..cc26b1819 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_items_links.html @@ -0,0 +1,4 @@ +{% load bootstrap_tags cms_tags %} +{% block navbar-nav %} +{% main_menu "bootstrap4/menu/navbar.html" %} +{% endblock %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_link.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_link.html new file mode 100644 index 000000000..42c1b3fd2 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_link.html @@ -0,0 +1,3 @@ +{% load cms_tags %} +{% block navbar-nav-li %} +{% endblock %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_list.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_list.html new file mode 100644 index 000000000..419acd817 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_list.html @@ -0,0 +1,5 @@ +{% load cms_tags %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %} +{% block navbar-nav-list %} +{% endblock %} +{% endwith %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_toogler.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_toogler.html new file mode 100644 index 000000000..fc75a17d7 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_toogler.html @@ -0,0 +1,3 @@ + diff --git a/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html b/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html index 07970e230..1db3cf1ab 100644 --- a/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html +++ b/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html @@ -10,4 +10,4 @@ {% if instance_link %}{% elif css_classes or inline_styles %}{% endif %} {% endwith %} -{% endspaceless %} \ No newline at end of file +{% endspaceless %} diff --git a/cmsplugin_cascade/utils.py b/cmsplugin_cascade/utils.py index bcf5b98b6..daa1e28b3 100644 --- a/cmsplugin_cascade/utils.py +++ b/cmsplugin_cascade/utils.py @@ -41,9 +41,12 @@ def validate_link(link_data): def compute_aspect_ratio(image): - if image.exif.get('Orientation', 1) > 4: - # image is rotated by 90 degrees, while keeping width and height - return float(image.width) / float(image.height) + if image.width != 0 or image.height: + if image.exif.get('Orientation', 1) > 4: + # image is rotated by 90 degrees, while keeping width and height + return float(image.width) / float(image.height) + else: + return float(image.height) / float(image.width) else: return float(image.height) / float(image.width) diff --git a/examples/bs4demo/settings.py b/examples/bs4demo/settings.py index 675ee358d..11b5942e6 100644 --- a/examples/bs4demo/settings.py +++ b/examples/bs4demo/settings.py @@ -237,9 +237,16 @@ CMS_PLACEHOLDER_CONF = { # this placeholder is used in templates/main.html, it shows how to # scaffold a djangoCMS page starting with an empty placeholder + 'Header Content': { + 'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin','NavbarPlugin'], + 'parent_classes': {'NavbarPlugin': None, 'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None}, + 'glossary': CASCADE_WORKAREA_GLOSSARY, + }, + + 'Main Content': { 'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'], - 'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None}, + 'parent_classes': {'NavbarPlugin': None, 'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None}, 'glossary': CASCADE_WORKAREA_GLOSSARY, }, # this placeholder is used in templates/wrapped.html, it shows how to @@ -256,7 +263,7 @@ 'language': '{{ language }}', 'skin': 'moono-lisa', 'toolbar': 'CMS', - 'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texticon_wysiwig_config')), + 'stylesSet': format_lazy('default:{}', reverse_lazy('admin:cascade_texteditor_config')), } SELECT2_CSS = 'node_modules/select2/dist/css/select2.min.css' diff --git a/examples/bs4demo/templates/bs4demo/main.html b/examples/bs4demo/templates/bs4demo/main.html index 51ae5935b..96cee7941 100644 --- a/examples/bs4demo/templates/bs4demo/main.html +++ b/examples/bs4demo/templates/bs4demo/main.html @@ -6,15 +6,29 @@ {% endblock head %} {% block header %} - {% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui' %} - {% include "bootstrap4/includes/ng-nav-navbar.html" with navbar_classes="navbar-expand-lg navbar-light bg-light fixed-top" %} - {% else %} - {% include "bootstrap4/includes/nav-navbar.html" with navbar_classes="navbar-expand-lg navbar-light bg-light fixed-top" role="navigation" %} + +{% static_placeholder 'Header Content' %} +{% if request.toolbar and request.toolbar.edit_mode_active %} +{% addtoblock "js" %} + +{% endaddtoblock %} {% endif %} {% if cms_version >= "3.5.0" and request.toolbar %} {% endif %} diff --git a/requirements/base.txt b/requirements/base.txt index a12c39af0..e081d774f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -10,7 +10,7 @@ django-sass-processor==0.6 django-sekizai==0.10.0 Django-Select2==5.11.1 django-treebeard==4.3 -djangocms-bootstrap==1.0.1 +djangocms-bootstrap==1.1.0 djangocms-text-ckeditor==3.5.3 easy-thumbnails==2.5.0 html5lib==0.9999999