Repository: wagtail/wagtail

Issue:
Auto-collapsing StreamField blocks in the admin while editing a page
### Is your proposal related to a problem?

My clients love the Wagtail CMS but are getting increasingly frustrated when editing their pages. The major complain is that it's very difficult to have an overview of all the StreamField blocks as their pages become more content-rich.

### Describe the solution you'd like

Previously we used [wagtail-react-streamfield](https://github.com/wagtail/wagtail-react-streamfield) which had the exact feature I'd like re-implemented—on page load it's **auto-collapsing all the StreamField blocks** which allows faster scrolling and overview.

Since wagtail-react-streamfield isn't supported any longer, there's no way to have clean overview.

### Describe alternatives you've considered

Another alternative would perhaps to have a "remote control" in the top right corner of the page with in-page anchors to every StreamField block. However, this remote control would have to be updated dynamically every time a block is moved, added, or deleted.

### Additional context

Decided to create a feature request after discussion on Slack, worrying that the conversation would disappear into the void. 


Discussion:
> My clients love the Wagtail CMS but are getting increasingly frustrated when editing their pages. The major complain is that it's very difficult to have an overview of all the StreamField blocks as their pages become more content-rich.

I get the very same feedback. People love putting together content using StreamField but get overwhelmed by the confusing amount of visible editing fields.

A solution would be highly appreciated.

Thank you for your great work on Wagtail!
I would also appreciate any solution. The flexibility and usability of the streamfield means that we are using it for different purposes - not just article pages, but sometimes to display structured data in an easy to edit format. For these, the ability to collapse the elements is a boon.

Even a "Collapse all/expand all" button/function would do fine for the time being, with the option to set the initial state when you define the stream field.
Hey all, 
I'd be happy to review a PR that implements this functionality.

In the meantime you could achieve this with a bit of custom JS added via [`insert_editor_js`](https://docs.wagtail.io/en/stable/reference/hooks.html#insert-editor-js):

```js
// jQuery
$('div.c-sf-block__content[aria-hidden="false"]').each(function() {
  $('.c-sf-block__header', $(this).parent()).click()
})
```
I'd use plain Javascript though:

```js
const blocks = document.querySelectorAll('div.c-sf-block__content[aria-hidden="false"]');

blocks.forEach(function(block) {
    block.parentNode.querySelector('.c-sf-block__header').dispatchEvent(new MouseEvent("click"));
});
```
> In the meantime you could achieve this with a bit of custom JS added via [`insert_editor_js`](https://docs.wagtail.io/en/stable/reference/hooks.html#insert-editor-js):
> 
> 
> ```js
> const blocks = document.querySelectorAll('div.c-sf-block__content[aria-hidden="false"]');
> 
> blocks.forEach(function(block) {
>     block.parentNode.querySelector('.c-sf-block__header').dispatchEvent(new MouseEvent("click"));
> });
> ```

Thank you, this works for me.

My wagtail_hooks.py now looks like this:

```python
from django.utils.safestring import mark_safe

from wagtail.core import hooks

@hooks.register('insert_editor_js')
def editor_js():

    return mark_safe(
        """
        <script>
          document.addEventListener('DOMContentLoaded', function () {
              const blocks = document.querySelectorAll('div.c-sf-block__content[aria-hidden="false"]');
  
              blocks.forEach(function(block) {
                  block.parentNode.querySelector('.c-sf-block__header').dispatchEvent(new MouseEvent("click"));
              });
          });
        </script>
        """
    )
```

Now all blocks in my StreamFields are collapsed by default when editing a page.

By default on collapsed blocks, the value of the first sub-block in the StructBlock is shown as their name/description which can be confusing.
So I am now using the label_format parameter on my StreamField blocks, e.g. `label_format='Intro-Section'` to rename them for clarity. You need wagtail 2.15 for this to work.

This seems like a good interim solution to me. Thank you!

Edit: in production I had to run `./manage.py collectstatic` in order to get the hook-related static files.
I also had to clear the admin site's browser cache in order to get changed descriptions provided by `label_format` displayed.

Resolving patch:
diff --git a/wagtail/core/blocks/list_block.py b/wagtail/core/blocks/list_block.py
--- a/wagtail/core/blocks/list_block.py
+++ b/wagtail/core/blocks/list_block.py
@@ -184,6 +184,7 @@ class Meta:
         form_classname = None
         min_num = None
         max_num = None
+        collapsed = False
 
     MUTABLE_META_ATTRIBUTES = ['min_num', 'max_num']
 
@@ -194,6 +195,7 @@ class ListBlockAdapter(Adapter):
     def js_args(self, block):
         meta = {
             'label': block.label, 'icon': block.meta.icon, 'classname': block.meta.form_classname,
+            'collapsed': block.meta.collapsed,
             'strings': {
                 'MOVE_UP': _("Move up"),
                 'MOVE_DOWN': _("Move down"),
diff --git a/wagtail/core/blocks/stream_block.py b/wagtail/core/blocks/stream_block.py
--- a/wagtail/core/blocks/stream_block.py
+++ b/wagtail/core/blocks/stream_block.py
@@ -326,8 +326,9 @@ class Meta:
         min_num = None
         max_num = None
         block_counts = {}
+        collapsed = False
 
-    MUTABLE_META_ATTRIBUTES = ['required', 'min_num', 'max_num', 'block_counts']
+    MUTABLE_META_ATTRIBUTES = ['required', 'min_num', 'max_num', 'block_counts', 'collapsed']
 
 
 class StreamBlock(BaseStreamBlock, metaclass=DeclarativeSubBlocksMetaclass):
@@ -590,6 +591,7 @@ def js_args(self, block):
             'classname': block.meta.form_classname,
             'maxNum': block.meta.max_num, 'minNum': block.meta.min_num,
             'blockCounts': block.meta.block_counts,
+            'collapsed': block.meta.collapsed,
             'strings': {
                 'MOVE_UP': _("Move up"),
                 'MOVE_DOWN': _("Move down"),
diff --git a/wagtail/core/fields.py b/wagtail/core/fields.py
--- a/wagtail/core/fields.py
+++ b/wagtail/core/fields.py
@@ -63,7 +63,7 @@ def __init__(self, block_types, **kwargs):
 
         # extract kwargs that are to be passed on to the block, not handled by super
         block_opts = {}
-        for arg in ['min_num', 'max_num', 'block_counts']:
+        for arg in ['min_num', 'max_num', 'block_counts', 'collapsed']:
             if arg in kwargs:
                 block_opts[arg] = kwargs.pop(arg)
 


Test patch:
diff --git a/wagtail/core/tests/test_blocks.py b/wagtail/core/tests/test_blocks.py
--- a/wagtail/core/tests/test_blocks.py
+++ b/wagtail/core/tests/test_blocks.py
@@ -2170,6 +2170,7 @@ class LinkBlock(blocks.StructBlock):
             'label': 'Test listblock',
             'icon': 'placeholder',
             'classname': None,
+            'collapsed': False,
             'strings': {
                 'DELETE': 'Delete',
                 'DUPLICATE': 'Duplicate',
@@ -2196,6 +2197,7 @@ class LinkBlock(blocks.StructBlock):
             'label': 'Test listblock',
             'icon': 'placeholder',
             'classname': None,
+            'collapsed': False,
             'minNum': 2,
             'maxNum': 5,
             'strings': {
@@ -2321,6 +2323,7 @@ class LinkBlock(blocks.StructBlock):
             'label': 'Test listblock',
             'icon': 'placeholder',
             'classname': 'special-list-class',
+            'collapsed': False,
             'strings': {
                 'DELETE': 'Delete',
                 'DUPLICATE': 'Duplicate',
@@ -2351,6 +2354,7 @@ class Meta:
             'label': 'Test listblock',
             'icon': 'placeholder',
             'classname': 'custom-list-class',
+            'collapsed': False,
             'strings': {
                 'DELETE': 'Delete',
                 'DUPLICATE': 'Duplicate',
@@ -2726,6 +2730,7 @@ class ArticleBlock(blocks.StreamBlock):
             'label': 'Test streamblock',
             'icon': 'placeholder',
             'classname': None,
+            'collapsed': False,
             'maxNum': None,
             'minNum': None,
             'blockCounts': {},
@@ -3329,6 +3334,7 @@ def test_adapt_with_classname_via_kwarg(self):
             'minNum': None,
             'maxNum': None,
             'blockCounts': {},
+            'collapsed': False,
             'required': True,
             'classname': 'rocket-section',
             'strings': {
@@ -3360,6 +3366,7 @@ class Meta:
             'minNum': None,
             'maxNum': None,
             'blockCounts': {},
+            'collapsed': False,
             'required': True,
             'classname': 'profile-block-large',
             'strings': {