diff --git a/cmsplugin_cascade/bootstrap4/buttons.py b/cmsplugin_cascade/bootstrap4/buttons.py index 0e8e1e4c1..53dd5f4ac 100644 --- a/cmsplugin_cascade/bootstrap4/buttons.py +++ b/cmsplugin_cascade/bootstrap4/buttons.py @@ -110,7 +110,7 @@ class Meta: class BootstrapButtonMixin(IconPluginMixin): require_parent = True - parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin'] + parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin','BootstrapNavItemsPlugin', 'BootstrapListsPlugin'] render_template = 'cascade/bootstrap4/button.html' allow_children = False default_css_class = 'btn' @@ -167,6 +167,9 @@ def get_css_classes(cls, obj): css_classes = cls.super(BootstrapButtonPlugin, cls).get_css_classes(obj) if obj.glossary.get('stretched_link'): css_classes.append('stretched_link') + if hasattr(obj, 'parent') and hasattr(obj.parent,'parent') and hasattr(obj.parent.parent,'plugin_type'): + if obj.parent.parent.plugin_type == 'BootstrapNavBrandPlugin': + css_classes.insert(0,'nav-link text-left') return css_classes @classmethod diff --git a/cmsplugin_cascade/bootstrap4/lists.py b/cmsplugin_cascade/bootstrap4/lists.py new file mode 100644 index 000000000..b755f232a --- /dev/null +++ b/cmsplugin_cascade/bootstrap4/lists.py @@ -0,0 +1,70 @@ +from django.utils.html import format_html +from django.utils.translation import ugettext_lazy as _ +from django.forms.fields import ChoiceField +from .plugin_base import BootstrapPluginBase +from cms.plugin_pool import plugin_pool +from entangled.forms import EntangledModelFormMixin + +import logging +logger = logging.getLogger('cascade') + +class BootstrapListsMixin( EntangledModelFormMixin): + list_options = ChoiceField( + label=_("List Options"), + choices=[ + ('inherit', _("inherit")), + ('inline-item', _('ul(class="inline-item").li(class="list-inline-item")')), + ('navbar-nav', _('ul(class="navbar-nav").li(class="nav-item")')), + ], + required=False, + ) + class Meta: + entangled_fields = {'glossary': ['list_options']} + + +@plugin_pool.register_plugin +class BootstrapListsPlugin(BootstrapPluginBase): + name = _("Lists") + alien_plugins = True + form = BootstrapListsMixin + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar_list.html' + default_css_class = '' + require_parent = False + + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super(BootstrapListsPlugin, cls).get_css_classes(obj) + list_options = [obj.glossary.get('list_options')] if obj.glossary.get('list_options') else '' + if list_options: + for opts in list_options: + css_classes.append(opts) + return css_classes + + + @classmethod + def get_identifier(cls, obj): + identifier = super(BootstrapListsPlugin, cls).get_identifier(obj) + 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 ) + + @classmethod + def sanitize_model(cls, obj): + list_child_css_classes = obj.glossary['child_css_classes'].split(' ') if 'child_css_classes' in obj.glossary else [] + list_options = 'list-inline-item' if obj.glossary.get('list_options') else '' + list_options = 'nav-item' if obj.glossary.get('list_options') == 'navbar-nav' else list_options + if list_options: + list_child_css_classes.append(list_options) + obj.glossary['child_css_classes'] = ' '.join(list_child_css_classes) + super().sanitize_model(obj) + +@plugin_pool.register_plugin +class BootstrapListsSeparatorPlugin(BootstrapPluginBase): + name = _("Li Separator") + require_parent = True + parent_classes = ['BootstrapListsPlugin'] + render_template = 'cascade/bootstrap4/navbar_list_separator.html' diff --git a/cmsplugin_cascade/bootstrap4/mixins.py b/cmsplugin_cascade/bootstrap4/mixins.py index 35202923f..41f58933b 100644 --- a/cmsplugin_cascade/bootstrap4/mixins.py +++ b/cmsplugin_cascade/bootstrap4/mixins.py @@ -170,3 +170,104 @@ def floats(cls): initial='', ) return form_fields + + @property + def flex_directions(cls): + form_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"))) + form_fields['Flex_{}'.format(bp.name)] = ChoiceField( + label=format_lazy(_("Flex Directions for {breakpoint}"), breakpoint=bp.label), + choices=choices, + required=False, + initial='' + ) + return form_fields + + @property + def display_propertys(cls): + form_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 Display Propertys"))) + 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"))) + form_fields['Flex_{}'.format(bp.name)] = ChoiceField( + label=format_lazy(_("Flex Directions for {breakpoint}"), breakpoint=bp.label), + choices=choices, + required=False, + initial='' + ) + return form_fields + + @property + def justify_content(cls): + form_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"))) + form_fields['Justify_content_{}'.format(bp.name)] = ChoiceField( + label=format_lazy(_("Justify Content for {breakpoint}"), breakpoint=bp.label), + choices=choices, + required=False, + initial='' + ) + return form_fields + + @property + def positions(cls): + form_fields = {} + choices_format = [ + ('{}', _("Position")), + ] + notation = ['inherit', 'fixed-top' , 'fixed-bottom' , 'sticky-top'] + choices = [ (str(n), str(n)) for c, l in choices_format for n in notation] + form_fields['Position'] = ChoiceField( + label=format_lazy(_("Position")), + choices=choices, + required=False, + initial='' + ) + return form_fields + + @property + def list_inline(cls): + form_fields = {} + choices_format = [ + ('{}', _("List inline")), + ] + notation = ['inherit', 'fixed-top' , 'fixed-bottom' , 'sticky-top'] + choices = [ (str(n), str(n)) for c, l in choices_format for n in notation] + form_fields['Position'] = ChoiceField( + label=format_lazy(_("Position")), + choices=choices, + required=False, + initial='' + ) + return form_fields diff --git a/cmsplugin_cascade/bootstrap4/navbar.py b/cmsplugin_cascade/bootstrap4/navbar.py new file mode 100644 index 000000000..4f329fd4b --- /dev/null +++ b/cmsplugin_cascade/bootstrap4/navbar.py @@ -0,0 +1,292 @@ +from django.utils.html import format_html +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings +from django.forms import widgets +from django.forms.fields import BooleanField, CharField, ChoiceField +from cmsplugin_cascade.fields import SizeField +from .plugin_base import BootstrapPluginBase +from cmsplugin_cascade.bootstrap4.jumbotron import ImageBackgroundMixin +from cmsplugin_cascade.bootstrap4.container import ContainerFormMixin, ContainerGridMixin +from cmsplugin_cascade.image import ImageFormMixin, ImagePropertyMixin +from cmsplugin_cascade.bootstrap4.container import get_widget_choices, ContainerBreakpointsWidget +from cmsplugin_cascade.bootstrap4.image import get_image_tags +from .grid import Breakpoint +from cmsplugin_cascade.link.config import LinkPluginBase +from cms.plugin_pool import plugin_pool +from entangled.forms import EntangledModelFormMixin +from django.core.exceptions import ValidationError + +import logging +logger = logging.getLogger('cascade') + + +class BootstrapNavbarFormMixin(EntangledModelFormMixin): + OPTION_NAV_COLLAPSE = [(c, c) for c in [ "inherit", "navbar-expand","navbar-expand-sm", "navbar-expand-md","navbar-expand-lg", "navbar-expand-xl"] ] + OPTION_NAV_COLOR = [(c, c) for c in [ "inherit", "navbar-light", "navbar-dark"]] + OPTION_NAV_BG_COLOR = [ "inherit", "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"] + + navbar_collapse = ChoiceField( + label=_('Navbar collapse'), + choices=OPTION_NAV_COLLAPSE, + help_text=_("Adjust interval for the navbar_collapse.") + ) + + navbar_color = ChoiceField( + label=_('Navbar text color'), + choices=OPTION_NAV_COLOR, + help_text=_("Adjust the navbar color.") + ) + + navbar_bg_color = ChoiceField( + label=_('Navbar bg color'), + choices=[(c, c) for c in OPTION_NAV_BG_MIX ], + help_text=_("Adjust interval for the navbar background color."), + ) + + navbar_placement = ChoiceField( + label=_('navbar-place'), + choices=[(c, c) for c in OPTION_NAV_PLACEMENTS], + help_text=_("Adjust position ('fixed-top or fixed-button need to be set in Jumbotron if it is a plugin parent.')") + ) + + class Meta: + entangled_fields = {'glossary':['navbar_collapse', 'navbar_color', 'navbar_bg_color', 'navbar_placement']} + + def validate_optional_field(self, name): + field = self.fields[name] + value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) + if value in field.empty_values: + self.add_error(name, ValidationError(field.error_messages['required'], code='required')) + else: + return value + + def clean(self): + cleaned_data = super().clean() + return cleaned_data + + +@plugin_pool.register_plugin +class BootstrapNavbarPlugin(BootstrapPluginBase): + name = _("Navbar") + parent_classes = None + require_parent = False + model_mixins = (ContainerGridMixin,) + default_css_class = 'navbar' + default_css_attributes = ('options') + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar.html' + ring_plugin = 'BootstrapNavbarPlugin' + fixed_top_and_toolbar = None + + class Media: + js = ['cascade/js/admin/navbarplugin.js'] + + def get_form(self, request, obj=None, **kwargs): + kwargs['form'] = BootstrapNavbarFormMixin + if hasattr(obj, 'glossary'): + if obj.glossary.get('navbar_placement') == 'fixed-top': + obj.fixed_top_and_toolbar=True + return super().get_form(request, obj, **kwargs) + + @classmethod + def sanitize_model(cls, obj): + sanitized = False + if 'Position' in obj.get_parent_glossary(): + obj.glossary['position_jumbotron'] = obj.get_parent_glossary()['Position'] + if hasattr(obj,'fixed_top_and_toolbar'): + navbar_placement = obj.glossary.get('navbar_placement') + del obj.fixed_top_and_toolbar + super().sanitize_model(obj) + return sanitized + + + @classmethod + def get_css_classes(cls, obj, ): + css_classes = cls.super(BootstrapNavbarPlugin, 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('navbar_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 + + @classmethod + def get_identifier(cls, obj): + identifier = super().get_identifier(obj) + 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 +class BootstrapNavBrandPlugin(LinkPluginBase): + name = _("Nav brand") + parent_classes = ['BootstrapNavbarPlugin'] + render_template = 'cascade/bootstrap4/navbar_brand.html' + raw_id_fields = LinkPluginBase.raw_id_fields + ['image_file'] + default_css_class = '' + require_parent = False + allow_children = True + alien_child_classes = True + + @classmethod + def get_child_css_classes(cls, obj): + child_css_classes = cls.super(BootstrapNavBrandPlugin, cls).get_child_css_classes(obj) + child_css_classes = obj.glossary.get('child_css_class') + if child_css_classes: + child_css_classes.append(css_class) + return css_classes + + @classmethod + def get_identifier(cls, obj): + identifier = super(BootstrapNavBrandPlugin, cls).get_identifier(obj) + css_classes_without_default = obj.css_classes.replace( cls.default_css_class,'',1) + return format_html('
{0}{1}
', + identifier, css_classes_without_default) + + +class BootstrapNavBrandThumbImageFormMixin(EntangledModelFormMixin): + image_width_fixed = SizeField( + label=_("Fixed Image Width"), + allowed_units=['px'], + required = False, + help_text=_("Set a fixed image width in pixels.") + ) + + class Meta: + entangled_fields = {'glossary': ['image_width_fixed']} + +class BootstrapNavBrandImageFormMixin(ImageFormMixin, BootstrapNavBrandThumbImageFormMixin): + pass + + +@plugin_pool.register_plugin +class BootstrapNavBrandImagePlugin(BootstrapPluginBase): + name = _("Nav brand Image") + model_mixins = (ImagePropertyMixin,) + parent_classes = ['BootstrapNavBrandPlugin', 'BootstrapListsPlugin'] + allow_children = True + alien_child_classes = True + html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'} + raw_id_fields = ['image_file'] + SIZE_CHOICES = ('auto', 'width/height', 'cover', 'contain') + form = BootstrapNavBrandImageFormMixin + raw_id_fields = ['image_file'] + render_template = 'cascade/bootstrap4/navbar_brand_image.html' + default_css_class = 'nav-brand-logo-ts' + + def render(self, context, instance, placeholder, tags=None): + 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 + + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super(BootstrapNavBrandImagePlugin, cls).get_css_classes(obj) + css_class = obj.glossary.get('css_class') + if css_class: + css_classes.append(css_class) + return css_classes + + @classmethod + def get_child_css_classes(cls, obj): + child_css_classes = cls.super(BootstrapNavBrandImagePlugin, cls).get_child_css_classes(obj) + child_css_classes = obj.glossary.get('child_css_class') + if child_css_classes: + child_css_classes.append(css_class) + return css_classes + + +@plugin_pool.register_plugin +class BootstrapNavCollapsePlugin(BootstrapPluginBase): + name = _("Nav Collapse") + parent_classes = ['BootstrapNavbarPlugin'] + if not settings.CMSPLUGIN_CASCADE['bootstrap4'].get('template_basedir'): + render_template = 'cascade/bootstrap4/navbar_collapse.html' + else: + render_template = 'cascade/bootstrap4/ng_navbar_collapse.html' + default_css_class = 'collapse navbar-collapse' + + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super( BootstrapNavCollapsePlugin, cls).get_css_classes(obj) + return css_classes + + @classmethod + def get_identifier(cls, obj): + identifier = super(BootstrapNavCollapsePlugin, cls).get_identifier(obj) + css_classes_without_default = obj.css_classes.replace( cls.default_css_class,'',1) + return format_html('
{0}{1}
', + identifier, css_classes_without_default) + + @classmethod + def sanitize_model(cls, obj): + sanitized = super().sanitize_model(obj) + return sanitized + +@plugin_pool.register_plugin +class BootstrapNavItemsMainMenuPlugin(BootstrapPluginBase): + name = _("NavItems MainMenu ") + parent_classes = ['BootstrapListsPlugin'] + require_parent = False + allow_children = False + alien_child_classes = True + render_template = 'cascade/bootstrap4/navbar_nav_items_li_menu_main_links.html' + + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super(BootstrapNavItemsMainMenuPlugin, cls).get_css_classes(obj) + return css_classes + + @classmethod + def get_identifier(cls, obj): + identifier = super(BootstrapNavItemsMainMenuPlugin, cls).get_identifier(obj) + 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 +class BootstrapNavbarToogler(BootstrapPluginBase): + name = _("Nav toogler") + default_css_class = 'navbar-toggler' + parent_classes = ['BootstrapNavbarPlugin',] + if not settings.CMSPLUGIN_CASCADE['bootstrap4'].get('template_basedir'): + render_template = 'cascade/bootstrap4/navbar_toogler.html' + else: + render_template = 'cascade/bootstrap4/ng_navbar_toogler.html' + + + +@plugin_pool.register_plugin +class BootstrapNavbarLanguageChooser(BootstrapPluginBase): + name = _("Nav Lang Chooser") + default_css_class = 'nav-item dropdown' + parent_classes = ['BootstrapListsPlugin'] + if not settings.CMSPLUGIN_CASCADE['bootstrap4'].get('template_basedir'): + render_template = 'bootstrap4/includes/language-chooser.html' + else: + render_template = 'bootstrap4/includes/ng-language-chooser.html' diff --git a/cmsplugin_cascade/bootstrap4/picture.py b/cmsplugin_cascade/bootstrap4/picture.py index 533df3e8a..3a0045c60 100644 --- a/cmsplugin_cascade/bootstrap4/picture.py +++ b/cmsplugin_cascade/bootstrap4/picture.py @@ -127,4 +127,4 @@ def sanitize_model(cls, obj): return return sanitized -plugin_pool.register_plugin(BootstrapPicturePlugin) +plugin_pool.register_plugin(BootstrapPicturePlugin) \ No newline at end of file diff --git a/cmsplugin_cascade/bootstrap4/settings.py b/cmsplugin_cascade/bootstrap4/settings.py index e52f64331..15f9f6354 100644 --- a/cmsplugin_cascade/bootstrap4/settings.py +++ b/cmsplugin_cascade/bootstrap4/settings.py @@ -7,7 +7,7 @@ CASCADE_PLUGINS = ['accordion', 'buttons', 'card', 'carousel', 'container', 'embeds', 'icon', 'image', 'jumbotron', - 'picture', 'tabs'] + 'picture', 'tabs', 'navbar', 'lists'] if 'cms_bootstrap' in settings.INSTALLED_APPS: CASCADE_PLUGINS.append('secondary_menu') @@ -53,7 +53,9 @@ def set_defaults(config): config['plugins_with_extra_mixins'].setdefault('HorizontalRulePlugin', BootstrapUtilities( BootstrapUtilities.margins, )) - + config['plugins_with_extra_mixins'].setdefault('BootstrapJumbotronPlugin', BootstrapUtilities( + BootstrapUtilities.positions, + )) config['plugins_with_extra_fields'].setdefault('BootstrapTabSetPlugin', PluginExtraFieldsConfig( css_classes={ 'multiple': True, @@ -65,3 +67,25 @@ def set_defaults(config): ('cascade/bootstrap4/secmenu-list-group.html', _("List Group")), ('cascade/bootstrap4/secmenu-unstyled-list.html', _("Unstyled List")) ]) + config['plugins_with_extra_fields'].setdefault('BootstrapNavbarPlugin', PluginExtraFieldsConfig( + inline_styles={ + 'extra_fields:Colors':['color','background-color'], + } + )) + config['plugins_with_extra_mixins'].setdefault('BootstrapNavBrandPlugin', BootstrapUtilities( + BootstrapUtilities.background_and_color + )) + config['plugins_with_extra_mixins'].setdefault('BootstrapListsPlugin', BootstrapUtilities( + BootstrapUtilities.flex_directions, BootstrapUtilities.margins, BootstrapUtilities.paddings, BootstrapUtilities.display_propertys, BootstrapUtilities.background_and_color + )) + config['plugins_with_extra_fields'].setdefault('BootstrapListsPlugin', PluginExtraFieldsConfig( + inline_styles={ + 'extra_fields:Colors':['color','background-color'], + }, + )) + config['plugins_with_extra_mixins'].setdefault('BootstrapNavbarToogler', BootstrapUtilities( + BootstrapUtilities.background_and_color + )) + config['plugins_with_extra_mixins'].setdefault('BootstrapNavCollapsePlugin', BootstrapUtilities( + BootstrapUtilities.justify_content + )) diff --git a/cmsplugin_cascade/icon/simpleicon.py b/cmsplugin_cascade/icon/simpleicon.py index e11c63446..cc800ad76 100644 --- a/cmsplugin_cascade/icon/simpleicon.py +++ b/cmsplugin_cascade/icon/simpleicon.py @@ -20,5 +20,13 @@ class SimpleIconPlugin(IconPluginMixin, LinkPluginBase): class Media: js = ['admin/js/jquery.init.js', 'cascade/js/admin/iconplugin.js'] + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super(SimpleIconPlugin, cls).get_css_classes(obj) + if hasattr(obj,'parent') and hasattr(obj.parent,'parent') and hasattr(obj.parent.parent,'plugin_type'): + if obj.parent.parent.plugin_type == 'BootstrapNavCollapsePlugin': + css_classes.insert(0,'nav-link navbar-text') + return css_classes + plugin_pool.register_plugin(SimpleIconPlugin) diff --git a/cmsplugin_cascade/link/cms_plugins.py b/cmsplugin_cascade/link/cms_plugins.py index 739783dfc..1e4fbcc3a 100644 --- a/cmsplugin_cascade/link/cms_plugins.py +++ b/cmsplugin_cascade/link/cms_plugins.py @@ -18,6 +18,18 @@ class TextLinkPlugin(LinkPluginBase): class Media: js = ['admin/js/jquery.init.js', 'cascade/js/admin/textlinkplugin.js'] + + @classmethod + def get_css_classes(cls, obj): + css_classes = cls.super(TextLinkPlugin, cls).get_css_classes(obj) + if hasattr(obj.parent.parent, 'plugin_type' ) and obj.parent.parent.plugin_type == 'BootstrapListsPlugin': + css_classes.insert(0,'nav-link navbar-text') + # strides pass + if hasattr(obj.parent.parent, 'plugin'): + css_classes.insert(0,'nav-link navbar-text') + return css_classes + + @classmethod def get_identifier(cls, obj): return mark_safe(obj.glossary.get('link_content', '')) diff --git a/cmsplugin_cascade/static/cascade/js/admin/placeholder_position.js b/cmsplugin_cascade/static/cascade/js/admin/placeholder_position.js new file mode 100644 index 000000000..51a47c30e --- /dev/null +++ b/cmsplugin_cascade/static/cascade/js/admin/placeholder_position.js @@ -0,0 +1,17 @@ +function set_placeholder_to_first_child_position(title, cms_structure_content){ + //Quick way to move placeholder selected by string name as first child position in cms structure mode + var p_header_title = `.cms-dragbar-title[title="${title}"]` + var p_header_cms_dragbar = document.querySelectorAll(p_header_title)[0]; + var p_header_cms_dragarea = p_header_cms_dragbar.parentNode.parentNode; + var isfirstchild = (p_header_cms_dragarea == p_header_cms_dragarea.parentNode.firstChild); + if (cms_structure_content.classList.contains('empty') || p_header_cms_dragbar && !isfirstchild ){ + cms_structure_content.prepend(p_header_cms_dragarea); + } +}; + +function placeholder_position_top(title_placeholder){ + var cms_structure_content = document.querySelectorAll('.cms-structure-content')[0]; + var observer = new MutationObserver(function(e) { set_placeholder_to_first_child_position(title_placeholder,cms_structure_content);}); + observer.observe(cms_structure_content, { childList: true}); +}; + diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html b/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html index fdffc3a0d..81ae3c745 100644 --- a/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/jumbotron.html @@ -28,6 +28,17 @@ } } {% endfor %} + +{% else %} + #cascadeelement_id-{{ instance.pk }} { + {# fallback instance #} + } +{% endif %} +{% if request.toolbar and request.toolbar.edit_mode_active %} +{% if 'fixed-top' in instance.glossary.values and instance.get_first_child.plugin_type == 'BootstrapNavbarPlugin' %} + body { padding-top:2.8rem } + .jumbotron-fluid { margin-top: 2.8rem !important ;} +{% endif %} {% endif %} {% endlocalize %}{% endaddtoblock %} @@ -37,5 +48,5 @@ {% for plugin in instance.child_plugin_instances %} {% render_plugin plugin %} {% endfor %} - + {% endspaceless %}{% endlocalize %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar.html new file mode 100644 index 000000000..fb75c1a3c --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar.html @@ -0,0 +1,25 @@ +{% load l10n cascade_tags thumbnail sekizai_tags %} +{% block extra-styles %}{% endblock %} +{% block extra-scripts %}{% endblock %} + +{# The Navbar plug-in compensates for the height of the CMS toolbar #} +{% if request.toolbar and request.toolbar.edit_mode_active %} + {% if instance.plugin_type == 'BootstrapNavbarPlugin' and 'fixed-top' in instance.css_classes %} + +{% endif %} +{% endif %} + +{% 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..43aa0aa49 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand.html @@ -0,0 +1,13 @@ +{% extends "cascade/link/link-base.html" %} +{% load l10n cascade_tags sekizai_tags %} +{% block link_link %}{% with instance_link=instance|default:"#" %} + {% block navbar-brand %} + {% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %} + {% for child in instance.child_plugin_instances %} + {{ block.super }} + {% render_plugin child %} + {% endfor %} +{% endwith %} + {% endblock %} +{% endwith %} +{% endblock %} 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..3a818c2ed --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_brand_image.html @@ -0,0 +1,14 @@ +{% 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 %} + {% 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..e24b26e51 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_collapse.html @@ -0,0 +1,7 @@ +{% load l10n cascade_tags sekizai_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_list.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_list.html new file mode 100644 index 000000000..e04506b2c --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_list.html @@ -0,0 +1,18 @@ +{% load l10n cascade_tags sekizai_tags i18n menu_tags %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles child_css_classes=instance.glossary.child_css_classes %} +{% block navbar-nav-list %} +{% endblock %} +{% endwith %} + + diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_list_separator.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_list_separator.html new file mode 100644 index 000000000..5fba5df37 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_list_separator.html @@ -0,0 +1 @@ +
  • diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_items_li_menu_main_links.html b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_items_li_menu_main_links.html new file mode 100644 index 000000000..d624b2899 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_nav_items_li_menu_main_links.html @@ -0,0 +1,9 @@ +{% load bootstrap_tags l10n cascade_tags sekizai_tags i18n menu_tags %} +{% block navbar-nav %} + +{% if DJANGO_CLIENT_FRAMEWORK == 'angular-ui/' %} +{% main_menu "bootstrap4/menu/navbar.html" %} +{% else %} +{% main_menu "bootstrap4/menu/ng-navbar.html" %} +{% endif %} +{% endblock %} 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..00cf42f2d --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/navbar_toogler.html @@ -0,0 +1,6 @@ +{% load bootstrap_tags cascade_tags i18n %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles child_css_classes=instance.glossary.child_css_classes %} + +{% endwith %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_collapse.html b/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_collapse.html new file mode 100644 index 000000000..dfbeaa3ca --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_collapse.html @@ -0,0 +1,7 @@ +{% load l10n cascade_tags sekizai_tags i18n %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles %} + +{% endwith %} diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_list.html b/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_list.html new file mode 100644 index 000000000..18b3f63b2 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_list.html @@ -0,0 +1,18 @@ +{% load l10n cascade_tags sekizai_tags %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles child_css_classes=instance.glossary.child_css_classes %} +{% block navbar-nav-list %} +{% endblock %} +{% endwith %} + + diff --git a/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_toogler.html b/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_toogler.html new file mode 100644 index 000000000..331348f03 --- /dev/null +++ b/cmsplugin_cascade/templates/cascade/bootstrap4/ng_navbar_toogler.html @@ -0,0 +1,6 @@ +{% load bootstrap_tags cascade_tags i18n %} +{% with instance_css_classes=instance.css_classes instance_inline_styles=instance.inline_styles child_css_classes=instance.glossary.child_css_classes %} + +{% endwith %} diff --git a/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html b/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html index 5c1f88396..b61615b78 100644 --- a/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html +++ b/cmsplugin_cascade/templates/cascade/plugins/simpleicon.html @@ -1,4 +1,4 @@ -{% load sekizai_tags %} +{% load sekizai_tags cascade_tags %} {% if stylesheet_url %}{% addtoblock "css" %}{% endaddtoblock %}{% endif %} {% spaceless %} {% with instance_link=instance.link tag_type=instance.tag_type css_classes=instance.css_classes inline_styles=instance.inline_styles %} @@ -7,7 +7,7 @@ {% elif css_classes or inline_styles %} {% endif %} - + {{ instance.glossary.content }} {% if instance_link %}{% elif css_classes or inline_styles %}{% endif %} {% endwith %} -{% endspaceless %} \ No newline at end of file +{% endspaceless %} diff --git a/examples/bs4demo/settings.py b/examples/bs4demo/settings.py index 30532ead0..ff2c8c8bf 100644 --- a/examples/bs4demo/settings.py +++ b/examples/bs4demo/settings.py @@ -213,7 +213,7 @@ ) CMSPLUGIN_CASCADE = { - 'alien_plugins': ('TextPlugin', 'TextLinkPlugin',), + 'alien_plugins': ('TextPlugin', 'TextLinkPlugin','BootstrapListPlugin', 'BootstrapNavMainMemuPlugin'), 'plugins_with_sharables': { 'BootstrapImagePlugin': ('image_shapes', 'image_width_responsive', 'image_width_fixed', 'image_height', 'resize_options',), @@ -225,17 +225,26 @@ 'cache_strides': True, } +CASCADE_CLIPS_LIBRARY = True + CMS_PLACEHOLDER_CONF = { + + 'Header Content': { + 'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin','BootstrapNavbarPlugin', 'BootstrapListPlugin', 'BootstrapNavItemsMainMemuPlugin'], + 'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None, 'BootstrapNavItemsMainMemuPlugin':None}, + + }, + # this placeholder is used in templates/main.html, it shows how to # scaffold a djangoCMS page starting with an empty placeholder 'Main Content': { - 'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'], - 'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None}, + 'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin' , 'BootstrapListPlugin'], + 'parent_classes': {'BootstrapContainerPlugin': None, 'BootstrapJumbotronPlugin': None , }, }, # this placeholder is used in templates/wrapped.html, it shows how to # add content to an existing Bootstrap column 'Bootstrap Column': { - 'plugins': ['BootstrapRowPlugin', 'TextPlugin', ], + 'plugins': ['BootstrapRowPlugin', 'TextPlugin','BootstrapListPlugin' ], 'parent_classes': {'BootstrapRowPlugin': None}, 'require_parent': False, }, diff --git a/examples/bs4demo/templates/bs4demo/base.html b/examples/bs4demo/templates/bs4demo/base.html index 002ad408b..8b22dc44b 100644 --- a/examples/bs4demo/templates/bs4demo/base.html +++ b/examples/bs4demo/templates/bs4demo/base.html @@ -11,7 +11,7 @@ {% render_block "css" %} - + {% cms_toolbar %}