# blökkli In-Page Editor

blökkli has a lot of configuration options. Please check the
[official documentation](https://docs.blokk.li/define-blokkli.html) 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.

```ts [.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](https://docs.blokk.li/define-blokkli/options.html) 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](https://docs.blokk.li/adapter/overview.html) to add custom
functionality:

```typescript
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:

```vue

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

```vue

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

```vue

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

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

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

```vue

<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.:

```twig
{{ 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:

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

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