blökkli In-Page Editor

blökkli has a lot of configuration options. Please check the official documentation for more information.

blökkli comes with an adapter supporting Drupal paragraphs out of the box.

Nuxt Configuration

The blökkli starterkit comes with a pre-configured blökkli installation. blökkli can be configured in the nuxt.config.ts file.

See the inline comments for more information on the configuration options.

nuxt.config.ts
export default defineNuxtConfig({
  blokkli: {
    // The path to the components blökkli should provide.
    pattern: ['components/Paragraph/**/*.{js,ts,vue}'],
    // Global options that are available for all components.
    // The 3 spacing options could be used on multiple components.
    globalOptions: {
      spacing: {
        type: 'radios',
        label: 'Spacing',
        default: 'none',
        displayAs: 'icons',
        options: {
          none: { label: 'no spacing', icon: 'icon-blokkli-option-no-spacing' },
          small: {
            label: 'small spacing',
            icon: 'icon-blokkli-option-small-spacing',
          },
          large: {
            label: 'large spacing',
            icon: 'icon-blokkli-option-large-spacing',
          },
        },
      },
    },
    // Translation for the edit indicator.
    translations: {
      en: {
        editIndicatorLabel: 'Edit paragraphs',
      },
      de: {
        editIndicatorLabel: 'Abschnitte bearbeiten',
      },
    },
    chunkNames: ['global', 'rare'],
    // The entity type that should be used for the items
    itemEntityType: 'paragraph',
    storageDefaults: {
      // Put some paragraph bundles into the favorites.
      blockFavorites: ['text'],
    },
    // Provide a schema for the optiopns that can be consumed by Drupal.
    // The paragraphs_blokkli module will use this schema file to
    // allow the editor to configure the components with paragraphs behaviours.
    schemaOptionsPath: IS_DEV
      ? '../drupal/docroot/modules/custom/blokkli_starterkit/data/schema.json'
      : undefined,
    defaultLanguage: 'de',
    // Make sure the editor is always rendered in German instead of the
    // current page language.
    forceDefaultLanguage: true,
  },
})

Defining component options

blökkli allows you to define custom options per component. These options are saved as behaviour settings on the paragraph entity.

Common options are:

  • Checkbox: This is the simplest option. It renders a single checkbox with the given label.
  • Checkboxes: This will render multiple checkboxes in a dropdown where zero or multiple options can be checked.
  • Text: This option renders a single text input.
  • Color: This option type renders a HTML color input.
  • Radios: This option renders a group of radio buttons.
  • Range: Renders a HTML range input to select a single numeric value
  • Number: Renders a HTML number input to enter an integer number.
  • Grouping: This option type allows you to group multiple options together.

Drupal Paragraphs integration

blökkli uses so called "adapters" to add support for various backends. The nuxt module ships with an adapter to integrate blökkli with the paragraphs_blokkli Drupal module.

The adapter is defined using the ./app/blokkli.editAdapter.ts file. To use the Drupal adapter, the file should contain this code:

import { defineBlokkliEditAdapter } from '#blokkli/adapter'
import drupalGraphlMiddlewareAdapter from '#blokkli/adapter/drupal/graphqlMiddleware'

export default defineBlokkliEditAdapter(drupalGraphlMiddlewareAdapter)

You can extend/override any adapter method to add custom functionality:

import { defineBlokkliEditAdapter } from '#blokkli/adapter'
import drupalGraphlMiddlewareAdapter from '#blokkli/adapter/drupal/graphqlMiddleware'

export default defineBlokkliEditAdapter(async (ctx) => {
  const adapter = await drupalGraphlMiddlewareAdapter(ctx)

  return {
    ...adapter,
    clipboardMapBundle: function (e) {
      // Custom logic to check if the pasted text can be used to create a paragraph.
      if (e.type === 'plaintext' && e.text.length > 1000) {
        // Text is too long, return.
        return null
      }

      // Call the default implementation.
      return adapter.clipboardMapBundle(e)
    },
  }
})

Editable fields

For text and string fields in Drupal, you can annotate a DOM element to be "editable" using the machine name of the field. For example:

<template>
  <div class="text-lg lg:text-xl">
    <h2 v-blokkli-editable:field_title v-html="text" />
  </div>
</template>

Assuming the paragraph has a field called field_title, by using the v-blokkli-editable directive, you can tell blökkli what field in Drupal this maps to.

Doing so allows editors to double click on the text and get an input field where they can directly edit the text.

blökkli automatically loads the field configuration to check for settings such as required or max length.

Droppable media field integration

Similar to editable fields, you can mark DOM elements as "droppable". Doing so allows editors to replace referenced entities such as media images.

Assuming we have a paragraph with a field called field_image that is an entity_reference field type, you could annotate it like this:

<template>
  <div>
    <div v-blokkli-droppable:field_image>
      <MediaImage v-bind="image" />
    </div>
  </div>
</template>

Now when the editor opens the Media Library in blökkli, they can replace the referenced media entity on that paragraph by just drag and dropping the new image onto the existing one.

blökkli is loading the field configuration at runtime to determine which bundles are allowed.

Currently only media entity reference fields are supported, but there are plans to add support for any entity references.

Editing entity fields

blökkli also allows you to edit fields on a host entity such as a node. The syntax is identical. For example, to make the title of a node editable within blökkli, we would do this:

<template>
  <BlokkliProvider v-slot="{ entity }" v-bind="blokkliProps" :entity="props">
    <h1 v-blokkli-editable:title>
      {{ entity?.title || title }}
    </h1>
  </BlokkliProvider>
</template>

There are a few things to note here:

When the title is edited, the new title is not stored on the node immediately - it's part of the edit state

Because we would normally display the "live" title of the node in the template, we need a way to get the "edited" title during editing. blökkli does this automatically as part of loading the edit state, but you have to tell blökkli what to load. The fragment used to load this is called pbMutatedEntity. blökkli expects this fragment to exist - if it doesn't, then none of the GraphQL queries and mutations will work.

It is recommended to not load the entire data for your node in this fragment, because it can significantly increase the request time for a query.

Let's say we only want to make the title editable on every node. Our fragment could look like this:

fragment pbMutatedEntity on Entity {
  ... on Node {
    title
  }
}

During editing the <BlokkliProvider> component passes this "edited" object as the entity slot value:

<template>
  <BlokkliProvider v-slot="{ entity }" v-bind="blokkliProps" :entity="props">
    <h1 v-blokkli-editable:title>
      <!-- Contains the edited title while editing. -->
      {{ entity?.title }}

      <!-- Always contains the title value of the "live" node. -->
      {{ title }}
    </h1>
  </BlokkliProvider>
</template>

<script lang="ts" setup>
import type { NodePageFragment } from '#graphql-operations'

const props = defineProps<{
  title?: string
  blokkliProps: NodePageFragment['blokkliProps']
}>()
</script>

The type of the entity object is inferred based on the type of :entity. We pass in the entire props of the component as the :entity prop.

For this reason, you will always need to write a fallback, e.g.:

{{ entity?.title || title }}

That way the title is always displayed, no matter if it has been edited or not.

A more complex example (as defined in the starterkit) would be this:

fragment pbMutatedEntity on Entity {
  ... on NodePage {
    title
    lead: fieldLead
    hero: fieldHeroImage {
      ...mediaImage
    }
  }

  ... on NodePressRelease {
    title
    lead: fieldLead
    image: fieldImage {
      ...mediaImage
    }
  }
}