From 49b741e035465c8c83c4dc0699675b249a13dcd0 Mon Sep 17 00:00:00 2001 From: petschki Date: Fri, 6 Dec 2024 08:13:28 +0100 Subject: [PATCH] [fc] Repository: Products.CMFPlone Branch: refs/heads/6.0.x Date: 2024-12-06T06:32:22+01:00 Author: Peter Mathis (petschki) Commit: https://github.com/plone/Products.CMFPlone/commit/2d0e821d4fb804e1d7c4ea8056e33e049f81322a Allow bundles to be rendered after all other. JS and CSS resources can now be rendered after all other resources in their resource group including the theme (e.g. the Barceloneta theme CSS). There is an exception for custom CSS which can be defined in the theming controlpanel. This one is always rendered as last style resource. To render resources after all others, give them the "depends" value of "all". For each of these resources, "all" indicates that the resource depends on all other resources, making it render after its dependencies. If you set multiple resources with "all", then they will render alphabetically after all other. This lets you override a theme with custom CSS from a bundle instead of having to add the CSS customizations to the registry via the "custom_css" settings. As a consequence, theme customization can now be done in the filesystem in ordinary CSS files instead of being bound to a time consuming workflow which involves upgrading the custom_css registry after every change. Co-authored-by: Johannes Raggam <thetetet@gmail.com> Files changed: A news/4054.feature M Products/CMFPlone/resources/browser/resource.py M Products/CMFPlone/tests/testResourceRegistries.py Repository: Products.CMFPlone Branch: refs/heads/6.0.x Date: 2024-12-06T08:13:28+01:00 Author: Peter Mathis (petschki) Commit: https://github.com/plone/Products.CMFPlone/commit/33b278811031fb154501226d408886ac187359fb Merge pull request #4077 from plone/petschki-deferred-resourcegroups-60 Implement resource bundle depends on `all` to be rendered at last (6.0 branch) Files changed: A news/4054.feature M Products/CMFPlone/resources/browser/resource.py M Products/CMFPlone/tests/testResourceRegistries.py --- last_commit.txt | 81 ++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/last_commit.txt b/last_commit.txt index df8f4e3dbb..f0c12249f3 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,50 +1,55 @@ -Repository: plone.app.theming - - -Branch: refs/heads/master -Date: 2024-12-03T19:42:58-08:00 -Author: Steve Piercy (stevepiercy) -Commit: https://github.com/plone/plone.app.theming/commit/1933dd8b1bb162ac71500cc0fa6d773572fad93d - -Update link and label to Theming of Classic UI documentation - -See https://github.com/plone/Products.CMFPlone/pull/4054 - -Files changed: -M src/plone/app/theming/browser/controlpanel.pt - -b'diff --git a/src/plone/app/theming/browser/controlpanel.pt b/src/plone/app/theming/browser/controlpanel.pt\nindex 382f638..621c8d5 100644\n--- a/src/plone/app/theming/browser/controlpanel.pt\n+++ b/src/plone/app/theming/browser/controlpanel.pt\n@@ -725,10 +725,10 @@\n Define your own custom CSS in the field below. This is a good place for quick customizations of things like colors and the toolbar. Definitions here will override previously defined CSS of Plone. Please use this only for small customizations, as it is hard to keep track of changes here. For bigger changes you most likely want to customize a full theme and make your changes there.\n \n \n \n
-Commit: https://github.com/plone/plone.app.theming/commit/dc15462a8c93c7c0467aee5b4767a412884a1b2e - -Create 248.bugfix +Repository: Products.CMFPlone + + +Branch: refs/heads/6.0.x +Date: 2024-12-06T06:32:22+01:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/Products.CMFPlone/commit/2d0e821d4fb804e1d7c4ea8056e33e049f81322a + +Allow bundles to be rendered after all other. + +JS and CSS resources can now be rendered after all other resources in their +resource group including the theme (e.g. the Barceloneta theme CSS). + +There is an exception for custom CSS which can be defined in the theming +controlpanel. This one is always rendered as last style resource. + +To render resources after all others, give them the "depends" value of "all". +For each of these resources, "all" indicates that the resource depends on all other resources, making it render after its dependencies. +If you set multiple resources with "all", then they will render alphabetically after all other. + +This lets you override a theme with custom CSS from a bundle instead of having +to add the CSS customizations to the registry via the "custom_css" settings. +As a consequence, theme customization can now be done in the filesystem in +ordinary CSS files instead of being bound to a time consuming workflow which +involves upgrading the custom_css registry after every change. + + +Co-authored-by: Johannes Raggam <thetetet@gmail.com> Files changed: -A news/248.bugfix +A news/4054.feature +M Products/CMFPlone/resources/browser/resource.py +M Products/CMFPlone/tests/testResourceRegistries.py -b'diff --git a/news/248.bugfix b/news/248.bugfix\nnew file mode 100644\nindex 00000000..1feb6363\n--- /dev/null\n+++ b/news/248.bugfix\n@@ -0,0 +1 @@\n+Update the link and label under Site Setup > Theming > Advanced settings > Custom Styles to Theming of Classic UI at https://6.docs.plone.org/classic-ui/theming/index.html. @stevepiercy\n' +b'diff --git a/Products/CMFPlone/resources/browser/resource.py b/Products/CMFPlone/resources/browser/resource.py\nindex aba363c73d..d3938fa805 100644\n--- a/Products/CMFPlone/resources/browser/resource.py\n+++ b/Products/CMFPlone/resources/browser/resource.py\n@@ -79,9 +79,15 @@ def update(self):\n registry_group_js = webresource.ResourceGroup(\n name="registry_js", group=root_group_js\n )\n+ registry_group_js_deferred = webresource.ResourceGroup(\n+ name="registry_js_deferred",\n+ )\n registry_group_css = webresource.ResourceGroup(\n name="registry_css", group=root_group_css\n )\n+ registry_group_css_deferred = webresource.ResourceGroup(\n+ name="registry_css_deferred",\n+ )\n records = self.registry.collectionOfInterface(\n IBundleRegistry, prefix="plone.bundles", check=False\n )\n@@ -107,7 +113,7 @@ def check_dependencies(bundle_name, depends, bundles):\n valid_dependencies = []\n \n for name in depend_names:\n- if name in bundles:\n+ if name in bundles or name == "all":\n valid_dependencies.append(name)\n continue\n if name in all_names:\n@@ -146,6 +152,11 @@ def check_dependencies(bundle_name, depends, bundles):\n if depends == "__broken__":\n continue\n external = self.is_external_url(record.jscompilation)\n+ r_group = registry_group_js\n+ if "all" in depends:\n+ # move to a separate group which is rendered after all others\n+ r_group = registry_group_js_deferred\n+ depends = None\n PloneScriptResource(\n context=self.context,\n name=name,\n@@ -155,7 +166,7 @@ def check_dependencies(bundle_name, depends, bundles):\n include=include,\n expression=record.expression,\n unique=unique,\n- group=registry_group_js,\n+ group=r_group,\n url=record.jscompilation if external else None,\n crossorigin="anonymous" if external else None,\n async_=record.load_async or None,\n@@ -168,6 +179,11 @@ def check_dependencies(bundle_name, depends, bundles):\n if depends == "__broken__":\n continue\n external = self.is_external_url(record.csscompilation)\n+ r_group = registry_group_css\n+ if "all" in depends:\n+ # move to a separate group which is rendered after all others\n+ r_group = registry_group_css_deferred\n+ depends = None\n PloneStyleResource(\n context=self.context,\n name=name,\n@@ -177,7 +193,7 @@ def check_dependencies(bundle_name, depends, bundles):\n include=include,\n expression=record.expression,\n unique=unique,\n- group=registry_group_css,\n+ group=r_group,\n url=record.csscompilation if external else None,\n media="all",\n rel="stylesheet",\n@@ -237,7 +253,11 @@ def check_dependencies(bundle_name, depends, bundles):\n **{"data-bundle": "diazo"},\n )\n \n- # add Custom CSS\n+ # add "deferred" groups at this point\n+ root_group_js.add(registry_group_js_deferred)\n+ root_group_css.add(registry_group_css_deferred)\n+\n+ # add Custom CSS always after everything else\n registry = getUtility(IRegistry)\n theme_settings = registry.forInterface(IThemeSettings, False)\n if theme_settings.custom_css:\ndiff --git a/Products/CMFPlone/tests/testResourceRegistries.py b/Products/CMFPlone/tests/testResourceRegistries.py\nindex f33fa292bc..c27639d5a8 100644\n--- a/Products/CMFPlone/tests/testResourceRegistries.py\n+++ b/Products/CMFPlone/tests/testResourceRegistries.py\n@@ -1,3 +1,4 @@\n+from lxml import etree\n from OFS.Image import File\n from plone.app.testing import logout\n from plone.app.testing import setRoles\n@@ -20,17 +21,17 @@\n \n \n class TestScriptsViewlet(PloneTestCase.PloneTestCase):\n- def _make_test_bundle(self):\n+ def _make_test_bundle(self, name="foobar", depends=""):\n registry = getUtility(IRegistry)\n \n bundles = registry.collectionOfInterface(\n IBundleRegistry, prefix="plone.bundles"\n )\n- bundle = bundles.add("foobar")\n- bundle.name = "foobar"\n- bundle.jscompilation = "http://foo.bar/foobar.js"\n- bundle.csscompilation = "http://foo.bar/foobar.css"\n- bundle.resources = ["foobar"]\n+ bundle = bundles.add(name)\n+ bundle.name = name\n+ bundle.jscompilation = f"http://foo.bar/{name}.js"\n+ bundle.csscompilation = f"http://foo.bar/{name}.css"\n+ bundle.depends = depends\n return bundle\n \n def test_bundle_defernot_asyncnot(self):\n@@ -173,6 +174,75 @@ def test_bundle_depends_on_multiple(self):\n results = view.render()\n self.assertIn("http://foo.bar/foobar.js", results)\n \n+ def test_js_bundle_depends_all(self):\n+ # Create a test bundle, which has unspecified dependencies and is\n+ # rendered in order as defined.\n+ self._make_test_bundle(name="a")\n+\n+ # Create a test bundle, which depends on "all" other and thus rendered\n+ # last.\n+ self._make_test_bundle(name="last", depends="all")\n+\n+ # Create a test bundle, which has unspecified dependencies and is\n+ # rendered in order as defined.\n+ self._make_test_bundle(name="b")\n+\n+ view = ScriptsView(self.layer["portal"], self.layer["request"], None)\n+ view.update()\n+ results = view.render()\n+\n+ parser = etree.HTMLParser()\n+ parsed = etree.fromstring(results, parser)\n+ scripts = parsed.xpath("//script")\n+\n+ # The last element is our JS, depending on "all".\n+ self.assertEqual(\n+ "http://foo.bar/last.js",\n+ scripts[-1].attrib["src"],\n+ )\n+\n+ # The first resource is our JS, which was defined with unspecified\n+ # dependency first.\n+ self.assertEqual(\n+ "http://foo.bar/a.js",\n+ scripts[0].attrib["src"],\n+ )\n+\n+ # The second resource is our JS, which was defined with unspecified\n+ # dependency last.\n+ self.assertEqual(\n+ "http://foo.bar/b.js",\n+ scripts[1].attrib["src"],\n+ )\n+\n+ # When more bundles depend on "all", they are ordered alphabetically\n+ # at the end.\n+ self._make_test_bundle(name="x-very-last", depends="all")\n+ self._make_test_bundle(name="a-last", depends="all")\n+\n+ # make sure cache purged\n+ setattr(self.layer["request"], REQUEST_CACHE_KEY, None)\n+\n+ view.update()\n+ results = view.render()\n+\n+ parsed = etree.fromstring(results, parser)\n+ scripts = parsed.xpath("//script")\n+\n+ # All the "all" depending bundles are sorted alphabetically at the end.\n+ self.assertEqual(\n+ "http://foo.bar/x-very-last.js",\n+ scripts[-1].attrib["src"],\n+ )\n+ self.assertEqual(\n+ "http://foo.bar/last.js",\n+ scripts[-2].attrib["src"],\n+ )\n+ self.assertEqual(\n+ "http://foo.bar/a-last.js",\n+ scripts[-3].attrib["src"],\n+ )\n+\n def test_bundle_depends_on_missing(self):\n bundle = self._make_test_bundle()\n bundle.depends = "nonexistsinbundle"\n@@ -217,6 +287,19 @@ def test_relative_uri_resource(self):\n \n \n class TestStylesViewlet(PloneTestCase.PloneTestCase):\n+ def _make_test_bundle(self, name="foobar", depends=""):\n+ registry = getUtility(IRegistry)\n+\n+ bundles = registry.collectionOfInterface(\n+ IBundleRegistry, prefix="plone.bundles"\n+ )\n+ bundle = bundles.add(name)\n+ bundle.name = name\n+ bundle.jscompilation = f"http://foo.bar/{name}.js"\n+ bundle.csscompilation = f"http://foo.bar/{name}.css"\n+ bundle.depends = depends\n+ return bundle\n+\n def test_styles_viewlet(self):\n styles = StylesView(self.layer["portal"], self.layer["request"], None)\n styles.update()\n@@ -323,6 +406,86 @@ def test_remove_bundle_on_request_with_subrequest(self):\n result = scripts.render()\n self.assertNotIn("http://test.foo/test.min.js", result)\n \n+ def test_css_bundle_depends_all(self):\n+ # Create a test bundle, which has unspecified dependencies and is\n+ # rendered in order as defined.\n+ self._make_test_bundle(name="a")\n+\n+ # Create a test bundle, which depends on "all" other and thus rendered\n+ # last.\n+ self._make_test_bundle(name="last", depends="all")\n+\n+ # Create a test bundle, which has unspecified dependencies and is\n+ # rendered in order as defined.\n+ self._make_test_bundle(name="b")\n+\n+ view = StylesView(self.layer["portal"], self.layer["request"], None)\n+ view.update()\n+ results = view.render()\n+\n+ parser = etree.HTMLParser()\n+ parsed = etree.fromstring(results, parser)\n+ styles = parsed.xpath("//link")\n+\n+ # The last element is our CSS, depending on "all".\n+ self.assertEqual(\n+ "http://foo.bar/last.css",\n+ styles[-1].attrib["href"],\n+ )\n+\n+ # The second last element is the theme barceloneta theme CSS.\n+ self.assertTrue(\n+ "++theme++barceloneta/css/barceloneta.min.css" in styles[-2].attrib["href"],\n+ )\n+\n+ # The first resource is our CSS, which was defined with unspecified\n+ # dependency.\n+ self.assertEqual(\n+ "http://foo.bar/a.css",\n+ styles[0].attrib["href"],\n+ )\n+\n+ # The second resource is our CSS, which was defined with unspecified\n+ # dependency first.\n+ self.assertEqual(\n+ "http://foo.bar/b.css",\n+ styles[1].attrib["href"],\n+ )\n+\n+ def test_css_bundle_depends_all_but_custom(self):\n+ registry = getUtility(IRegistry)\n+\n+ custom_key = "plone.app.theming.interfaces.IThemeSettings.custom_css"\n+ registry[custom_key] = "html { background-color: red; }"\n+\n+ # Create a test bundle, which depends on "all" other and thus rendered\n+ # after all except the custom styles.\n+ self._make_test_bundle(name="almost-last", depends="all")\n+\n+ view = StylesView(self.layer["portal"], self.layer["request"], None)\n+ view.update()\n+ results = view.render()\n+\n+ parser = etree.HTMLParser()\n+ parsed = etree.fromstring(results, parser)\n+ styles = parsed.xpath("//link")\n+\n+ # The last element is are the custom styles.\n+ self.assertTrue(\n+ "@@custom.css" in styles[-1].attrib["href"],\n+ )\n+\n+ # The second last element is now our CSS, depending on "all".\n+ self.assertEqual(\n+ "http://foo.bar/almost-last.css",\n+ styles[-2].attrib["href"],\n+ )\n+\n+ # The third last element is the theme barceloneta theme CSS.\n+ self.assertTrue(\n+ "++theme++barceloneta/css/barceloneta.min.css" in styles[-3].attrib["href"],\n+ )\n+\n \n class TestExpressions(PloneTestCase.PloneTestCase):\n def logout(self):\ndiff --git a/news/4054.feature b/news/4054.feature\nnew file mode 100644\nindex 0000000000..c7a171ee2a\n--- /dev/null\n+++ b/news/4054.feature\n@@ -0,0 +1,18 @@\n+Allow bundles to be rendered after all others.\n+\n+JS and CSS resources can now be rendered after all other resources in their\n+resource group including the theme (e.g. the Barceloneta theme CSS).\n+\n+There is an exception for custom CSS which can be defined in the theming\n+controlpanel. This one is always rendered as last style resource.\n+\n+To render resources after all others, give them the "depends" value of "all".\n+For each of these resources, "all" indicates that the resource depends on all other resources, making it render after its dependencies.\n+If you set multiple resources with "all", then they will render alphabetically after all other.\n+\n+This lets you override a theme with custom CSS from a bundle instead of having\n+to add the CSS customizations to the registry via the "custom_css" settings.\n+As a consequence, theme customization can now be done in the filesystem in\n+ordinary CSS files instead of being bound to a time consuming workflow which\n+involves upgrading the custom_css registry after every change.\n+[thet, petschki]\n' -Repository: plone.app.theming +Repository: Products.CMFPlone -Branch: refs/heads/master -Date: 2024-12-04T06:32:57-08:00 -Author: Steve Piercy (stevepiercy) -Commit: https://github.com/plone/plone.app.theming/commit/907d61d66eab7c649ed0b2f5c02e3dd3e57e319d +Branch: refs/heads/6.0.x +Date: 2024-12-06T08:13:28+01:00 +Author: Peter Mathis (petschki) +Commit: https://github.com/plone/Products.CMFPlone/commit/33b278811031fb154501226d408886ac187359fb -Merge pull request #248 from plone/docs-classic-ui-theming +Merge pull request #4077 from plone/petschki-deferred-resourcegroups-60 -Update link and label to Theming of Classic UI documentation +Implement resource bundle depends on `all` to be rendered at last (6.0 branch) Files changed: -A news/248.bugfix -M src/plone/app/theming/browser/controlpanel.pt +A news/4054.feature +M Products/CMFPlone/resources/browser/resource.py +M Products/CMFPlone/tests/testResourceRegistries.py -b'diff --git a/news/248.bugfix b/news/248.bugfix\nnew file mode 100644\nindex 00000000..1feb6363\n--- /dev/null\n+++ b/news/248.bugfix\n@@ -0,0 +1 @@\n+Update the link and label under Site Setup > Theming > Advanced settings > Custom Styles to Theming of Classic UI at https://6.docs.plone.org/classic-ui/theming/index.html. @stevepiercy\ndiff --git a/src/plone/app/theming/browser/controlpanel.pt b/src/plone/app/theming/browser/controlpanel.pt\nindex 382f638b..621c8d5d 100644\n--- a/src/plone/app/theming/browser/controlpanel.pt\n+++ b/src/plone/app/theming/browser/controlpanel.pt\n@@ -725,10 +725,10 @@\n Define your own custom CSS in the field below. This is a good place for quick customizations of things like colors and the toolbar. Definitions here will override previously defined CSS of Plone. Please use this only for small customizations, as it is hard to keep track of changes here. For bigger changes you most likely want to customize a full theme and make your changes there.\n
\n \n \n