Code Monkey home page Code Monkey logo

franklin-sidekick-library's Introduction

AEM Sidekick Library

codecov GitHub license GitHub issues semantic-release

This repository contains the Library plugin for the Franklin Sidekick.

DEMO

What is the Sidekick Library?

The Sidekick Library is an extension for the AEM Sidekick that enables developers to create UI-driven tooling for content authors. It includes a built-in blocks plugin that can display a list of all blocks to authors in an intuitive manner, removing the need for authors to remember or search for every variation of a block. Developers can also write their own plugins for the sidekick library.

How to use the Sidekick Library?

The steps below detail how to setup the sidekick library and configure the blocks plugin.

Library Sheet Setup

The sidekick library is populated with your plugins and plugin content using a sheet.

  1. Start by creating a directory in sharepoint or gdrive where you want to store the content for the library. We recommmending storing the content in /tools/sidekick (or any other name) in the root of the mountpoint. The next steps will assume the directory is called /tools/sidekick.
  2. Next create a workbook (an Excel file) in the /tools/sidekick directory called library (or any other name). Each sheet in the workbook represents a plugin that will be loaded by the Sidekick Library. The name of the sheet determines the name of the plugin that will be loaded. Any data contained in the sheet will be passed to the plugin when loaded. The plugin sheet name must be prepended with helix-. For example, if you want to load a plugin called tags, you would create a sheet named helix-tags.
  3. For this tutorial we will create a sheet for our blocks plugin. Create a sheet (or rename the default sheet) and call it helix-blocks and leave it empty for now.

Blocks Plugin

The Sidekick library comes with a blocks plugin.

library.blocks.plugin.mp4

Blocks Plugin Setup

To generate content for the blocks plugin, you need to prepare a separate Word document for each block you want to include.

  1. Create a directory inside the /tools/sidekick directory where you will store all the block variations. For example, you could create a directory called blocks inside /tools/sidekick.
  2. For this example, let's assume we want to define all the variations of a block called columns. First create a Word document called columns inside the blocks directory and provide examples of all the variations of the columns block. After each variation of the block add in a section delimiter. (Example).
  3. Preview and publish the columns document.
  4. Open the library workbook created in the last section, inside the helix-blocks sheet, create two columns named name and path.
  5. Next we need to add a row for our columns block. Add the name of the block in the first column and the url to the document that defines the block variations in the second column. For instance, if you want to add the columns block, you could create a row with the name Columns and the path /tools/sidekick/blocks/columns. In order for the library to work across enviroments (page, live, prod) you should not use an absolute url for the path column.
  6. Preview and publish the library workbook.

Since the example blocks are being published you should use bulk metadata to exclude the content inside of /tools/** from being indexed.

Screenshot 2024-04-09 at 2 54 19 PM

Example library.xlsx

Library.xlsx

Library Metadata

The blocks plugins supports a special type of block called library metadata which provides a way for developers to tell the blocks plugin some information about the block or how it should be rendered.

Supported library metadata options

Key Name Value Description Required
name Name of the block Allows you to set a custom name for the block false
description A description of the block Allows you to set a custom description for a block false
type The type of the block This tells the blocks plugin how to group the content that makes up your block. Possible options are template or section (details below) false
include next sections How many sections to include in the block item Use if your block requires content from subsequence sections in order to render. Should be a number value that indicates how much subsequent sections to include. false
searchtags A comma seperated list of search terms Allows you to define other terms that could be used when searching for this block in the blocks plugin false
tableHeaderBackgroundColor A hex color (ex #ff3300) Overrides the table header background color for any blocks in the section or page. false
tableHeaderForegroundColor A hex color (ex #ffffff) Overrides the table header foreground color for any blocks in the section or page. false
contentEditable A boolean value (default: true) Set to false to disable content editing in the preview window. false
disableCopy A boolean value (default: false) Set to true to disable the copy button in the preview window. false
hideDetailsView A boolean value (default: false) Hide the block details panel inside the preview window. false

Default Library metadata vs Library metadata

There are two types of library metadata. Library metadata that lives within a section containing the block, or default library metadata that applies to the document as a whole and lives in a section on it's own (a block called library metadata as the only child in a section).

Let's take an example of a hero block that has 5 variants. Suppose you want to add the same description for each variation of the block, rather than duplicating the library metadata with the description into each section containing the variations. You could instead use default library metadata to apply the same description to every variation of the block. If you decide that one variation actually needs a slightly different description you could add library metadata to the section containing the variation and it would override the default library metadata description when it's rendered within the blocks plugin.

Authoring block names and descriptions using Library Metadata

By default the block name (with variation) will be used to render the item in the blocks plugin. For example, if the name of the block is columns (center, background) than that name will be used as the label when it’s rendered in the blocks plugin. This can be customized by creating a library metadata section within the same section as the block. Library metadata can also be used to author a description of the block as well as adding searchTags to include an alias for the block when using the search feature.

Example block with custom name and description

Content

Screenshot 2023-06-08 at 1 11 09 PM

Display

Screenshot 2023-06-08 at 1 13 32 PM

Autoblocks and Default Content

The blocks plugin is capable of rendering default content and autoblocks. In order to achieve this, it is necessary to place your default content or autoblock within a dedicated section, which should include a library metadata table defining a name property, as previously described. If no name is specified in the library metadata, the item will be labeled as "Unnamed Item."

Blocks composed of content in subsequent sections

There are situations where developers may want a block to consist of content from subsequent sections. This pattern is discouraged for reasons stated here, but if you choose to use it the blocks plugin can render these items using the include next sections property in library metadata.

Screenshot 2023-09-07 at 2 42 13 PM

Templates

Templates are a way to group an entire document into a single element in the sidekick library. To mark a document as a template set type to template in default library metadata.

Important, the library metadata needs to be in it's own section and be the only child to be considered default library metadata.

Supporting metadata is also desirable for templates. To add a metadata table to the template you can use a Page metadata block.

266064147-12883ee0-147b-4171-b89a-c313e33eef24

When the template is copied a metadata with the values will be added along with the content to the clipboard.

Sidekick plugin setup

Since the sidekick library is hosted on the same origin as the content, a static HTML page needs to be created to load and configure the content.

  1. Create a file called library.html in tools/sidekick/;

  2. Paste the following code in library.html.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, viewport-fit=cover"
    />
    <meta name="Description" content="AEM Sidekick Library" />
    <meta name="robots" content="noindex" />
    <base href="/" />

    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        font-family: sans-serif;
        background-color: #ededed;
        height: 100%;
      }
      
      helix-sidekick { display: none }
    </style>
    <title>Sidekick Library</title>
  </head>

  <body>
    <script
      type="module"
      src="https://www.aem.live/tools/sidekick/library/index.js"
    ></script>
    <script>
      const library = document.createElement('sidekick-library')
      library.config = {
        base: '/tools/sidekick/library.json',
      }

      document.body.prepend(library)
    </script>
  </body>
</html>

In the code above we load the sidekick library from aem.live and then create a custom sidekick-library element and add it to the page. The sidekick-library element accepts a config object that is required to configure the sidekick library.

Supported configuration parameters

Parameter Name Value Description Required
base Path to the library The base library to be loaded true
extends Absolute URL to the extended library A library to extend the base library with
plugins An object containing plugins to register with the sidekick library. The plugins object can be used to register plugins and configure data that should be passed to the plugin false

The blocks plugin supports the following configuration properties that can be set using the plugins object.

Blocks plugin configuration parameters

Parameter Name Value Description Required
encodeImages A boolean value that indicates if images should be encoded during copy operations If your site has a Zero trust network access (ZTNA) service enabled such as Cloudflare Access then images should be encoded for copy/paste operations to work correctly with images. false
viewPorts Full or simplified configuration object, see examples below. Configuration to overwrite the default viewport sizes. The default is 480px fo mobile, 768px for tablet and 100% of the current window for desktop. false
contentEditable A boolean value to disable content editing in all block previews. Set to false to disable content editing. false

Examples

const library = document.createElement('sidekick-library')
library.config = {
  base: '/tools/sidekick/library.json',
  plugins: {
    blocks: {
      encodeImages: true,
    }
  }
}
const library = document.createElement('sidekick-library')
library.config = {
  base: '/tools/sidekick/library.json',
  plugins: {
    blocks: {
      viewPorts: [600, 900],
    }
  }
}
const library = document.createElement('sidekick-library')
library.config = {
  base: '/tools/sidekick/library.json',
  plugins: {
    blocks: {
      viewPorts: [
        {
          width: '599px',
          label: 'Small',
          icon: 'device-phone',
        },
        {
          width: '899px',
          label: 'Medium',
          icon: 'device-tablet',
        },
        {
          width: '100%',
          label: 'Large',
          icon: 'device-desktop',
          default: true,
        },
      ],
    }
  }
}

Custom table header colors

You can customize the table header background and foreground color when pasting a block, section metadata or metadata that was copied from the blocks plugin.

Default styles can be set in library.html using css variables.

  <style>
    :root {
      --sk-block-table-background-color: #03A;
      --sk-block-table-foreground-color: #fff;

      --sk-section-metadata-table-background-color: #f30;
      --sk-section-metadata-table-foreground-color: #000;

      --sk-metadata-table-background-color: #000;
      --sk-metadata-table-foreground-color: #fff;
    }
  </style>

These values can be overridden using library metadata.

Depending on the system color scheme selected for the users computer (dark mode), Word may alter the chosen colors in an attempt to improve accessibility.

Custom plugin setup

The example below defines a tags plugin in the config. The keys of the plugins object must match the name of the plugin, any other properties defined in the plugin object will be available to the plugin via the context argument of the decorate method.

const library = document.createElement('sidekick-library')
library.config = {
  base: '/tools/sidekick/library.json',
  plugins: {
    tags: {
      src: '/tools/sidekick/plugins/tags/tags.js',
      foo: 'bar'
    }
  }
}

Extended Libraries

In some cases merging two block libraries may be desirable. When an extended library is defined the sidekick library application will merge the base library and the extended library together into a single library list for authors.

The example below defines a base library and an extended library (on another origin) that will be merged into the base library.

const library = document.createElement('sidekick-library')
library.config = {
  base: '/tools/sidekick/library.json',
  extends: 'https://main--repo--owner.hlx.live/tools/sidekick/library.json'
}

The Access-Control-Allow-Origin headers will need to be set on the library.json and blocks of the extended library in order for them to load in the sidekick library. See Custom HTTP Response Headers for more info.

Due to same-origin policies enforced by browsers on iframes a preview of an extended block cannot be loaded at this time.

Sidekick config.json setup

Next, in order for the sidekick library to appear in the sidekick a config file needs to be created at tools/sidekick/config.json. This config file needs to be created in the code bus and should be checked into github.

{
  "project": "Example",
  "plugins": [
    {
      "id": "library",
      "title": "Library",
      "environments": ["edit"],
      "url": "/tools/sidekick/library.html",
      "excludePaths": ["**"],
      "includePaths": ["**.docx**", "/document/**"]
    }
  ]
}

The url property in the plugin configuration indicates the location from which the sidekick should load the plugin. This should point to the library.html file we previously created.

The sidekick config must be checked into the main branch in order to for the plugin to appear in the sidekick.

If the tools/sidekick/config.json file does not exist in your github repository, it must be created. For more information on sidekick plugin configuration options, see the docs.

Considerations when building blocks for the library

The sidekick library renders blocks by first fetching the plain.html rendition of the the block and then strips it of any other blocks in the content (for example if there are multiple variations of a block in the response). It then requests the same page (without .plain.html) and replaces the main element with the stripped block and loads the entire document into an iframe using the srcdoc attribute.

Use of window.location

Since the block is loaded in an iframe using the srcdoc attribute, the instance of the window.location object used by your sites code will not contain the typical values you would expect to see.

Example window.location object when running in the library

{
  "host": "",
  "hostname": "",
  "href": "about:srcdoc"
  "origin": "null"
  "pathname": "srcdoc"
  "port": ""
  "protocol": "about:"
}

For this reason, if your block requires use of the window.location object we recommend adding the following functions to your scripts.js file and importing them into your function for use.

/**
 * Returns the true origin of the current page in the browser.
 * If the page is running in a iframe with srcdoc, the ancestor origin is returned.
 * @returns {String} The true origin
 */
export function getOrigin() {
  const { location } = window;
  return location.href === 'about:srcdoc' ? window.parent.location.origin : location.origin;
}

/**
 * Returns the true of the current page in the browser.mac
 * If the page is running in a iframe with srcdoc,
 * the ancestor origin + the path query param is returned.
 * @returns {String} The href of the current page or the href of the block running in the library
 */
export function getHref() {
  if (window.location.href !== 'about:srcdoc') return window.location.href;

  const { location: parentLocation } = window.parent;
  const urlParams = new URLSearchParams(parentLocation.search);
  return `${parentLocation.origin}${urlParams.get('path')}`;
}

Use of createOptimizedPicture in lib-franklin

The createOptimizedPicture function in lib-franklin also uses window.location.href. If you are using this function we recommend to move it into scripts.js and modify it to use the getHref() function above.

Checking for the presence of the sidekick library

Sometimes you may want to know if the page or the block is running in the sidekick library. To do this there are a couple of options.

  1. Check if window.location.href === 'about:srcdoc'
  2. The main element and the block element will contain the sidekick-library class

Building a Plugin

Developing a plugin is similar to constructing a block in Franklin. Once a user tries to load the plugin, the sidekick library will trigger the decorate() method on your plugin. This method receives the container to render the plugin in and any data that included in the plugins sheet.

/**
 * Called when a user tries to load the plugin
 * @param {HTMLElement} container The container to render the plugin in
 * @param {Object} data The data contained in the plugin sheet
 * @param {String} query If search is active, the current search query
 * @param {Object} context contains any properties set when the plugin was registered
 */
export async function decorate(container, data, query, context) {
  // Render your plugin
}

The decorate() function must be exported from the plugin.

Plugin default export & search

The default export from a plugin allows authors have the ability to customize the plugin name displayed in the header upon loading, as well as activate the search functionality within the sidekick library.

export default {
  title: 'Tags',
  searchEnabled: true,
};

When the searchEnabled property is true, the library header will display a search icon upon loading the plugin. If the user initiates a search by entering a query, the decorate() function of the plugin will be triggered again, this time with the search string passed in the query parameter of the decorate() function.

Plugin web components

Plugin authors can utilize a select set of web components from Spectrum when building a custom plugin.

The following components from Spectrum are available

Component Documentation Link
sp-tooltip Docs
sp-toast Docs
sp-textfield Docs
sp-sidenav-item Docs
sp-sidenav Docs
sp-search Docs
sp-progress-circle Docs
sp-picker Docs
sp-menu-item Docs
sp-menu-group Docs
sp-menu-divider Docs
sp-menu Docs
sp-illustrated-message Docs
sp-divider Docs
sp-card Docs
sp-button-group Docs
sp-button Docs
sp-action-button Docs
overlay-trigger Docs

The following icons from Spectrum are also available

Component Documentation Link
sp-icon-search Docs
sp-icon-file-template Docs
sp-icon-file-code Docs
sp-icon-device-phone Docs
sp-icon-device-tablet Docs
sp-icon-device-desktop Docs
sp-icon-magic-wand Docs
sp-icon-copy Docs
sp-icon-preview Docs
sp-icon-info Docs
sp-icon-view-detail Docs
sp-icon-chevron-right Docs
sp-icon-chevron-left Docs

Plugin Events

Plugin authors can dispatch events from their plugin to the parent sidekick library in order to display a loader or to show a toast message.

Toast Messages

import { PLUGIN_EVENTS } from 'https://www.aem.live/tools/sidekick/library/events/events.js';

export async function decorate(container, data, query) {
  // Show a toast message
  container.dispatchEvent(new CustomEvent(PLUGIN_EVENTS.TOAST,  { detail: { message: 'Toast Shown!', variant: 'positive | negative' } }))
}

Show and Hide Loader

import { PLUGIN_EVENTS } from 'https://www.aem.live/tools/sidekick/library/events/events.js';

export async function decorate(container, data, query) {
  // Show loader
  container.dispatchEvent(new CustomEvent(PLUGIN_EVENTS.SHOW_LOADER))
  ...
  // Hide loader
  container.dispatchEvent(new CustomEvent(PLUGIN_EVENTS.HIDE_LOADER))
}

Example plugin

Tags Plugin

Plugin API Example

Development

If testing a customer configuration locally that does not have the correct CORS headers set you will have to run the in a browser with web security disabled.

Install Dependencies

$ npm install

Start Development Server

$ npm run start

Run Tests

$ npm test

Lint

$ npm run lint

Build

$ npm run build

franklin-sidekick-library's People

Contributors

dylandepass avatar nc-andreashaller avatar rofe avatar semantic-release-bot avatar shsteimer avatar trieloff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

franklin-sidekick-library's Issues

Add `sidekick-library` class to body tag when rendering

If a site added structural css to the body tag they require a way to update that structure when running in the sidekick library.

Example, the body tag uses grid to layout it's children (Header on the left, Content on the right). When running in the sidekick library the grid needs to be modified to not include the Header in the layout. Currently the only way to do this is by adding some custom js or by using the has selector. Both are not great options.

Adding the sidekick-library class to the body tag will allow developers to use css to control the layout of the body when running in the sidekick library.

Normalize data keys or document explicit naming requirement

Expected Behaviour

When using the terms Name and Path for the column headers in the library.xlsx, the plugin renders correctly.

Actual Behaviour

Only the literal terms name and path for the column headers in the library.xlsx will allow the plugin to render.

If we don't want to normalize these, then the documentation should explicitly state that name and path must be all lowercase. Though I feel like we should normalize the data, rather than expect an explicit value.

Steps to Reproduce

Change the header in any configuration to Path.

Rename block names to not include the lower

The block library plugin operates on the .plain.html version of the document and not the source document in word or it's markdown equivalent. As such, the original naming of a block could be Core Hero but becomes core-hero when it's sent through the pipeline (.plain.html).

When pasted, should we keep the dash notation (core-hero) or should we assume it should be Core Hero and modify the header name? Does the same thing need to be applied to variations?

Add iframe plugins support

Some customers may like to add plugins that embed external applications using an iframe. As an example, to integrate the assets selector plugin (AEM assets) an iframe plugin is required since the application must be served from experience.adobe.com.

Deeplinking

The sidekick library should support deeplinking to a specific plugin or block.

Make italic text also editable

Hi,
The current selector for all elements that are recognized as editable (block-renderer.js#L81) lists the strong element but not em. I'm wondering if it is by design that if a table cell has only bold text, it should be editable in the side-kick library but with italic text not. Or the selector is simply missing? In that case let me know and I can open a PR.

cheers
Mehran

Authored block names are incorrect when rendered a second time

Expected Behaviour

When switching back to the blocks plugin from another plugin. The blocks plugin should continue to use the authored name in library-metadata if it was provided.

Actual Behaviour

Blacks are cached and library metadata is getting stripped before rendering. The cached version of the block is also getting stripped of it's library metadata which in turn stops the authored names from being used the second time it's rendered.

Template support

The block library should support page templates. This feature has been requested by a few customers who are currently using the sidekick library.

Template support should also include a way to specify page metadata.

Picture tag has source elements stripped off

Expected Behaviour

Picture tags have source tags under them, that are applied according to the official specifications.

Actual Behaviour

Picture tags are stripped of source tags, and thus the fallback img tag is used in all cases.

Reproduce Scenario (including but not limited to)

Use any block with a picture tag.

Steps to Reproduce

Platform and Version

Sample Code that illustrates the problem

Logs taken while reproducing problem

The automated release is failing 🚨

🚨 The automated release from the main branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can fix this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the main branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here are some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


No Discrod Webhook defined.

A Discord Webhook must be set in the DISCORD_WEBHOOK environment variable on your CI environment.

Please make sure you created a token and to sat it in the DISCORD_WEBHOOK environment variable on your CI environment.


Good luck with your project ✨

Your semantic-release bot 📦🚀

Extra sidekick-library style added to block during copy/paste

Expected Behaviour

The blocks should have only the styles explicitly added in the doc file

Actual Behaviour

An extra sidekick-library gets added to block style

Reproduce Scenario (including but not limited to)

Copy a block from the library and pasted into a document

Steps to Reproduce

  1. Go to https://main--boilerplate-with-library--dylandepass.hlx.live/tools/sidekick/library.html?plugin=blocks&path=/tools/sidekick/blocks/columns/columns&index=0
  2. Copy block and paste to doc

index.json images are not displayed correctly

e.g. https://main--24life--hlxsites.hlx.page/articles.json

has images with a path like /focus/2016/media_1086b7…at=pjpg&optimize=medium

and the extension does not show the image preview, because the wrong host is used. The image is loaded from chrome-extension://ccfggkjabjahcjoljmgmklhpaccedipo/fitness/2016/media_180d9d498b84b24b79db58bc929764b37524c07c7.jpeg?width=1200&format=pjpg&optimize=medium

Expected Behaviour

Shows image

Actual Behaviour

Screenshot 2023-08-23 at 14 58 58

Steps to Reproduce

Open https://main--24life--hlxsites.hlx.page/articles.json

Platform and Version

Chrome on Macos

Sample Code that illustrates the problem

Logs taken while reproducing problem

Add a way for sites to know if the page or block is being loaded by the library

Expected Behaviour

Developers need some way to know if the page or block is being loaded by the sidekick library.

Actual Behaviour

This can be done today by check for
window.location.href === 'about:srcdoc'

A better approach would be to decorate the main and block elements with a sidekick-library class so things can be done conditionally with css as well.

Disable Copy button in MetaData

Expected Behaviour

Disable the "copy" button based on metadata section, if the functionality is not applicable for that section.

Actual Behaviour

Currently the "copy" button is visible for some sections which aren't able to be copied. In those cases we are displaying a demo block or just examples for editors to reference. One of our examples would be displaying different types of headings in ?plugin=blocks&path=/blocks/heading

image

image

Reproduce Scenario (including but not limited to)

Adding any non-block examples, like inline headings or paragraph tags

Steps to Reproduce

n/a

Platform and Version

n/a

Sample Code that illustrates the problem

n/a

Logs taken while reproducing problem

n/a

Only call `decorateIcons` in devMode

When the sidekick library is running typically it is running on the same origin as the content and icons.

When in dev mode though (aka doing sidekick library development), the origin is different than the content so I was calling decorateIcons myself on the decorated content in order to change the loading location of the svg to be the same origin as the content (and not the window location). This has been fine up until now but a customer has an issue so I'm putting a check in front to only call decorateIcons when in dev mode.

Enhancement to add an introduction page in the library

We are looking for an enhancement to have a custom introduction page for the library.
This will be the default page that it will be loaded when the library loads up and the page can be used to describe how to use the library.

Support encoding images during copy operation

Problem

Customers using a Zero-Trust Network Access (ZTNA) service or similar authentication methods may encounter issues when copying and pasting blocks containing images from the sidekick library. Typically, images fail to load when the content is pasted in Word or Google Docs. This happens because the sidekick library, by default, uses the src attribute on image tags to link to resources secured behind the ZTNA (applicable to both .page and .live domains). To resolve this, images should be base64 encoded, replacing the direct resource link with the encoded data.

Solution

Allow customers to optionally tell the sidekick library to encode any images during a copy operation and embed the encoded data directly in the image tag.

This will be done using the plugins object of the sidekick config object.

const library = document.createElement('sidekick-library')
library.config = {
  base: '/tools/sidekick/library.json',
  plugins: {
    blocks: {
      encodeImages: true,
    }
  }
}

Compound blocks support

A customer requested the ability for the sidekick library to support compound blocks.

The idea is that there is an entry in the library that consists of multiple blocks and section metadata. The usecase is maybe they have a banner block that always needs to be followed by some gallery block. The want to have a single entry in the block library that can be copied and get both blocks and any optional section metadata to go with those blocks.

The proposed way to fix this is with library metadata and sections. The block document would contain a section with all the blocks that should be grouped (and any optional section metadata. Within that section should be library metadata with the key/value type : section. This will tell the block library to treat everything within this section as a single item.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.