diff --git a/README.md b/README.md index 7b9516acc..b242f9869 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ To contribute, first read [How to Contribute][Contributing]. | `python manage.py collectstatic` | [django](https://docs.djangoproject.com/en/3.2/howto/static-files/) | `python manage.py createsuperuser` | [django cms](https://docs.django-cms.org/en/release-3.8.x/how_to/install.html#admin-user), [django](https://docs.djangoproject.com/en/3.2/ref/django-admin/#createsuperuser) + [Camino]: https://github.com/TACC/Camino diff --git a/poetry.lock b/poetry.lock index 4dd79a685..f684671c2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -747,13 +747,15 @@ taggit-helpers = ["django-taggit-helpers"] [[package]] name = "djangocms-bootstrap4" -version = "3.0.1" +version = "3.0.2" description = "Adds Bootstrap 4 components as plugins." optional = false python-versions = "*" groups = ["main"] -files = [] -develop = false +files = [ + {file = "djangocms_bootstrap4-3.0.2-py3-none-any.whl", hash = "sha256:3b7e7eb1b7551cf433bf2bb0f3601d47645203c3bb4ad75ec097eb4bd81a34cf"}, + {file = "djangocms_bootstrap4-3.0.2.tar.gz", hash = "sha256:e931aefadbd22ab00e6cea8b7224da767afea125df15bf0c8a8d5f7872153e64"}, +] [package.dependencies] django-cms = ">=3.7,<4" @@ -767,12 +769,6 @@ djangocms-text-ckeditor = ">=3.1.0" [package.extras] static-ace = ["djangocms-static-ace"] -[package.source] -type = "git" -url = "https://github.com/django-cms/djangocms-bootstrap4.git" -reference = "49983f4" -resolved_reference = "49983f4175ec4a4e2b5076993a893cbdd79c4ab2" - [[package]] name = "djangocms-column" version = "2.0.0" @@ -2502,4 +2498,4 @@ testing = ["func-timeout", "jaraco.itertools"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "0e49bdbb7a51070f47f88658d8fb9cdcdc2c20fd3140267b933c7d0fd3f9743e" +content-hash = "2ac17635289fc7cf2692591557221b048c40769a3ea1a108915ed19fc43cd098" diff --git a/pyproject.toml b/pyproject.toml index 6b42d2cd1..e58c7c762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tacc-core-cms-backend" -version = "v4.35.14" +version = "v4.36.14" description = "DjangoCMS backend for the TACC ACI-WMA Core-CMS Codebase." authors = ["TACC-WMA "] @@ -45,9 +45,7 @@ djangocms-admin-style = "~3.2.6" djangocms-apphook-setup = "0.4.1" djangocms-attributes-field = "2.1.0" djangocms-blog = "^1.2" -# TO get a commit in main (since v3.0.1) to fix Container error -# https://github.com/django-cms/djangocms-bootstrap4/pull/164 -djangocms-bootstrap4 = {git = "https://github.com/django-cms/djangocms-bootstrap4.git", rev = "49983f4"} +djangocms-bootstrap4 = "^3.0" djangocms-column = "^2.0" djangocms-file = "3.0.0" djangocms-forms-maintained = { git = "https://github.com/TACC/djangocms-forms", rev = "6b59ff366495915f06f4d6fac01a2f0aa9efecaf" } diff --git a/taccsite_cms/_settings/djangocms_plugins.py b/taccsite_cms/_settings/djangocms_plugins.py index 21c8d6dec..5406aa1d9 100644 --- a/taccsite_cms/_settings/djangocms_plugins.py +++ b/taccsite_cms/_settings/djangocms_plugins.py @@ -38,7 +38,9 @@ ('center', _('Align center')), ] DJANGOCMS_PICTURE_TEMPLATES = [ - ('no_link_to_ext_image', _('Do not link to external image')), + ('no_link_to_ext_image', _('Do not link to "External image"')), + ('zoom_effect', _('Zoom image on hover')), + ('zoom_effect_no_link_to_ext_image', _('Zoom image on hover & Do not link to "External image"')), ] ######################## diff --git a/taccsite_cms/apps.py b/taccsite_cms/apps.py index 4775f2ae9..478f0765d 100644 --- a/taccsite_cms/apps.py +++ b/taccsite_cms/apps.py @@ -1,7 +1,9 @@ import logging + from django.apps import AppConfig from django.db.models.signals import post_migrate +from .djangocms_picture.extend import extendPicturePlugin from .djangocms_bootstrap4.contrib.bootstrap4_link.extend import extendBootstrap4LinkPlugin logger = logging.getLogger(f"portal.{__name__}") @@ -12,6 +14,7 @@ class TaccsiteCmsConfig(AppConfig): def ready(self): post_migrate.connect(self.create_groups, sender=self) + extendPicturePlugin() extendBootstrap4LinkPlugin() def create_groups(self, sender, **kwargs): diff --git a/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py b/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py deleted file mode 100644 index 3edf41136..000000000 --- a/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py +++ /dev/null @@ -1,23 +0,0 @@ -# To support generic Image plugin without uninstalling Bootstrap's -# FAQ: Bootstrap Image plugin has features not desirable in TACC plugins -# FAQ: We must not break sites that already use Bootstrap Image plugin -try: - from django.utils.translation import gettext as _ - - from cms.plugin_pool import plugin_pool - - from djangocms_picture.cms_plugins import PicturePlugin - from djangocms_bootstrap4.contrib.bootstrap4_picture.cms_plugins import Bootstrap4PicturePlugin - - # To clairfy for users how this plugin differs from Generic > Image - Bootstrap4PicturePlugin.name = _('Picture / Image (Responsive)') - - # To re-register generic Picture plugin - # SEE: https://github.com/django-cms/djangocms-bootstrap4/blob/master/djangocms_bootstrap4/contrib/bootstrap4_picture/cms_plugins.py#L54 - plugin_pool.register_plugin(PicturePlugin) - -# To avoid server crash if Boostrap plugin is not registered -# CAVEAT: If import statement fails for reason other than Bootstrap presence, -# then that failure, and the failure of this plugin, is silent -except ImportError: - pass diff --git a/taccsite_cms/custom_app_settings.example.py b/taccsite_cms/custom_app_settings.example.py index 4bc48c268..620484bf7 100644 --- a/taccsite_cms/custom_app_settings.example.py +++ b/taccsite_cms/custom_app_settings.example.py @@ -11,5 +11,7 @@ 'djangocms_blog', ] -CUSTOM_MIDDLEWARE = [] +CUSTOM_MIDDLEWARE = [ + 'taccsite_cms.middleware.settings.DynamicSiteIdMiddleware' +] STATICFILES_DIRS = () diff --git a/taccsite_cms/djangocms_picture/extend.py b/taccsite_cms/djangocms_picture/extend.py new file mode 100644 index 000000000..21798b481 --- /dev/null +++ b/taccsite_cms/djangocms_picture/extend.py @@ -0,0 +1,163 @@ +def extendPicturePlugin(): + from django.utils.translation import gettext_lazy as _ + + from cms.plugin_pool import plugin_pool + + ZOOM_TEMPLATE_NAME = 'zoom_effect' + ZOOM_TEMPLATE_LABEL = 'Zoom image on hover' + ZOOM_TEMPLATE_NOTE = _('The "%(zoom_template_label)s" templates only have effect if Image either has a Link or is within a Link.') % {"zoom_template_label": ZOOM_TEMPLATE_LABEL} + ZOOM_TEMPLATE_ERROR = _(' "%(zoom_template_label)s" templates require Image to either have a Link or be within a Link.') % {"zoom_template_label": ZOOM_TEMPLATE_LABEL} + + LINK_TEMPLATE_NAME = 'no_link_to_ext_image' + + def add_help_text(form_instance): + """Adds help text for: 'Template' field""" + + if 'template' in form_instance.fields: + form_instance.fields['template'].help_text += _(' %(ZOOM_TEMPLATE_NOTE)s') % {"ZOOM_TEMPLATE_NOTE": ZOOM_TEMPLATE_NOTE} + + def whether_to_render_link(instance): + has_explicit_link = bool(instance.link_url or instance.link_page_id) + # FAQ: The djangocms_picture has "feature" such that an image with + # "External image" URL will automatically link to that resource + has_implicit_link = bool(instance.get_link()) and not has_explicit_link + + allow_implicit_link = not LINK_TEMPLATE_NAME in instance.template + + if has_explicit_link or (has_implicit_link and allow_implicit_link): + return True + + return False + + def validate_zoom_template(instance): + """Validates: 'Template' field choice 'Zoom image …'""" + from django.core.exceptions import ValidationError + from djangocms_link.cms_plugins import LinkPlugin + + errors = {} + + would_render_link = whether_to_render_link(instance) + should_add_zoom_effect = ZOOM_TEMPLATE_NAME in instance.template + parent_plugin = instance.parent.get_plugin_instance()[0] if instance.parent else None + is_in_link = instance.parent.plugin_type == 'LinkPlugin' if instance.parent else False + + if (should_add_zoom_effect and not would_render_link and not is_in_link): + errors['template'] = ZOOM_TEMPLATE_ERROR + + if errors: + raise ValidationError(errors) + + def get_more_context_variables(instance): + """ + Calculate boolean context variables to simplify template logic. + Returns a dictionary of context variables. + """ + # Figure/Caption + has_caption_text = bool(instance.caption_text) + has_child_plugins = bool(instance.child_plugin_instances) + has_figure_content = has_caption_text or has_child_plugins + + # Template + is_zoom_template = ZOOM_TEMPLATE_NAME in instance.template + + # Link + should_render_link = whether_to_render_link(instance) + + # Zoom Effect + should_add_zoom_effect = is_zoom_template + should_wrap_image_for_zoom = ( + should_add_zoom_effect and + (has_figure_content or not should_render_link) + ) + should_add_zoom_class_to_link = ( + should_add_zoom_effect and + should_render_link and + not has_figure_content + ) + + # Attributes + should_add_attributes_to_image = ( + not has_figure_content and + not should_render_link + ) + + return { + 'should_render_link': should_render_link, + 'has_figure_content': has_figure_content, + 'should_wrap_image_for_zoom': should_wrap_image_for_zoom, + 'should_add_zoom_class_to_link': should_add_zoom_class_to_link, + 'should_add_attributes_to_image': should_add_attributes_to_image, + } + + + # djangocms_picture + from djangocms_picture.cms_plugins import PicturePlugin as OriginalPicturePlugin + from djangocms_picture.models import Picture as OriginalPicture + + class PicturePluginForm(OriginalPicturePlugin.form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + add_help_text(self) + + class PicturePluginModel(OriginalPicture): + class Meta: + proxy = True + + def clean(self): + super().clean() + validate_zoom_template(self) + + class PicturePlugin(OriginalPicturePlugin): + model = PicturePluginModel + form = PicturePluginForm + name = 'Image' + + def render(self, context, instance, placeholder): + context = super().render(context, instance, placeholder) + + more_context = get_more_context_variables(instance) + context.update(more_context) + + return context + + # To support generic Image plugin without uninstalling Bootstrap's + # FAQ: Had been done cuz Image plugin had percieved use cases, + # but is since not regularly used, but is used, thus maintained + # FAQ: No need to unregister cuz Bootstrap4PicturePlugin does that + # https://github.com/django-cms/djangocms-bootstrap4/blob/3.0.0/djangocms_bootstrap4/contrib/bootstrap4_picture/cms_plugins.py#L54 + # plugin_pool.unregister_plugin(OriginalPicturePlugin) + plugin_pool.register_plugin(PicturePlugin) + + + # djangocms_bootstrap4: bootstrap4_picture + from djangocms_bootstrap4.contrib.bootstrap4_picture.cms_plugins import Bootstrap4PicturePlugin as OriginalBootstrap4PicturePlugin + from djangocms_bootstrap4.contrib.bootstrap4_picture.models import Bootstrap4Picture as OriginalBootstrap4Picture + + class Bootstrap4PictureForm(OriginalBootstrap4PicturePlugin.form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + add_help_text(self) + + class Bootstrap4PictureModel(OriginalBootstrap4Picture): + class Meta: + proxy = True + + def clean(self): + super().clean() + validate_zoom_template(self) + + class Bootstrap4PicturePlugin(OriginalBootstrap4PicturePlugin): + model = Bootstrap4PictureModel + form = Bootstrap4PictureForm + name = 'Picture / Image (Responsive)' + + def render(self, context, instance, placeholder): + context = super().render(context, instance, placeholder) + + more_context = get_more_context_variables(instance) + context.update(more_context) + + return context + + plugin_pool.unregister_plugin(OriginalBootstrap4PicturePlugin) + plugin_pool.register_plugin(Bootstrap4PicturePlugin) diff --git a/taccsite_cms/management/commands/README.md b/taccsite_cms/management/commands/README.md index d2416238d..e28094462 100644 --- a/taccsite_cms/management/commands/README.md +++ b/taccsite_cms/management/commands/README.md @@ -2,6 +2,7 @@ - [How to Use](#how-to-use) - [List Pages Using Each Template](#list-pages-using-each-template) +- [List Pages Using Plugins](#list-pages-using-plugins) - [Set Groups & Permissions](#set-groups--permissions) - [Reference](#reference) @@ -25,6 +26,21 @@ Usage: python manage.py list_page_templates ``` +## List Pages Using Plugins + +This command lists all pages that use a specific plugin instance (given a plugin instance ID), along with the page's path. + +Usage: +```sh +python manage.py list_plugin_pages [ ...] +``` + +Example: +```sh +python manage.py list_plugin_pages 42 99 +``` + + ## Set Groups & Permissions Every file in [`group_perms/`](./group_perms) represents a group. Each group's intended usage is described at the top of its file. Permissions are set via function calls in each file.[^1] diff --git a/taccsite_cms/management/commands/list_plugin_pages.py b/taccsite_cms/management/commands/list_plugin_pages.py new file mode 100644 index 000000000..8ae1b244d --- /dev/null +++ b/taccsite_cms/management/commands/list_plugin_pages.py @@ -0,0 +1,25 @@ +from django.core.management import BaseCommand +from django.apps import apps + +class Command(BaseCommand): + help = 'Finds the page that uses a specific plugin instance by its ID' + + def add_arguments(self, parser): + parser.add_argument('plugin_instance_ids', nargs='+', type=str) + + def handle(self, *args, **options): + for plugin_instance_id in options['plugin_instance_ids']: + page = self.find_page_using_plugin_instance(plugin_instance_id) + if page: + self.stdout.write(f'Plugin instance ID {plugin_instance_id} is used on page: {page.get_path()}') + else: + self.stdout.write(f'No page found using plugin instance ID {plugin_instance_id}') + + def find_page_using_plugin_instance(self, plugin_instance_id): + try: + CMSPlugin = apps.get_model('cms', 'CMSPlugin') + plugin_instance = CMSPlugin.objects.get(id=plugin_instance_id) + page = plugin_instance.placeholder.page if plugin_instance.placeholder else None + return page + except CMSPlugin.DoesNotExist: + return None diff --git a/taccsite_cms/middleware/settings.py b/taccsite_cms/middleware/settings.py new file mode 100644 index 000000000..be2cc28b2 --- /dev/null +++ b/taccsite_cms/middleware/settings.py @@ -0,0 +1,18 @@ +"""Change site based on domain name (fallback to default site)""" +from django.conf import settings +from django.contrib.sites.models import Site + +class DynamicSiteIdMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + try: + host = request.get_host() + site = Site.objects.get(domain=host) + settings.SITE_ID = site.id + except Site.DoesNotExist: + settings.SITE_ID = getattr(settings, 'DEFAULT_SITE_ID', 1) + + response = self.get_response(request) + return response diff --git a/taccsite_cms/settings.py b/taccsite_cms/settings.py index a4a4ebeda..ed33357a4 100644 --- a/taccsite_cms/settings.py +++ b/taccsite_cms/settings.py @@ -264,6 +264,15 @@ def gettext(s): return s "is_remote": False, "img_file_src": "site_cms/img/favicons/favicon.ico", } +favicon_path = f"/static/site_cms/favicon/" +PORTAL_FAVICON_HTML = f''' + + + + + + +''' ######################## @@ -352,8 +361,10 @@ def gettext(s): return s # FAQ: List custom directory first, so custom templates take precedence # SEE: https://docs.djangoproject.com/en/2.2/topics/templates/#configuration 'DIRS': glob( + # XXX: Strange and from my ignorant implementation os.path.join(BASE_DIR, 'taccsite_custom') ) + [ + os.path.join(BASE_DIR, 'taccsite_custom', 'templates'), os.path.join(BASE_DIR, 'taccsite_cms', 'templates') ], 'OPTIONS': { @@ -484,7 +495,6 @@ def gettext(s): return s # django CMS Bootstrap # IDEA: Extend Bootstrap apps instead of overwrite 'taccsite_cms.contrib.bootstrap4_djangocms_link', - 'taccsite_cms.contrib.bootstrap4_djangocms_picture', # TACC CMS Plugins 'djangocms_tacc_image_gallery', @@ -707,6 +717,7 @@ def get_subdirs_as_module_names(path): 'PORTAL_BRANDING', 'PORTAL_LOGO', 'PORTAL_FAVICON', + 'PORTAL_FAVICON_HTML', 'PORTAL_IS_TACC_CORE_PORTAL', 'PORTAL_HAS_LOGIN', 'PORTAL_LOGIN_PATH', diff --git a/taccsite_cms/settings_custom.example.py b/taccsite_cms/settings_custom.example.py index 9cc4973ee..d413f8860 100644 --- a/taccsite_cms/settings_custom.example.py +++ b/taccsite_cms/settings_custom.example.py @@ -86,6 +86,17 @@ "is_remote": True, "img_file_src": "https://cdn.jsdelivr.net/gh/TACC/Core-CMS@v4.33.0/taccsite_cms/static/site_cms/img/favicons/favicon.ico", } +# The PORTAL_FAVICON_HTML takes precedence over PORTAL_FAVICON, +# This HTML is like output from https://realfavicongenerator.net/ circa 2025-09 +# favicon_path = "https://cdn.jsdelivr.net/gh/TACC/Core-CMS-Custom@________/________/favicon/" +# PORTAL_FAVICON_HTML = f''' +# +# +# +# +# +# +# ''' ######################## # SEARCH @@ -101,16 +112,16 @@ # DJANGOCMS_BLOG ######################## -BLOG_AUTO_SETUP = True # Set to False after setup (minimize overhead) -BLOG_AUTO_HOME_TITLE ='Home' -BLOG_AUTO_BLOG_TITLE = 'News' -BLOG_AUTO_APP_TITLE = 'News' -BLOG_AUTO_NAMESPACE = 'News' -BLOG_ENABLE_COMMENTS = False +# BLOG_AUTO_SETUP = True # Set to False after setup (minimize overhead) +# BLOG_AUTO_HOME_TITLE ='Home' +# BLOG_AUTO_BLOG_TITLE = 'News' +# BLOG_AUTO_APP_TITLE = 'News' +# BLOG_AUTO_NAMESPACE = 'News' +# BLOG_ENABLE_COMMENTS = False ######################## # DJANGOCMS_BLOG: TACC ######################## -PORTAL_BLOG_SHOW_CATEGORIES = True -PORTAL_BLOG_SHOW_TAGS = True +# PORTAL_BLOG_SHOW_CATEGORIES = True +# PORTAL_BLOG_SHOW_TAGS = True diff --git a/taccsite_cms/settings_local.example.py b/taccsite_cms/settings_local.example.py index e71f16743..68e58f35a 100644 --- a/taccsite_cms/settings_local.example.py +++ b/taccsite_cms/settings_local.example.py @@ -27,3 +27,10 @@ # IMPORTANT: Do not disable by default, because [Core-Portal clones this file](https://github.com/TACC/Core-Portal/pull/1034) # PORTAL_IS_TACC_CORE_PORTAL = False # PORTAL_HAS_LOGIN = False + +# To let user assign News article to specific Site +# BLOG_MULTISITE = True + +# To allow login in unique situations +# FAQ: If `BLOG_MULTISITE = True`, set `SESSION_COOKIE_SECURE=False` to be able to log in to CMS Admin at different domain (e.g. 0.0.0.0) +SESSION_COOKIE_SECURE = False diff --git a/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css b/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css index 4a6c5ef4f..13dadc8da 100644 --- a/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css +++ b/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.blog.app.page.css @@ -170,6 +170,15 @@ Styleguide Components.DjangoCMS.Blog.App.Page margin-inline: auto; } +/* To add space under aligned images */ +/* NOTE: All images should be aligned by news editor, + so missing space is considered news editor error */ +:--article-page .blog-content .align-right, +:--article-page .blog-content .align-center, +:--article-page .blog-content .align-left { + margin-bottom: var(--global-space--grid-gap); +} + /* To always align Bootstrap blockquote text left */ /* FAQ: Boostrap, loaded in foundation layer, used !important */ /* TODO: When extending Core-Styles c-news, .blockquote... is only for CMS */ diff --git a/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.picture.css b/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.picture.css index ee1caa672..623683b3e 100644 --- a/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.picture.css +++ b/taccsite_cms/static/site_cms/css/src/_imports/components/django.cms.picture.css @@ -2,10 +2,9 @@ /* SEE: https://github.com/django-cms/djangocms-picture/blob/2.3.0/djangocms_picture/models.py#L24-L34 */ @import url("@tacc/core-styles/src/lib/_imports/components/align.css"); -/* Allow anchor tag to wrap like a block but still behave inline */ -/* FAQ: TACC/Core-CMS causes this tag-class combination, so it must manage it */ -/* FAQ: TACC/Core-CMS moves (side effect) Picture classes to
or */ -a.img-fluid { +/* Give linked image same box-model behavior as image */ +/* FAQ: Not doing this for `figure` cuz it is already a block */ +a.editor-img-wrap { display: inline-block; } @@ -26,11 +25,11 @@ img.align-center { /* To apply djangocms-bootstrap4/…_picture class styles from parent to image */ /* FAQ: TACC/Core-CMS moves (side effect) Picture classes to
or */ /* SEE: taccsite_cms/templates/djangocms_picture/default/picture.html */ -:is(figure, a).img-fluid img { +.editor-img-wrap:is(figure, a).img-fluid img { max-width: 100%; height: auto; } -:is(figure, a).img-thumbnail img { +.editor-img-wrap:is(figure, a).img-thumbnail img { padding: 0.25rem; background-color: #fff; border: 1px solid #dee2e6; @@ -38,19 +37,18 @@ img.align-center { max-width: 100%; height: auto; } -:is(figure, a).rounded img { +.editor-img-wrap:is(figure, a).rounded img { /* NOTE: Bootstrap used 0.25rem */ border-radius: 1rem !important; /* overwrite Bootstrap (uses !important) */ } /* To undo some djangocms-bootstrap4/…_picture class styles on parent */ /* FAQ: The duplicate styles on parent tags look odd or are unnecessary */ -:is(figure, a).img-thumbnail { +.editor-img-wrap:is(figure, a).img-thumbnail { padding: unset; background-color: unset; border: unset; border-radius: unset; } -:is(figure, a).rounded { +.editor-img-wrap:is(figure, a).rounded { border-radius: unset !important; /* overwrite Bootstrap (uses !important) */ } - diff --git a/taccsite_cms/static/site_cms/css/test/djangocms-picture.css b/taccsite_cms/static/site_cms/css/test/djangocms-picture.css new file mode 100644 index 000000000..0a91e3675 --- /dev/null +++ b/taccsite_cms/static/site_cms/css/test/djangocms-picture.css @@ -0,0 +1,72 @@ +main { + .col { + /* To support notes */ + + :is(a, figure, span) { + position: relative; + } + :is(a, figure, span)::before { + position: absolute; + inset: 0 auto auto 0; + font-size: smaller; + background: rgb(from black r g b / 50%); + z-index: 1; + padding-inline: 0.5em; + } + + + /* To make notes unique */ + + a::before { + top: 0; + color: aqua; + content: ''; + } + a[class]::before { + content: ''; + } + a[data-class]::before { + content: ''; + } + + figure::before { + top: 3lh; + color: fuchsia; + content: '
'; + } + figure[class]::before { + content: '
'; + } + figure[data-class]::before { + content: '
'; + } + + span::before { + top: 6lh; + color: yellow; + content: ''; + } + + + /* To align notes to mimic tag hierarchy */ + + a > span::before, + a > figure::before, + figure > span::before { + margin-left: 1em; + } + a > figure > span::before { + margin-left: 2em; + } + + + /* To disable unsupported combos */ + + .u-image-zoom--on-hover:is( + span:not(a > *, a > figure > *), + figure:not(a > *) + ) { + opacity: 0.5; + } + } +} diff --git a/taccsite_cms/static/site_cms/favicon/apple-touch-icon.png b/taccsite_cms/static/site_cms/favicon/apple-touch-icon.png new file mode 100644 index 000000000..0697cb2d0 Binary files /dev/null and b/taccsite_cms/static/site_cms/favicon/apple-touch-icon.png differ diff --git a/taccsite_cms/static/site_cms/favicon/favicon-96x96.png b/taccsite_cms/static/site_cms/favicon/favicon-96x96.png new file mode 100644 index 000000000..202fb3677 Binary files /dev/null and b/taccsite_cms/static/site_cms/favicon/favicon-96x96.png differ diff --git a/taccsite_cms/static/site_cms/favicon/favicon.ico b/taccsite_cms/static/site_cms/favicon/favicon.ico new file mode 100644 index 000000000..9f44baf99 Binary files /dev/null and b/taccsite_cms/static/site_cms/favicon/favicon.ico differ diff --git a/taccsite_cms/static/site_cms/favicon/favicon.svg b/taccsite_cms/static/site_cms/favicon/favicon.svg new file mode 100644 index 000000000..d5550f7dc --- /dev/null +++ b/taccsite_cms/static/site_cms/favicon/favicon.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/taccsite_cms/static/site_cms/favicon/site.webmanifest b/taccsite_cms/static/site_cms/favicon/site.webmanifest new file mode 100644 index 000000000..913ac9706 --- /dev/null +++ b/taccsite_cms/static/site_cms/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Texas Advanced Computing Center", + "short_name": "TACC", + "icons": [ + { + "src": "/site_cms/favicon/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/site_cms/favicon/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/taccsite_cms/static/site_cms/favicon/web-app-manifest-192x192.png b/taccsite_cms/static/site_cms/favicon/web-app-manifest-192x192.png new file mode 100644 index 000000000..3ca5458ed Binary files /dev/null and b/taccsite_cms/static/site_cms/favicon/web-app-manifest-192x192.png differ diff --git a/taccsite_cms/static/site_cms/favicon/web-app-manifest-512x512.png b/taccsite_cms/static/site_cms/favicon/web-app-manifest-512x512.png new file mode 100644 index 000000000..c0f2de012 Binary files /dev/null and b/taccsite_cms/static/site_cms/favicon/web-app-manifest-512x512.png differ diff --git a/taccsite_cms/static/site_cms/img/favicons/favicon.ico b/taccsite_cms/static/site_cms/img/favicons/favicon.ico deleted file mode 100644 index 533966ed9..000000000 Binary files a/taccsite_cms/static/site_cms/img/favicons/favicon.ico and /dev/null differ diff --git a/taccsite_cms/templates/assets_custom.html b/taccsite_cms/templates/assets_custom.html index 60ecefb61..45d57cf73 100644 --- a/taccsite_cms/templates/assets_custom.html +++ b/taccsite_cms/templates/assets_custom.html @@ -8,9 +8,13 @@ {% endcomment %} -{% with settings.PORTAL_FAVICON as favicon %} - -{% endwith %} +{% if settings.PORTAL_FAVICON_HTML %} + {{ settings.PORTAL_FAVICON_HTML|safe }} +{% else %} + {% with settings.PORTAL_FAVICON as favicon %} + + {% endwith %} +{% endif %} {% with settings.PORTAL_STYLES as styles %} diff --git a/taccsite_cms/templates/cms_menu.html b/taccsite_cms/templates/cms_menu.html index bcd6b1e4d..0d67909c8 100644 --- a/taccsite_cms/templates/cms_menu.html +++ b/taccsite_cms/templates/cms_menu.html @@ -1,6 +1,6 @@ {% load menu_tags tacc_uri_shortcuts limit_visibility_in_menu %} -{# NOTE: This template content is copied from a third-party plugin that we do not use nor need #} +{# NOTE: This template content was copied (and changed) from 3rd-party plugin that we have long since not used #} {# SEE: https://github.com/jrief/djangocms-bootstrap/blob/aa74994/cms_bootstrap/templates/bootstrap4/menu/navbar.html #} {% spaceless %} diff --git a/taccsite_cms/templates/djangocms_picture/default/picture.html b/taccsite_cms/templates/djangocms_picture/default/picture.html index 15f50ab31..92ea2666f 100644 --- a/taccsite_cms/templates/djangocms_picture/default/picture.html +++ b/taccsite_cms/templates/djangocms_picture/default/picture.html @@ -1,45 +1,55 @@ {# https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/templates/djangocms_picture/default/picture.html #} -{# TACC (mimic v3.0.0 to v4.0.0 changes): #} {# TACC (support children as caption content): #} +{# TACC (mimic v3.0.0 to v4.0.0 changes): #} +{# TACC (support image zoom): #} {# {% load thumbnail %} #} -{% load thumbnail l10n cms_tags %} +{% load thumbnail l10n cms_tags strip_class_attribute %} +{# /TACC #} {# /TACC #} {# /TACC #} -{# TACC (allow link to be conditional): #} -{% block picture_link_start %} +{# TACC (support custom condition): #} +{% if should_render_link %} {# /TACC #} -{% if picture_link %} - {# TACC (allow link to be conditional): #} - {% block picture_link %} - {# /TACC #} -{# TACC (allow link to be conditional): #} -{% endblock %} -{# /TACC #} + {# TACC (support image zoom): #} + class=" + editor-img-wrap + {{ instance.attributes.class|default:'' }} + {{ instance.link_attributes.class|default:'' }} + {{ className }} + {% if should_add_zoom_class_to_link %}u-image-zoom--on-hover{% endif %} + " + {# TACC (assign attributes to parent): #} + {{ instance.attributes_str|strip_class_attribute|safe }} + {{ instance.link_attributes_str|strip_class_attribute|safe }} + {# /TACC #} + {# /TACC #} + > {% endif %} -{# TACC (allow link to be conditional): #} -{% endblock %} -{# /TACC #} -{# start render figure/figcaption #} + {# TACC (support children as caption content): #} -{# {% if instance.caption_text %} #} -{% if instance.caption_text or instance.child_plugin_instances %} +{% if has_figure_content %} +{# /TACC #} {# TACC (assign attributes to parent): #} - {#
#} -
+
{# /TACC #} {% endif %} -{# /TACC #} -{# end render figure/figcaption #} +{# TACC (support image zoom): #} +{% if should_wrap_image_for_zoom %} + +{% endif %} +{# /TACC #} {# TACC (mimic v3.0.0 to v4.0.0 changes): #} {% localize off %} {# /TACC #} @@ -64,7 +74,7 @@ {# TACC (allow link to be conditional): #} {% block picture_attributes %} {# TACC (assign attributes to parent): #} - {% if not instance.caption_text and not picture_link and not instance.child_plugin_instances %} + {% if should_add_attributes_to_image %} {{ instance.attributes_str }} {% endif %} {# /TACC #} @@ -74,10 +84,15 @@ {# TACC (mimic v3.0.0 to v4.0.0 changes): #} {% endlocalize %} {# /TACC #} +{# TACC (support image zoom): #} + {% if should_wrap_image_for_zoom %} + + {% endif %} +{# /TACC #} -{# start render figure/figcaption #} -{# {% if instance.caption_text %} #} -{% if instance.caption_text or instance.child_plugin_instances %} +{# TACC (support custom condition): #} +{% if has_figure_content %} +{# /TACC #} {# TACC (support children as caption content): #} {#
{{ instance.caption_text }}
#}
@@ -89,17 +104,12 @@ {# /TACC #}
{% endif %} -{# end render figure/figcaption #} -{# TACC (allow link to be conditional): #} -{% block picture_link_end %} +{# TACC (support custom condition): #} +{% if should_render_link %} {# /TACC #} -{% if picture_link %}
{% endif %} -{# TACC (allow link to be conditional): #} -{% endblock %} -{# /TACC #} {% comment %} # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img diff --git a/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html b/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html index 479af2ce9..d9e697407 100644 --- a/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html +++ b/taccsite_cms/templates/djangocms_picture/no_link_to_ext_image/picture.html @@ -1,28 +1,6 @@ {% extends "djangocms_picture/default/picture.html" %} -{# Do not let instance.external_picture trigger picture_link templating #} -{% comment %} -App djangocms_picture assumes external image must be linked to, -but we want to allow external image to be displayed without link. -{% endcomment %} -{# FAQ: picture_link is instance link_url or link_page_id or external_picture #} -{# https://github.com/django-cms/djangocms-picture/blob/3.0.0/djangocms_picture/models.py#L269-L276 #} -{# So picture with external image is not wrapped in a link #} -{% block picture_link_start %} - {% if instance.link_url or instance.link_page_id %} - {% block picture_link %} - {{ block.super }} - {% endblock %} - {% endif %} -{% endblock %} - -{# So picture attributes can be added to picture with external image #} -{% block picture_attributes %} - {% if not instance.caption_text and not instance.link_url and not instance.link_page_id and not instance.child_plugin_instances %} - {{ instance.attributes_str }} - {% endif %} -{% endblock %} - -{# So picture with external image is not wrapped in a link #} -{% block picture_link_end %} -{% endblock %} +{# To allow "External image" to be rendered without a link #} +{# FAQ: Template exists to avoid adding a new model field #} +{# FAQ: Default template uses custom boolean context variables for logic #} +{# FAQ: App djangocms_picture assumes that an image, if not explicitely linked, must link to itself. But sometimes, we want an image to just be an image, unlinked. #} diff --git a/taccsite_cms/templates/djangocms_picture/no_link_to_image/picture.html b/taccsite_cms/templates/djangocms_picture/no_link_to_image/picture.html new file mode 100644 index 000000000..5ab79b9f9 --- /dev/null +++ b/taccsite_cms/templates/djangocms_picture/no_link_to_image/picture.html @@ -0,0 +1,2 @@ +{# backwards-compatibility for temporary name #} +{% extends "djangocms_picture/no_link_to_ext_image/picture.html" %} diff --git a/taccsite_cms/templates/djangocms_picture/zoom_effect/picture.html b/taccsite_cms/templates/djangocms_picture/zoom_effect/picture.html new file mode 100644 index 000000000..f1f398d5c --- /dev/null +++ b/taccsite_cms/templates/djangocms_picture/zoom_effect/picture.html @@ -0,0 +1,5 @@ +{% extends "djangocms_picture/default/picture.html" %} + +{# To zoom image on hover #} +{# FAQ: Template exists to avoid adding a new model field #} +{# FAQ: Default template uses custom boolean context variables for logic #} diff --git a/taccsite_cms/templates/djangocms_picture/zoom_effect_no_link_to_ext_image/picture.html b/taccsite_cms/templates/djangocms_picture/zoom_effect_no_link_to_ext_image/picture.html new file mode 100644 index 000000000..e94d37638 --- /dev/null +++ b/taccsite_cms/templates/djangocms_picture/zoom_effect_no_link_to_ext_image/picture.html @@ -0,0 +1,5 @@ +{% extends "djangocms_picture/default/picture.html" %} + +{# To combine `zoom_effect` and `no_link_to_image` %} +{# SEE: taccsite_cms/templates/djangocms_picture/zoom_effect #} +{# SEE: taccsite_cms/templates/djangocms_picture/no_link_to_image #} diff --git a/taccsite_cms/templates/fullwidth.html b/taccsite_cms/templates/fullwidth.html index 98dab9413..13167975b 100755 --- a/taccsite_cms/templates/fullwidth.html +++ b/taccsite_cms/templates/fullwidth.html @@ -1,8 +1,9 @@ {% extends "base.html" %} {% load cms_tags %} -{# To remove container and breadcrumbs #} +{# To remove container and not render breadcrumbs #} {% block content %} + {% block breadcrumbs %}{% endblock breadcrumbs %} {% block cms_content %} {% placeholder "content" %} {% endblock cms_content %} diff --git a/taccsite_cms/templates/guide.html b/taccsite_cms/templates/guide.html index 72ec294d7..f2f68123d 100644 --- a/taccsite_cms/templates/guide.html +++ b/taccsite_cms/templates/guide.html @@ -13,7 +13,9 @@ {% block content %}
- {% include "nav_cms_breadcrumbs.html" %} + {% block breadcrumbs %} + {% include "nav_cms_breadcrumbs.html" %} + {% endblock breadcrumbs %} {% block guide %} {% placeholder "content" %} {% endblock guide %} diff --git a/taccsite_cms/templatetags/strip_class_attribute.py b/taccsite_cms/templatetags/strip_class_attribute.py new file mode 100644 index 000000000..24a55b428 --- /dev/null +++ b/taccsite_cms/templatetags/strip_class_attribute.py @@ -0,0 +1,22 @@ +from django import template +import re + +register = template.Library() + +@register.filter +def strip_class_attribute(attributes_str): + """ + Remove class attribute from an HTML attributes string. + Usage: {{ instance.attributes_str|strip_class_attribute|safe }} + """ + if not attributes_str: + return "" + + # To remove `class="…"` or anything equivalent + pattern = r'(? [!NOTE] -> This directory is a vestige of archived [Core CMS Resources](https://github.com/TACC/Core-CMS-Resources/tree/151ef91f) and we may re-evaluate whether to continue to use it. +> This directory is a vestige of archived [Core CMS Resources](https://github.com/TACC/Core-CMS-Resources/tree/151ef91f).