@blokkli/editor 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "blokkli",
3
3
  "configKey": "blokkli",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "compatibility": {
6
6
  "nuxt": "^3.12.0"
7
7
  },
package/dist/module.mjs CHANGED
@@ -6,7 +6,7 @@ import { createUnplugin } from 'unplugin';
6
6
  import MagicString from 'magic-string';
7
7
  import { walk } from 'estree-walker-ts';
8
8
 
9
- const version = "1.0.1";
9
+ const version = "1.0.2";
10
10
 
11
11
  function sortObjectKeys(obj) {
12
12
  const sortedKeys = Object.keys(obj).sort();
@@ -1155,6 +1155,14 @@ const libraryError$2 = {
1155
1155
  source: "Failed to add block to library.",
1156
1156
  translation: "Das Element konnte nicht wiederverwendbar gemacht werden."
1157
1157
  };
1158
+ const libraryItemEditOverlayBack$2 = {
1159
+ source: "Back to page",
1160
+ translation: "Zurück zur Seite"
1161
+ };
1162
+ const libraryItemEditOverlayTitle$2 = {
1163
+ source: "Edit reusable block",
1164
+ translation: "Wiederverwendbaren Block bearbeiten"
1165
+ };
1158
1166
  const libraryPlaceBundleSelectLabel$2 = {
1159
1167
  source: "Bundle",
1160
1168
  translation: "Typ"
@@ -1692,6 +1700,8 @@ const de = {
1692
1700
  libraryDialogTitle: libraryDialogTitle$2,
1693
1701
  libraryDialogTitleInputPlaceholder: libraryDialogTitleInputPlaceholder$2,
1694
1702
  libraryError: libraryError$2,
1703
+ libraryItemEditOverlayBack: libraryItemEditOverlayBack$2,
1704
+ libraryItemEditOverlayTitle: libraryItemEditOverlayTitle$2,
1695
1705
  libraryPlaceBundleSelectLabel: libraryPlaceBundleSelectLabel$2,
1696
1706
  libraryPlaceDialogLead: libraryPlaceDialogLead$2,
1697
1707
  libraryPlaceDialogSubmit: libraryPlaceDialogSubmit$2,
@@ -2298,6 +2308,14 @@ const libraryError$1 = {
2298
2308
  source: "Failed to add block to library.",
2299
2309
  translation: "L'élément n’a pas pu être rendu réutilisable."
2300
2310
  };
2311
+ const libraryItemEditOverlayBack$1 = {
2312
+ source: "Back to page",
2313
+ translation: ""
2314
+ };
2315
+ const libraryItemEditOverlayTitle$1 = {
2316
+ source: "Edit reusable block",
2317
+ translation: ""
2318
+ };
2301
2319
  const libraryPlaceBundleSelectLabel$1 = {
2302
2320
  source: "Bundle",
2303
2321
  translation: ""
@@ -2835,6 +2853,8 @@ const fr = {
2835
2853
  libraryDialogTitle: libraryDialogTitle$1,
2836
2854
  libraryDialogTitleInputPlaceholder: libraryDialogTitleInputPlaceholder$1,
2837
2855
  libraryError: libraryError$1,
2856
+ libraryItemEditOverlayBack: libraryItemEditOverlayBack$1,
2857
+ libraryItemEditOverlayTitle: libraryItemEditOverlayTitle$1,
2838
2858
  libraryPlaceBundleSelectLabel: libraryPlaceBundleSelectLabel$1,
2839
2859
  libraryPlaceDialogLead: libraryPlaceDialogLead$1,
2840
2860
  libraryPlaceDialogSubmit: libraryPlaceDialogSubmit$1,
@@ -3441,6 +3461,14 @@ const libraryError = {
3441
3461
  source: "Failed to add block to library.",
3442
3462
  translation: "L'elemento non può essere reso riutilizzabile."
3443
3463
  };
3464
+ const libraryItemEditOverlayBack = {
3465
+ source: "Back to page",
3466
+ translation: ""
3467
+ };
3468
+ const libraryItemEditOverlayTitle = {
3469
+ source: "Edit reusable block",
3470
+ translation: ""
3471
+ };
3444
3472
  const libraryPlaceBundleSelectLabel = {
3445
3473
  source: "Bundle",
3446
3474
  translation: ""
@@ -3978,6 +4006,8 @@ const it = {
3978
4006
  libraryDialogTitle: libraryDialogTitle,
3979
4007
  libraryDialogTitleInputPlaceholder: libraryDialogTitleInputPlaceholder,
3980
4008
  libraryError: libraryError,
4009
+ libraryItemEditOverlayBack: libraryItemEditOverlayBack,
4010
+ libraryItemEditOverlayTitle: libraryItemEditOverlayTitle,
3981
4011
  libraryPlaceBundleSelectLabel: libraryPlaceBundleSelectLabel,
3982
4012
  libraryPlaceDialogLead: libraryPlaceDialogLead,
3983
4013
  libraryPlaceDialogSubmit: libraryPlaceDialogSubmit,
@@ -339,6 +339,13 @@ export default defineBlokkliEditAdapter(
339
339
  const url = typeof parts === "string" ? parts : "/" + parts.join("/");
340
340
  return { url: prefix + url + `?paragraphsBlokkli=true` };
341
341
  };
342
+ const getLibraryItemEditUrl = (uuid) => {
343
+ const url = buildFormUrl(
344
+ ["blokkli", "library-item", uuid],
345
+ ctx.value.language
346
+ ).url;
347
+ return `${url}&blokkliEditing=${uuid}&language=${ctx.value.language}`;
348
+ };
342
349
  const formFrameBuilder = (e) => {
343
350
  const entityType = ctx.value.entityType.toLowerCase();
344
351
  if (e.id === "block:add") {
@@ -637,7 +644,8 @@ export default defineBlokkliEditAdapter(
637
644
  addContentSearchItem,
638
645
  clipboardMapBundle,
639
646
  addBlockFromClipboardItem,
640
- changeLanguage
647
+ changeLanguage,
648
+ getLibraryItemEditUrl
641
649
  };
642
650
  }
643
651
  );
@@ -197,6 +197,10 @@ export interface BlokkliAdapter<T> {
197
197
  * Add a reusable item.
198
198
  */
199
199
  addLibraryItem?: (e: AddReusableItemEvent) => Promise<MutationResponseLike<T>>;
200
+ /**
201
+ * Build the URL to edit a library item.
202
+ */
203
+ getLibraryItemEditUrl?: (uuid: string) => string;
200
204
  /**
201
205
  * Delete multiple items.
202
206
  */
@@ -7,6 +7,7 @@
7
7
  :data-reusable-bundle="item.bundle"
8
8
  :data-reusable-uuid="item.uuid"
9
9
  :data-bk-library-label="libraryItem?.label"
10
+ :data-bk-library-item-uuid="libraryItem?.uuid"
10
11
  data-blokkli-is-reusable="true"
11
12
  :parent-type="parentType"
12
13
  />
@@ -23,6 +24,7 @@ import type { FieldListItem } from '#blokkli/types'
23
24
  interface LibraryItem {
24
25
  block?: FieldListItem
25
26
  label?: string
27
+ uuid?: string
26
28
  }
27
29
 
28
30
  const props = defineProps<{
@@ -31,9 +33,6 @@ const props = defineProps<{
31
33
 
32
34
  const { index, options, parentType } = defineBlokkli({
33
35
  bundle: 'from_library',
34
- editor: {
35
- disableEdit: true,
36
- },
37
36
  })
38
37
 
39
38
  // Reusable items inherit the options from this wrapper paragraph.
@@ -19,7 +19,12 @@
19
19
  </button>
20
20
  </div>
21
21
 
22
- <div class="bk-dialog-content">
22
+ <div
23
+ class="bk-dialog-content"
24
+ :class="{
25
+ 'bk-is-fullscreen': fullScreen,
26
+ }"
27
+ >
23
28
  <div class="bk-dialog-content-inner">
24
29
  <div v-if="lead" class="bk bk-dialog-lead">
25
30
  {{ lead }}
@@ -67,6 +72,7 @@ const props = withDefaults(
67
72
  isLoading?: boolean
68
73
  hideButtons?: boolean
69
74
  icon?: BlokkliIcon
75
+ fullScreen?: boolean
70
76
  }>(),
71
77
  {
72
78
  width: 600,
@@ -82,6 +88,13 @@ const style = computed(() => {
82
88
  return {}
83
89
  }
84
90
 
91
+ if (props.fullScreen) {
92
+ return {
93
+ maxWidth: '100vw',
94
+ height: '100vh',
95
+ }
96
+ }
97
+
85
98
  if (typeof props.width === 'number') {
86
99
  return {
87
100
  maxWidth: props.width + 'px',
@@ -241,10 +241,7 @@ function onPointerUp(e: PointerEvent) {
241
241
  if (!block) {
242
242
  return
243
243
  }
244
- eventBus.emit('item:edit', {
245
- uuid: lastInteractedElement.uuid,
246
- bundle: block.itemBundle,
247
- })
244
+ eventBus.emit('item:doubleClick', block)
248
245
  }
249
246
  }
250
247
  }
@@ -166,6 +166,7 @@ onMounted(() => {
166
166
  setRootClasses()
167
167
  baseLogger.log('EditProvider mounted')
168
168
  dom.init()
169
+ broadcast.emit('editorLoaded', { uuid: props.entityUuid })
169
170
  })
170
171
 
171
172
  onBeforeUnmount(() => {
@@ -2,7 +2,7 @@
2
2
  <PluginItemAction
3
3
  id="edit"
4
4
  :title="$t('edit', 'Edit')"
5
- :disabled="disabled"
5
+ :disabled="!canEdit"
6
6
  meta
7
7
  key-code="E"
8
8
  icon="edit"
@@ -16,6 +16,7 @@ import { computed, useBlokkli, defineBlokkliFeature } from '#imports'
16
16
  import type { DraggableExistingBlock } from '#blokkli/types'
17
17
  import { PluginItemAction } from '#blokkli/plugins'
18
18
  import { getDefinition } from '#blokkli/definitions'
19
+ import onBlokkliEvent from '#blokkli/helpers/composables/onBlokkliEvent'
19
20
 
20
21
  defineBlokkliFeature({
21
22
  id: 'edit',
@@ -25,23 +26,45 @@ defineBlokkliFeature({
25
26
  requiredAdapterMethods: ['formFrameBuilder'],
26
27
  })
27
28
 
28
- const { eventBus, selection, state, $t } = useBlokkli()
29
+ const { eventBus, selection, state, $t, adapter } = useBlokkli()
29
30
 
30
- const disabled = computed(() => {
31
- if (state.editMode.value !== 'editing') {
32
- return true
33
- }
31
+ const block = computed(() => {
34
32
  if (selection.blocks.value.length !== 1) {
35
- return true
33
+ return null
34
+ }
35
+
36
+ return selection.blocks.value[0]
37
+ })
38
+
39
+ const canEdit = computed(() => {
40
+ // Editing is only possible when a single block is selected.
41
+ if (!block.value) {
42
+ return false
36
43
  }
37
44
 
38
- const block = selection.blocks.value[0]
39
45
  const definition = getDefinition(
40
- block.itemBundle,
41
- block.hostFieldListType,
42
- block.parentBlockBundle,
46
+ block.value.itemBundle,
47
+ block.value.hostFieldListType,
48
+ block.value.parentBlockBundle,
43
49
  )
44
- return definition?.editor?.disableEdit === true
50
+
51
+ // Editing is explicitly disabled via the definition.
52
+ if (definition?.editor?.disableEdit) {
53
+ return false
54
+ }
55
+
56
+ // For reusable blocks, editing is only possible if the adapter implements
57
+ // the getLibraryItemEditUrl method.
58
+ if (block.value.libraryItemUuid) {
59
+ return (
60
+ !!adapter.getLibraryItemEditUrl &&
61
+ (state.editMode.value === 'editing' ||
62
+ state.editMode.value === 'translating') &&
63
+ !block.value.isNew
64
+ )
65
+ }
66
+
67
+ return state.editMode.value === 'editing'
45
68
  })
46
69
 
47
70
  function onClick(items: DraggableExistingBlock[]) {
@@ -49,11 +72,34 @@ function onClick(items: DraggableExistingBlock[]) {
49
72
  return
50
73
  }
51
74
 
75
+ if (!canEdit.value) {
76
+ return
77
+ }
78
+
79
+ const item = items[0]
80
+
81
+ // Because editing library items inside the current context is not (yet)
82
+ // supported, editing has to happen in a separate window where the host
83
+ // context is the library item entity.
84
+ if (item.libraryItemUuid && adapter.getLibraryItemEditUrl) {
85
+ const url = adapter.getLibraryItemEditUrl(item.libraryItemUuid)
86
+ eventBus.emit('library:edit-item', {
87
+ url,
88
+ label: item.editTitle,
89
+ uuid: item.libraryItemUuid,
90
+ })
91
+ return
92
+ }
93
+
52
94
  eventBus.emit('item:edit', {
53
- uuid: items[0].uuid,
54
- bundle: items[0].itemBundle,
95
+ uuid: item.uuid,
96
+ bundle: item.itemBundle,
55
97
  })
56
98
  }
99
+
100
+ onBlokkliEvent('item:doubleClick', function (block) {
101
+ onClick([block])
102
+ })
57
103
  </script>
58
104
 
59
105
  <script lang="ts">
@@ -20,12 +20,13 @@ defineBlokkliFeature({
20
20
  description: 'Provides a menu button to exit the editor without saving.',
21
21
  })
22
22
 
23
- const { $t } = useBlokkli()
23
+ const { $t, broadcast, context } = useBlokkli()
24
24
 
25
25
  const route = useRoute()
26
26
 
27
27
  function onClick() {
28
28
  nextTick(() => {
29
+ broadcast.emit('closeEditor', { uuid: context.value.entityUuid })
29
30
  window.location.href = route.path
30
31
  })
31
32
  }
@@ -0,0 +1,206 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <Loading v-if="isLoading" />
4
+ <Transition name="bk-library-edit-header">
5
+ <header v-show="isLoaded" class="bk bk-library-edit-overlay-header">
6
+ <h2>
7
+ <span>{{
8
+ $t('libraryItemEditOverlayTitle', 'Edit reusable block')
9
+ }}</span>
10
+ <span v-if="label">&nbsp;{{ label }}</span>
11
+ </h2>
12
+ <button @click.prevent="closeOverlay">
13
+ <Icon name="arrow-left" />
14
+ <span>{{ $t('libraryItemEditOverlayBack', 'Back to page') }}</span>
15
+ </button>
16
+ </header>
17
+ </Transition>
18
+ <Transition
19
+ :css="false"
20
+ @enter="onEnter"
21
+ @after-enter="onAfter"
22
+ @enter-cancelled="onAfter"
23
+ @leave="onLeave"
24
+ @after-leave="onAfterLeave"
25
+ @leave-cancelled="onAfterLeave"
26
+ >
27
+ <div v-show="isLoaded" class="bk bk-library-edit-overlay">
28
+ <iframe
29
+ ref="iframe"
30
+ :src="url"
31
+ style="width: 100%; height: 100%"
32
+ @load="onLoad"
33
+ />
34
+ </div>
35
+ </Transition>
36
+ </Teleport>
37
+ </template>
38
+
39
+ <script lang="ts" setup>
40
+ import onBroadcastEvent from '#blokkli/helpers/composables/onBroadcastEvent'
41
+ import { ref, useBlokkli } from '#imports'
42
+ import { Icon } from '#blokkli/components'
43
+ import Loading from './../../../Loading/index.vue'
44
+
45
+ const props = defineProps<{
46
+ url: string
47
+ uuid: string
48
+ label?: string
49
+ }>()
50
+
51
+ const DURATION = 530
52
+ const emit = defineEmits(['submit', 'close'])
53
+
54
+ function getOriginatingElement(): HTMLElement | null {
55
+ const el = document.querySelector(
56
+ `[data-bk-library-item-uuid="${props.uuid}"]`,
57
+ )
58
+ if (el instanceof HTMLElement) {
59
+ return el
60
+ }
61
+
62
+ return null
63
+ }
64
+
65
+ // called one frame after the element is inserted.
66
+ // use this to start the entering animation.
67
+ function onEnter(el: Element, done: () => void) {
68
+ if (el instanceof HTMLElement) {
69
+ const originating = getOriginatingElement()
70
+ if (!originating) {
71
+ done()
72
+ isLoading.value = false
73
+ return
74
+ }
75
+
76
+ const originatingRect = originating.getBoundingClientRect()
77
+ const overlayRect = el.getBoundingClientRect()
78
+
79
+ const offsetX =
80
+ originatingRect.x - overlayRect.x + originatingRect.width / 2
81
+ const offsetY =
82
+ originatingRect.y - overlayRect.y + originatingRect.height / 2
83
+
84
+ el.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(0, 0)`
85
+
86
+ setTimeout(() => {
87
+ el.style.transitionDuration = DURATION + 'ms'
88
+ el.style.transitionTimingFunction = 'cubic-bezier(0.56, 0.04, 0.25, 1)'
89
+ el.style.transitionProperty = 'transform'
90
+ el.style.transformOrigin = '0px 0px'
91
+ el.style.transform = 'translate(0px, 0px)'
92
+ }, 10)
93
+
94
+ setTimeout(() => {
95
+ done()
96
+ isLoading.value = false
97
+ }, DURATION)
98
+ }
99
+ }
100
+
101
+ // called when the enter transition has finished.
102
+ function onAfter(el: Element) {
103
+ if (el instanceof HTMLElement) {
104
+ el.style.transform = ''
105
+ el.style.transitionDuration = ''
106
+ el.style.opacity = ''
107
+ el.style.transitionProperty = ''
108
+ el.style.transitionTimingFunction = ''
109
+ el.style.transformOrigin = ''
110
+ }
111
+ }
112
+
113
+ // called when the leave transition starts.
114
+ // use this to start the leaving animation.
115
+ function onLeave(el: Element, done: () => void) {
116
+ if (el instanceof HTMLElement) {
117
+ const originating = getOriginatingElement()
118
+ if (!originating) {
119
+ done()
120
+ return
121
+ }
122
+
123
+ const originatingRect = originating.getBoundingClientRect()
124
+ const overlayRect = el.getBoundingClientRect()
125
+
126
+ const offsetX =
127
+ originatingRect.x - overlayRect.x + originatingRect.width / 2
128
+ const offsetY =
129
+ originatingRect.y - overlayRect.y + originatingRect.height / 2
130
+
131
+ el.style.transform = 'translate(0px, 0px)'
132
+
133
+ setTimeout(() => {
134
+ el.style.transitionDuration = DURATION + 'ms'
135
+ el.style.transitionTimingFunction = 'cubic-bezier(0.56, 0.04, 0.25, 1)'
136
+ el.style.transitionProperty = 'transform'
137
+ el.style.transformOrigin = '0px 0px'
138
+ el.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(0, 0)`
139
+ }, 10)
140
+
141
+ setTimeout(() => {
142
+ done()
143
+ }, DURATION)
144
+ }
145
+ }
146
+
147
+ function onAfterLeave(el: Element) {
148
+ onAfter(el)
149
+ if (hasPublished.value) {
150
+ emit('submit')
151
+ } else {
152
+ emit('close')
153
+ }
154
+ }
155
+
156
+ const { $t } = useBlokkli()
157
+
158
+ const iframe = ref<HTMLIFrameElement | null>(null)
159
+ const isLoaded = ref(false)
160
+ const isLoading = ref(true)
161
+ const hasPublished = ref(false)
162
+ let timeout: any = null
163
+
164
+ function onLibraryItemPublished({ uuid }: { uuid: string }) {
165
+ if (props.uuid === uuid) {
166
+ hasPublished.value = true
167
+ isLoaded.value = false
168
+ }
169
+ }
170
+
171
+ function onLoad() {
172
+ clearTimeout(timeout)
173
+
174
+ timeout = window.setTimeout(() => {
175
+ isLoaded.value = true
176
+ }, 3000)
177
+ if (!iframe.value) {
178
+ return
179
+ }
180
+
181
+ iframe.value.focus()
182
+
183
+ iframe.value.contentWindow?.focus()
184
+ }
185
+
186
+ function closeOverlay() {
187
+ hasPublished.value = false
188
+ isLoaded.value = false
189
+ }
190
+
191
+ function onLibraryItemClose({ uuid }: { uuid: string }) {
192
+ if (props.uuid === uuid) {
193
+ closeOverlay()
194
+ }
195
+ }
196
+
197
+ function onEditorLoaded({ uuid }: { uuid: string }) {
198
+ if (props.uuid === uuid) {
199
+ isLoaded.value = true
200
+ }
201
+ }
202
+
203
+ onBroadcastEvent('published', onLibraryItemPublished)
204
+ onBroadcastEvent('closeEditor', onLibraryItemClose)
205
+ onBroadcastEvent('editorLoaded', onEditorLoaded)
206
+ </script>
@@ -50,7 +50,7 @@
50
50
  </Teleport>
51
51
 
52
52
  <Teleport to="body">
53
- <transition name="bk-slide-in" :duration="200">
53
+ <transition name="bk-slide-up" :duration="200">
54
54
  <LibraryDialog
55
55
  v-if="placedAction && adapter.getLibraryItems"
56
56
  :field="placedAction.field"
@@ -59,6 +59,12 @@
59
59
  />
60
60
  </transition>
61
61
  </Teleport>
62
+ <EditReusable
63
+ v-if="editingLibraryItem"
64
+ v-bind="editingLibraryItem"
65
+ @submit="onSubmitLibraryItem"
66
+ @close="cancelLibraryItemEdit"
67
+ />
62
68
  </template>
63
69
 
64
70
  <script lang="ts" setup>
@@ -66,8 +72,10 @@ import { ref, computed, useBlokkli, defineBlokkliFeature } from '#imports'
66
72
  import { PluginItemAction, PluginAddAction } from '#blokkli/plugins'
67
73
  import ReusableDialog from './ReusableDialog/index.vue'
68
74
  import LibraryDialog from './LibraryDialog/index.vue'
75
+ import EditReusable from './EditReusable/index.vue'
69
76
  import { getDefinition } from '#blokkli/definitions'
70
- import type { ActionPlacedEvent } from '#blokkli/types'
77
+ import type { ActionPlacedEvent, LibraryEditItemEvent } from '#blokkli/types'
78
+ import onBlokkliEvent from '#blokkli/helpers/composables/onBlokkliEvent'
71
79
 
72
80
  const { adapter } = defineBlokkliFeature({
73
81
  id: 'library',
@@ -175,6 +183,21 @@ const canMakeReusable = computed(
175
183
  itemBundle?.value?.allowReusable &&
176
184
  fromLibraryAllowedInList.value,
177
185
  )
186
+
187
+ const editingLibraryItem = ref<LibraryEditItemEvent | null>(null)
188
+
189
+ onBlokkliEvent('library:edit-item', function (e) {
190
+ editingLibraryItem.value = e
191
+ })
192
+
193
+ function cancelLibraryItemEdit() {
194
+ editingLibraryItem.value = null
195
+ }
196
+
197
+ function onSubmitLibraryItem() {
198
+ eventBus.emit('reloadState')
199
+ cancelLibraryItemEdit()
200
+ }
178
201
  </script>
179
202
 
180
203
  <script lang="ts">
@@ -24,7 +24,7 @@ const { adapter } = defineBlokkliFeature({
24
24
  'Provides a menu button to publish the changes of the current entity.',
25
25
  })
26
26
 
27
- const { state, $t, eventBus } = useBlokkli()
27
+ const { state, $t, eventBus, broadcast, context } = useBlokkli()
28
28
  const { mutations, canEdit, mutateWithLoadingState } = state
29
29
 
30
30
  const onClick = async () => {
@@ -39,7 +39,10 @@ const onClick = async () => {
39
39
  if (validations.length) {
40
40
  eventBus.emit('publish:failed')
41
41
  }
42
+ return
42
43
  }
44
+
45
+ broadcast.emit('published', { uuid: context.value.entityUuid })
43
46
  }
44
47
  </script>
45
48
 
@@ -72,6 +72,7 @@
72
72
  <PluginItemAction
73
73
  v-if="editMode === 'translating'"
74
74
  id="translate"
75
+ :disabled="!canTranslateBlock"
75
76
  :title="$t('translationsItemAction', 'Translate')"
76
77
  icon="translate"
77
78
  @click="onTranslate"
@@ -98,6 +99,7 @@ import type {
98
99
  Language,
99
100
  } from '#blokkli/types'
100
101
  import Banner from './Banner/index.vue'
102
+ import { getDefinition } from '#blokkli/definitions'
101
103
 
102
104
  const { adapter } = defineBlokkliFeature({
103
105
  id: 'translations',
@@ -107,7 +109,7 @@ const { adapter } = defineBlokkliFeature({
107
109
  description: 'Adds support for block translations.',
108
110
  })
109
111
 
110
- const { eventBus, state, context, $t, ui } = useBlokkli()
112
+ const { eventBus, state, context, $t, ui, selection } = useBlokkli()
111
113
  const { translation, editMode } = state
112
114
 
113
115
  const isOpen = ref(false)
@@ -152,6 +154,29 @@ const items = computed<TranslationStateItem[]>(() => {
152
154
  .filter(falsy)
153
155
  })
154
156
 
157
+ const canTranslateBlock = computed(() => {
158
+ if (selection.blocks.value.length !== 1) {
159
+ return false
160
+ }
161
+ const block = selection.blocks.value[0]
162
+
163
+ if (block.libraryItemUuid) {
164
+ return false
165
+ }
166
+
167
+ const definition = getDefinition(
168
+ block.itemBundle,
169
+ block.hostFieldListType,
170
+ block.parentBlockBundle,
171
+ )
172
+
173
+ if (definition?.editor?.disableEdit) {
174
+ return false
175
+ }
176
+
177
+ return true
178
+ })
179
+
155
180
  function onClick(item: TranslationStateItem, event: Event) {
156
181
  if (item.translation?.exists) {
157
182
  return adapter.changeLanguage(item.translation)