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 .config/blokkli.ts file.

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

.config/blokkli.ts
import drupal from '@blokkli/editor/drupal'
import { defineNuxtConfigProperty } from '.'

export default defineNuxtConfigProperty<'blokkli'>((ctx) => {
  return {
    modules: [drupal()],
    globalOptions: {
      teaserStyle: {
        type: 'radios',
        label: 'Display',
        default: 'grid',
        options: {
          grid: 'Grid',
          staggered_grid: 'Staggered Grid',
        },
      },
      mobileStyle: {
        type: 'radios',
        label: 'Mobile display',
        default: 'stapel',
        options: {
          stack: 'Stack',
          slider: 'Slider',
        },
      },
      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',
          },
        },
      },
      imageFormat: {
        type: 'radios',
        label: 'Image Format',
        default: 'full',
        displayAs: 'icons',
        options: {
          full: { label: 'extra large', icon: 'icon-blokkli-option-xlarge' },
          big: { label: 'large', icon: 'icon-blokkli-option-large' },
          text: { label: 'medium', icon: 'icon-blokkli-option-medium' },
          small: { label: 'small', icon: 'icon-blokkli-option-small' },
        },
      },
    },
    translations: {
      en: {
        editIndicatorLabel: 'Edit paragraphs',
      },
      de: {
        editIndicatorLabel: 'Abschnitte bearbeiten',
      },
    },
    chunkNames: ['global', 'rare'],
    storageDefaults: {
      blockFavorites: ['text'],
    },
    schemaOptionsPath: ctx.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. The file is optional if the Drupal backend is used. You can extend/override any adapter method to add custom functionality:

import { defineBlokkliEditAdapter } from '#blokkli/adapter'
import drupalAdapter from '#blokkli/drupal/adapter'

export default defineBlokkliEditAdapter(async (ctx) => {
  const baseAdapter = await drupalAdapter(ctx)
  const runtimeConfig = useRuntimeConfig()

  return {
    ...baseAdapter,

    // Override specific methods from the base adapter.
    buildEditableFrameUrl(e) {
      const url = baseAdapter.buildEditableFrameUrl?.(e)
      if (!url) {
        throw new Error('Failed to build editable frame URL')
      }
      return `${runtimeConfig.public.drupalBackendUrl}${url}`
    },
    formFrameBuilder(e) {
      const result = baseAdapter.formFrameBuilder?.(e)
      if (!result || !result.url) {
        throw new Error('Failed to build form frame')
      }
      return {
        url: `${runtimeConfig.public.drupalBackendUrl}${result.url}`,
      }
    },
  }
})

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
    }
  }
}