@blokkli/editor 2.0.0-alpha.46 → 2.0.0-alpha.47
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 +1 -1
- package/dist/module.mjs +2 -1
- package/dist/modules/agent/runtime/app/helpers/validation.d.ts +13 -0
- package/dist/modules/agent/runtime/app/helpers/validation.js +22 -0
- package/dist/modules/agent/runtime/app/tools/add_content_search_paragraph/index.js +12 -0
- package/dist/modules/agent/runtime/app/tools/add_fragment/index.js +12 -1
- package/dist/modules/agent/runtime/app/tools/add_media_paragraph/index.js +12 -0
- package/dist/modules/agent/runtime/app/tools/add_paragraphs/index.js +10 -0
- package/dist/modules/agent/runtime/app/tools/add_reusable_paragraph/index.js +12 -0
- package/dist/modules/agent/runtime/app/tools/add_template/index.js +5 -0
- package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/index.js +15 -0
- package/dist/modules/agent/runtime/app/tools/delete_paragraphs/index.js +12 -0
- package/dist/modules/agent/runtime/app/tools/detach_reusable_paragraph/index.js +12 -0
- package/dist/modules/agent/runtime/app/tools/duplicate_paragraphs/index.js +16 -1
- package/dist/modules/agent/runtime/app/tools/move_paragraphs/index.js +17 -0
- package/dist/modules/agent/runtime/app/tools/rearrange_paragraphs/index.js +11 -0
- package/dist/modules/agent/runtime/app/tools/replace_content_search_item/index.js +8 -0
- package/dist/modules/agent/runtime/app/tools/replace_media_field/index.js +10 -0
- package/dist/modules/agent/runtime/app/tools/set_paragraph_options/index.js +10 -0
- package/dist/modules/agent/runtime/app/tools/swap_paragraphs/index.js +15 -0
- package/dist/modules/agent/runtime/app/tools/update_text_fields/index.js +21 -1
- package/dist/modules/agent/runtime/app/types/index.d.ts +6 -6
- package/dist/modules/drupal/index.mjs +2 -1
- package/dist/modules/drupal/runtime/adapter/index.js +15 -3
- package/dist/runtime/editor/components/Actions/index.vue +47 -2
- package/dist/runtime/editor/components/AnimationCanvas/index.vue +6 -3
- package/dist/runtime/editor/components/BundleSelector/index.d.vue.ts +8 -4
- package/dist/runtime/editor/components/BundleSelector/index.vue +111 -13
- package/dist/runtime/editor/components/BundleSelector/index.vue.d.ts +8 -4
- package/dist/runtime/editor/components/EditProvider.vue +2 -2
- package/dist/runtime/editor/components/FlexTextarea/index.vue +8 -1
- package/dist/runtime/editor/css/output.css +1 -1
- package/dist/runtime/editor/features/add-list/Blocks/index.vue +6 -3
- package/dist/runtime/editor/features/analyze/Renderer/index.vue +1 -1
- package/dist/runtime/editor/features/block-scheduler/index.vue +7 -1
- package/dist/runtime/editor/features/changelog/Dialog/index.vue +1 -1
- package/dist/runtime/editor/features/changelog/changelog.json +18 -10
- package/dist/runtime/editor/features/clipboard/index.vue +6 -1
- package/dist/runtime/editor/features/comments/AddForm/index.d.vue.ts +2 -2
- package/dist/runtime/editor/features/comments/AddForm/index.vue.d.ts +2 -2
- package/dist/runtime/editor/features/delete/index.vue +17 -2
- package/dist/runtime/editor/features/dragging-overlay/Renderer/index.vue +12 -2
- package/dist/runtime/editor/features/dragging-overlay/index.vue +5 -2
- package/dist/runtime/editor/features/duplicate/index.vue +23 -7
- package/dist/runtime/editor/features/edit/index.vue +29 -8
- package/dist/runtime/editor/features/editable-field/index.vue +15 -1
- package/dist/runtime/editor/features/fragments/index.vue +5 -2
- package/dist/runtime/editor/features/hover/Renderer/index.vue +19 -6
- package/dist/runtime/editor/features/hover/Renderer/vertex.glsl +5 -2
- package/dist/runtime/editor/features/library/index.vue +52 -8
- package/dist/runtime/editor/features/media-library/index.vue +7 -2
- package/dist/runtime/editor/features/multi-select/Renderer/index.vue +4 -1
- package/dist/runtime/editor/features/search/index.vue +7 -2
- package/dist/runtime/editor/features/selection/AddButtons/Renderer/index.vue +1 -1
- package/dist/runtime/editor/features/selection/AddButtons/index.vue +26 -2
- package/dist/runtime/editor/features/selection/Renderer/index.vue +23 -5
- package/dist/runtime/editor/features/selection/Renderer/vertex.glsl +5 -2
- package/dist/runtime/editor/features/selection/index.vue +17 -5
- package/dist/runtime/editor/features/translations/index.vue +17 -11
- package/dist/runtime/editor/helpers/dropTargets/index.d.ts +1 -1
- package/dist/runtime/editor/helpers/dropTargets/index.js +2 -2
- package/dist/runtime/editor/plugins/ItemAction/index.d.vue.ts +4 -1
- package/dist/runtime/editor/plugins/ItemAction/index.vue +9 -3
- package/dist/runtime/editor/plugins/ItemAction/index.vue.d.ts +4 -1
- package/dist/runtime/editor/providers/permissions.d.ts +22 -1
- package/dist/runtime/editor/providers/permissions.js +99 -3
- package/dist/runtime/editor/providers/selection.d.ts +2 -1
- package/dist/runtime/editor/providers/selection.js +10 -5
- package/dist/runtime/editor/translations/de.json +89 -1
- package/dist/runtime/editor/translations/fr.json +89 -1
- package/dist/runtime/editor/translations/gsw_CH.json +89 -1
- package/dist/runtime/editor/translations/it.json +89 -1
- package/dist/runtime/editor/types/definitions.d.ts +2 -0
- package/package.json +1 -1
|
@@ -31,6 +31,7 @@ uniform vec3 u_color_accent;
|
|
|
31
31
|
uniform vec3 u_color_teal;
|
|
32
32
|
uniform vec3 u_color_white;
|
|
33
33
|
uniform vec3 u_color_lime;
|
|
34
|
+
uniform vec3 u_color_yellow;
|
|
34
35
|
|
|
35
36
|
// The transformed quad for the fragment shader.
|
|
36
37
|
out vec4 v_quad;
|
|
@@ -140,8 +141,10 @@ void main() {
|
|
|
140
141
|
v_dash_cycle = 14.0 - u_scale * 1.0;
|
|
141
142
|
v_rect_size_artboard = vec2(hoverPos.z, hoverPos.w);
|
|
142
143
|
|
|
143
|
-
// Select color based on type: 0 = mono, 1 = accent, 2 = teal, 3 = white (inverted), 4 = lime (library)
|
|
144
|
-
if (hoverType >
|
|
144
|
+
// Select color based on type: 0 = mono, 1 = accent, 2 = teal, 3 = white (inverted), 4 = lime (library), 5 = yellow (restricted)
|
|
145
|
+
if (hoverType > 4.5) {
|
|
146
|
+
v_color = u_color_yellow;
|
|
147
|
+
} else if (hoverType > 3.5) {
|
|
145
148
|
v_color = u_color_lime;
|
|
146
149
|
} else if (hoverType > 2.5) {
|
|
147
150
|
v_color = u_color_white;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
v-if="isReusable"
|
|
4
4
|
id="library_detach"
|
|
5
5
|
:title="$t('libraryDetach', 'Detach from library')"
|
|
6
|
+
:disabled="detachDisabledReason"
|
|
6
7
|
icon="reusable-detach"
|
|
7
8
|
edit-only
|
|
8
9
|
multiple
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
v-else-if="!isReusable"
|
|
14
15
|
id="library_make_reusable"
|
|
15
16
|
:title="$t('libraryAdd', 'Add to library...')"
|
|
16
|
-
:disabled="
|
|
17
|
+
:disabled="makeReusableDisabledReason"
|
|
17
18
|
edit-only
|
|
18
19
|
icon="reusable"
|
|
19
20
|
:weight="-70"
|
|
@@ -82,6 +83,12 @@ const showReusableDialog = useDialog("library-reusable", "center");
|
|
|
82
83
|
const userCanCreateLibraryItem = computed(
|
|
83
84
|
() => permissions.hasPermission("create_library_item")
|
|
84
85
|
);
|
|
86
|
+
const canAddFromLibrary = computed(
|
|
87
|
+
() => permissions.checkBlockBundlePermission(fromLibraryBlockBundle, "add")
|
|
88
|
+
);
|
|
89
|
+
const canEditFromLibrary = computed(
|
|
90
|
+
() => permissions.checkBlockBundlePermission(fromLibraryBlockBundle, "edit")
|
|
91
|
+
);
|
|
85
92
|
async function selectNewlyAdded(cb) {
|
|
86
93
|
const uuidsBefore = state.getAllUuids();
|
|
87
94
|
await cb();
|
|
@@ -93,7 +100,7 @@ async function selectNewlyAdded(cb) {
|
|
|
93
100
|
eventBus.emit("select", newUuid);
|
|
94
101
|
}
|
|
95
102
|
const onDetach = async () => {
|
|
96
|
-
if (!adapter.detachReusableBlock || !selection.uuids.value.length) {
|
|
103
|
+
if (!adapter.detachReusableBlock || !selection.uuids.value.length || detachDisabledReason.value) {
|
|
97
104
|
return;
|
|
98
105
|
}
|
|
99
106
|
await selectNewlyAdded(
|
|
@@ -106,7 +113,7 @@ const onDetach = async () => {
|
|
|
106
113
|
};
|
|
107
114
|
const placedAction = ref(null);
|
|
108
115
|
const onAddLibraryItem = async (uuid) => {
|
|
109
|
-
if (!placedAction.value || !adapter.addLibraryItem) {
|
|
116
|
+
if (!placedAction.value || !adapter.addLibraryItem || !canAddFromLibrary.value) {
|
|
110
117
|
return;
|
|
111
118
|
}
|
|
112
119
|
await state.mutateWithLoadingState(
|
|
@@ -166,9 +173,46 @@ const fromLibraryAllowedInList = computed(() => {
|
|
|
166
173
|
}
|
|
167
174
|
return types.allowedTypesInList.value.includes(fromLibraryBlockBundle);
|
|
168
175
|
});
|
|
169
|
-
const
|
|
170
|
-
(
|
|
171
|
-
|
|
176
|
+
const detachDisabledReason = computed(() => {
|
|
177
|
+
if (!canEditFromLibrary.value) {
|
|
178
|
+
return $t(
|
|
179
|
+
"libraryDetachNoPermission",
|
|
180
|
+
"You do not have permission to detach this block."
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
});
|
|
185
|
+
const makeReusableDisabledReason = computed(() => {
|
|
186
|
+
if (isReusable.value) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (!userCanCreateLibraryItem.value) {
|
|
190
|
+
return $t(
|
|
191
|
+
"libraryAddNoPermission",
|
|
192
|
+
"You do not have permission to create library items."
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const item = selection.item.value;
|
|
196
|
+
if (item && !permissions.checkBlockBundlePermission(item.bundle, "edit")) {
|
|
197
|
+
return $t(
|
|
198
|
+
"libraryAddNoEditPermission",
|
|
199
|
+
"You do not have permission to edit this block."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (!itemBundle?.value?.allowReusable) {
|
|
203
|
+
return $t(
|
|
204
|
+
"libraryAddNotSupported",
|
|
205
|
+
"This block type cannot be made reusable."
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (!fromLibraryAllowedInList.value) {
|
|
209
|
+
return $t(
|
|
210
|
+
"libraryAddNotAllowedInField",
|
|
211
|
+
"Reusable blocks are not allowed in this field."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
});
|
|
172
216
|
const editingLibraryItem = ref(null);
|
|
173
217
|
onBlokkliEvent("library:edit-item", function(e) {
|
|
174
218
|
editingLibraryItem.value = {
|
|
@@ -186,7 +230,7 @@ function onSubmitLibraryItem() {
|
|
|
186
230
|
}
|
|
187
231
|
defineDropHandler("reusable", {
|
|
188
232
|
async execute({ items, host, afterUuid }) {
|
|
189
|
-
if (adapter.addLibraryItem) {
|
|
233
|
+
if (adapter.addLibraryItem && canAddFromLibrary.value) {
|
|
190
234
|
await state.mutateWithLoadingState(
|
|
191
235
|
() => adapter.addLibraryItem({
|
|
192
236
|
libraryItemUuid: items[0].libraryItemUuid,
|
|
@@ -198,7 +242,7 @@ defineDropHandler("reusable", {
|
|
|
198
242
|
}
|
|
199
243
|
});
|
|
200
244
|
defineAddAction(() => {
|
|
201
|
-
if (!adapter.addLibraryItem || !adapter.getLibraryItems || !isSupportedOnEntity.value) {
|
|
245
|
+
if (!adapter.addLibraryItem || !adapter.getLibraryItems || !isSupportedOnEntity.value || !canAddFromLibrary.value) {
|
|
202
246
|
return;
|
|
203
247
|
}
|
|
204
248
|
return {
|
|
@@ -30,7 +30,7 @@ defineBlokkliFeature({
|
|
|
30
30
|
description: "Implements a media library to easily drag and drop media like images or videos.",
|
|
31
31
|
requiredAdapterMethods: ["mediaLibraryGetResults", "mediaLibraryAddBlock"]
|
|
32
32
|
});
|
|
33
|
-
const { $t, adapter, state, types, directive } = useBlokkli();
|
|
33
|
+
const { $t, adapter, state, types, directive, permissions } = useBlokkli();
|
|
34
34
|
const ERROR_MESSAGE = $t(
|
|
35
35
|
"mediaLibraryReplaceFailed",
|
|
36
36
|
"Failed to replace media."
|
|
@@ -55,6 +55,9 @@ defineDropAreas((dragItems) => {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
const isBlock = field.type === itemEntityType;
|
|
58
|
+
if (isBlock && (!permissions.checkBlockBundlePermission(field.bundle, "edit") || permissions.blockHasRestrictedAncestor(field.uuid))) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
58
61
|
const draggableHost = {
|
|
59
62
|
uuid: field.uuid,
|
|
60
63
|
type: field.type,
|
|
@@ -106,7 +109,9 @@ defineDropHandler("media_library", {
|
|
|
106
109
|
return [];
|
|
107
110
|
}
|
|
108
111
|
const item = items[0];
|
|
109
|
-
return field.allowedBundles.filter(
|
|
112
|
+
return field.allowedBundles.filter(
|
|
113
|
+
(b) => item.itemBundles.includes(b) && permissions.checkBlockBundlePermission(b, "add")
|
|
114
|
+
);
|
|
110
115
|
},
|
|
111
116
|
async execute({ items, host, afterUuid, bundle }) {
|
|
112
117
|
if (adapter.mediaLibraryAddBlock && items.length === 1) {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "twgl.js";
|
|
16
16
|
import { RectangleBufferCollector } from "#blokkli/editor/helpers/webgl";
|
|
17
17
|
import { defineRenderer, useDebugLogger } from "#blokkli/editor/composables";
|
|
18
|
-
const { eventBus, dom, theme, animation, ui, blocks } = useBlokkli();
|
|
18
|
+
const { eventBus, dom, theme, animation, ui, blocks, permissions } = useBlokkli();
|
|
19
19
|
const logger = useDebugLogger();
|
|
20
20
|
const props = defineProps({
|
|
21
21
|
startX: { type: Number, required: true },
|
|
@@ -43,6 +43,9 @@ class MultiSelectRectangleBufferCollector extends RectangleBufferCollector {
|
|
|
43
43
|
if (!block) {
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
|
+
if (permissions.blockHasRestrictedAncestor(uuid)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
46
49
|
const el = dom.getDragElement(block);
|
|
47
50
|
if (!el) {
|
|
48
51
|
continue;
|
|
@@ -58,7 +58,7 @@ defineBlokkliFeature({
|
|
|
58
58
|
label: "Search",
|
|
59
59
|
description: "Provides an overlay with shortcut to search for blocks on the current page or existing content to add as blocks."
|
|
60
60
|
});
|
|
61
|
-
const { $t, selection, ui, adapter, state, types, directive } = useBlokkli();
|
|
61
|
+
const { $t, selection, ui, adapter, state, types, directive, permissions } = useBlokkli();
|
|
62
62
|
const ERROR_MESSAGE = $t(
|
|
63
63
|
"searchContentReplaceFailed",
|
|
64
64
|
"Failed to replace content."
|
|
@@ -79,6 +79,9 @@ defineDropAreas((dragItems) => {
|
|
|
79
79
|
if (field.type !== itemEntityType) {
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
|
+
if (!permissions.checkBlockBundlePermission(field.bundle, "edit") || permissions.blockHasRestrictedAncestor(field.uuid)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
82
85
|
const config = types.getDroppableFieldConfig(field.fieldName, field);
|
|
83
86
|
const allowedBundles = config.allowed.find(
|
|
84
87
|
(v) => v.type === searchItem.entityType
|
|
@@ -115,7 +118,9 @@ defineDropAreas((dragItems) => {
|
|
|
115
118
|
defineDropHandler("search_content", {
|
|
116
119
|
resolveBundles({ items, field }) {
|
|
117
120
|
const item = items[0];
|
|
118
|
-
return field.allowedBundles.filter(
|
|
121
|
+
return field.allowedBundles.filter(
|
|
122
|
+
(b) => item.itemBundles.includes(b) && permissions.checkBlockBundlePermission(b, "add")
|
|
123
|
+
);
|
|
119
124
|
},
|
|
120
125
|
async execute({ items, host, afterUuid, bundle }) {
|
|
121
126
|
if (!adapter.addContentSearchItem) {
|
|
@@ -52,7 +52,7 @@ const currentUuid = ref("");
|
|
|
52
52
|
const currentBundleLabel = ref("");
|
|
53
53
|
const currentSingleAllowedBundleLabel = ref(null);
|
|
54
54
|
const tooltipData = computed(() => {
|
|
55
|
-
if (hoveredCircle.value < 0) {
|
|
55
|
+
if (hoveredCircle.value < 0 || ui.openTooltip.value) {
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
const index = hoveredCircle.value;
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
:anchor-el="addData.anchorEl"
|
|
9
9
|
:anchor-coordinates="addData.anchorCoordinates"
|
|
10
10
|
:label="addData.label"
|
|
11
|
+
:field="addData.field"
|
|
11
12
|
@select="onSelectBundle"
|
|
12
13
|
@close="closeOverlay"
|
|
13
14
|
@action="onSelectAction"
|
|
15
|
+
@fragment="onSelectFragment"
|
|
14
16
|
/>
|
|
15
17
|
</BlokkliTransition>
|
|
16
18
|
</Teleport>
|
|
@@ -59,7 +61,9 @@ const {
|
|
|
59
61
|
fields,
|
|
60
62
|
animation,
|
|
61
63
|
context,
|
|
62
|
-
selection
|
|
64
|
+
selection,
|
|
65
|
+
permissions,
|
|
66
|
+
adapter
|
|
63
67
|
} = useBlokkli();
|
|
64
68
|
const isLocked = ref(false);
|
|
65
69
|
const shouldRender = computed(() => {
|
|
@@ -258,6 +262,21 @@ function onSelectAction(action) {
|
|
|
258
262
|
});
|
|
259
263
|
closeOverlay();
|
|
260
264
|
}
|
|
265
|
+
async function onSelectFragment(name) {
|
|
266
|
+
const fragmentsAddBlock = adapter.fragmentsAddBlock;
|
|
267
|
+
if (!addData.value || !fragmentsAddBlock) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const { host, preceedingUuid } = addData.value;
|
|
271
|
+
closeOverlay();
|
|
272
|
+
await state.mutateWithLoadingState(
|
|
273
|
+
() => fragmentsAddBlock({
|
|
274
|
+
name,
|
|
275
|
+
host: { ...host },
|
|
276
|
+
preceedingUuid
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
}
|
|
261
280
|
const cache = /* @__PURE__ */ new Map();
|
|
262
281
|
function clearAllCache() {
|
|
263
282
|
cache.clear();
|
|
@@ -349,7 +368,12 @@ onBlokkliEvent("state:reloaded", () => {
|
|
|
349
368
|
}
|
|
350
369
|
});
|
|
351
370
|
function setAddData(key, field, label, preceedingUuid, anchorEl, anchorCoordinates) {
|
|
352
|
-
|
|
371
|
+
if (field.hostEntityType === itemEntityType && (!permissions.checkBlockBundlePermission(field.hostEntityBundle, "edit") || permissions.blockHasRestrictedAncestor(field.hostEntityUuid))) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const allowedBundles = field.allowedBundles.filter(
|
|
375
|
+
(v) => permissions.checkBlockBundlePermission(v, "add")
|
|
376
|
+
);
|
|
353
377
|
if (allowedBundles.length === 0) {
|
|
354
378
|
return;
|
|
355
379
|
}
|
|
@@ -22,7 +22,7 @@ const props = defineProps({
|
|
|
22
22
|
blocks: { type: Array, required: true },
|
|
23
23
|
hasHostSelected: { type: Boolean, required: true }
|
|
24
24
|
});
|
|
25
|
-
const { animation, theme, dom, ui, state } = useBlokkli();
|
|
25
|
+
const { animation, theme, dom, ui, state, permissions } = useBlokkli();
|
|
26
26
|
class SelectionRectangleBufferCollector extends RectangleBufferCollector {
|
|
27
27
|
uuids = [];
|
|
28
28
|
lastCount = 0;
|
|
@@ -50,7 +50,8 @@ class SelectionRectangleBufferCollector extends RectangleBufferCollector {
|
|
|
50
50
|
y: 0,
|
|
51
51
|
radius: [0, 0, 0, 0],
|
|
52
52
|
isInverted: false,
|
|
53
|
-
isFromLibrary: false
|
|
53
|
+
isFromLibrary: false,
|
|
54
|
+
isRestricted: false
|
|
54
55
|
},
|
|
55
56
|
3
|
|
56
57
|
// Type 3 = host selection
|
|
@@ -70,8 +71,11 @@ class SelectionRectangleBufferCollector extends RectangleBufferCollector {
|
|
|
70
71
|
}
|
|
71
72
|
const style = ui.lowPerformanceMode.value ? null : theme.getDraggableStyle(el);
|
|
72
73
|
const isFromLibrary = state.fromLibraryUuids.value.includes(block.uuid);
|
|
74
|
+
const isRestricted = !permissions.checkBlockBundlePermission(block.bundle, "edit") || !permissions.checkBlockBundlePermission(block.bundle, "delete") || !permissions.checkBlockBundlePermission(block.bundle, "add");
|
|
73
75
|
let type = 0;
|
|
74
|
-
if (
|
|
76
|
+
if (isRestricted) {
|
|
77
|
+
type = 4;
|
|
78
|
+
} else if (isFromLibrary) {
|
|
75
79
|
type = 2;
|
|
76
80
|
} else if (style?.isInverted) {
|
|
77
81
|
type = 1;
|
|
@@ -85,7 +89,8 @@ class SelectionRectangleBufferCollector extends RectangleBufferCollector {
|
|
|
85
89
|
y: rect.y,
|
|
86
90
|
radius: style?.radius ?? [0, 0, 0, 0],
|
|
87
91
|
isInverted: !!style?.isInverted,
|
|
88
|
-
isFromLibrary
|
|
92
|
+
isFromLibrary,
|
|
93
|
+
isRestricted
|
|
89
94
|
},
|
|
90
95
|
type
|
|
91
96
|
);
|
|
@@ -141,6 +146,15 @@ const getColorLibrary = useTransitionedValue(() => {
|
|
|
141
146
|
}
|
|
142
147
|
return theme.lime.value.normal;
|
|
143
148
|
});
|
|
149
|
+
const getColorRestricted = useTransitionedValue(() => {
|
|
150
|
+
if (selectionColorOverride.value) {
|
|
151
|
+
return selectionColorOverride.value;
|
|
152
|
+
}
|
|
153
|
+
if (hasTransformingStyle.value) {
|
|
154
|
+
return theme.orange.value.normal;
|
|
155
|
+
}
|
|
156
|
+
return theme.yellow.value.normal;
|
|
157
|
+
});
|
|
144
158
|
const getColorHost = useTransitionedValue(() => {
|
|
145
159
|
return theme.mono.value[700];
|
|
146
160
|
});
|
|
@@ -161,6 +175,7 @@ const { collector } = defineRenderer("selection-overlay", {
|
|
|
161
175
|
u_color_default: toShaderColor(getColorDefault()),
|
|
162
176
|
u_color_inverted: toShaderColor(getColorInverted()),
|
|
163
177
|
u_color_library: toShaderColor(getColorLibrary()),
|
|
178
|
+
u_color_restricted: toShaderColor(getColorRestricted()),
|
|
164
179
|
u_color_host: toShaderColor(getColorHost()),
|
|
165
180
|
u_artboard_size: [
|
|
166
181
|
ui.artboardSize.value.width,
|
|
@@ -187,6 +202,7 @@ const { collector } = defineRenderer("selection-overlay", {
|
|
|
187
202
|
const colorDefault = rgbaToCss(getColorDefault());
|
|
188
203
|
const colorInverted = rgbaToCss(getColorInverted());
|
|
189
204
|
const colorLibrary = rgbaToCss(getColorLibrary());
|
|
205
|
+
const colorRestricted = rgbaToCss(getColorRestricted());
|
|
190
206
|
const colorHost = rgbaToCss(getColorHost());
|
|
191
207
|
const smoothstepValue = Math.max(
|
|
192
208
|
0,
|
|
@@ -196,7 +212,9 @@ const { collector } = defineRenderer("selection-overlay", {
|
|
|
196
212
|
for (let i = 0; i < rects.length; i++) {
|
|
197
213
|
const rect = rects[i];
|
|
198
214
|
let strokeColor = colorDefault;
|
|
199
|
-
if (rect.
|
|
215
|
+
if (rect.isRestricted) {
|
|
216
|
+
strokeColor = colorRestricted;
|
|
217
|
+
} else if (rect.isFromLibrary) {
|
|
200
218
|
strokeColor = colorLibrary;
|
|
201
219
|
} else if (rect.isInverted) {
|
|
202
220
|
strokeColor = colorInverted;
|
|
@@ -21,6 +21,7 @@ uniform vec2 u_resolution;
|
|
|
21
21
|
uniform vec3 u_color_default;
|
|
22
22
|
uniform vec3 u_color_inverted;
|
|
23
23
|
uniform vec3 u_color_library;
|
|
24
|
+
uniform vec3 u_color_restricted;
|
|
24
25
|
uniform vec3 u_color_host;
|
|
25
26
|
|
|
26
27
|
// The transformed quad for the fragment shader.
|
|
@@ -86,9 +87,11 @@ void main() {
|
|
|
86
87
|
|
|
87
88
|
v_rect_width = adjusted_quad.x;
|
|
88
89
|
|
|
89
|
-
// Set color based on type: 0=default, 1=inverted, 2=library, 3=host
|
|
90
|
+
// Set color based on type: 0=default, 1=inverted, 2=library, 3=host, 4=restricted
|
|
90
91
|
v_color = u_color_default;
|
|
91
|
-
if (a_rect_type >
|
|
92
|
+
if (a_rect_type > 3.5) {
|
|
93
|
+
v_color = u_color_restricted;
|
|
94
|
+
} else if (a_rect_type > 2.5) {
|
|
92
95
|
v_color = u_color_host;
|
|
93
96
|
} else if (a_rect_type > 1.5) {
|
|
94
97
|
v_color = u_color_library;
|
|
@@ -52,7 +52,8 @@ const {
|
|
|
52
52
|
types,
|
|
53
53
|
state,
|
|
54
54
|
blocks,
|
|
55
|
-
element
|
|
55
|
+
element,
|
|
56
|
+
permissions
|
|
56
57
|
} = useBlokkli();
|
|
57
58
|
const originatesFromTextInput = (e) => e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement;
|
|
58
59
|
const getSelectionOrder = useStateBasedCache(
|
|
@@ -223,11 +224,22 @@ function selectInList(prev) {
|
|
|
223
224
|
}
|
|
224
225
|
const selectionOrder = getSelectionOrder();
|
|
225
226
|
const currentIndex = selectionOrder.indexOf(currentUuid);
|
|
227
|
+
if (currentIndex === -1) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
226
230
|
const delta = prev ? -1 : 1;
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
+
const length = selectionOrder.length;
|
|
232
|
+
for (let step = 1; step < length; step++) {
|
|
233
|
+
const newIndex = modulo(currentIndex + delta * step, length);
|
|
234
|
+
const candidate = selectionOrder[newIndex];
|
|
235
|
+
if (!candidate) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const resolved = permissions.getRestrictedAncestor(candidate) ?? candidate;
|
|
239
|
+
if (resolved !== currentUuid) {
|
|
240
|
+
selectBlock(candidate);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
231
243
|
}
|
|
232
244
|
}
|
|
233
245
|
onBlokkliEvent("keyPressed", (e) => {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<PluginItemAction
|
|
56
56
|
v-if="isTranslating"
|
|
57
57
|
id="translate"
|
|
58
|
-
:disabled="
|
|
58
|
+
:disabled="translateDisabledReason"
|
|
59
59
|
:title="$t('translationsItemAction', 'Translate')"
|
|
60
60
|
icon="bk_mdi_translate"
|
|
61
61
|
:weight="-100"
|
|
@@ -117,13 +117,16 @@ const items = computed(() => {
|
|
|
117
117
|
return null;
|
|
118
118
|
}).filter(falsy);
|
|
119
119
|
});
|
|
120
|
-
const
|
|
120
|
+
const translateDisabledReason = computed(() => {
|
|
121
121
|
const block = selection.item.value;
|
|
122
122
|
if (!block) {
|
|
123
123
|
return false;
|
|
124
124
|
}
|
|
125
125
|
if (block.library?.libraryItemUuid) {
|
|
126
|
-
return
|
|
126
|
+
return $t(
|
|
127
|
+
"translateLibraryBlock",
|
|
128
|
+
"Reusable blocks cannot be translated here."
|
|
129
|
+
);
|
|
127
130
|
}
|
|
128
131
|
const definition = definitions.getBlockDefinition(
|
|
129
132
|
block.bundle,
|
|
@@ -131,16 +134,19 @@ const canTranslateBlock = computed(() => {
|
|
|
131
134
|
block.parentBlockBundle
|
|
132
135
|
);
|
|
133
136
|
if (definition?.editor?.disableEdit) {
|
|
134
|
-
return
|
|
137
|
+
return $t(
|
|
138
|
+
"translateEditDisabled",
|
|
139
|
+
"Editing is disabled for this block type."
|
|
140
|
+
);
|
|
135
141
|
}
|
|
136
142
|
const type = types.getBlockBundleDefinition(block.bundle);
|
|
137
|
-
if (!type) {
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
if (!type || !type.isTranslatable) {
|
|
144
|
+
return $t(
|
|
145
|
+
"translateNotTranslatable",
|
|
146
|
+
"This block type is not translatable."
|
|
147
|
+
);
|
|
142
148
|
}
|
|
143
|
-
return
|
|
149
|
+
return false;
|
|
144
150
|
});
|
|
145
151
|
function onClick(item, event) {
|
|
146
152
|
if (item.translation?.exists) {
|
|
@@ -161,7 +167,7 @@ function onTranslate(items2) {
|
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
onBlokkliEvent("item:doubleClick", function(block) {
|
|
164
|
-
if (isTranslating.value &&
|
|
170
|
+
if (isTranslating.value && !translateDisabledReason.value) {
|
|
165
171
|
onTranslate([block]);
|
|
166
172
|
}
|
|
167
173
|
});
|
|
@@ -3,4 +3,4 @@ export type Orientation = 'horizontal' | 'vertical';
|
|
|
3
3
|
export declare const MIN_GAP = 20;
|
|
4
4
|
export declare function getGapSize(orientation: Orientation, element: HTMLElement): number;
|
|
5
5
|
export declare function getChildrenOrientation(element: HTMLElement): Orientation;
|
|
6
|
-
export declare function determineCanAddChildren(field: BlokkliFieldElement, children: HTMLElement[], uuids: string[], currentCount: number, itemsToAdd: number, draggingBundles?: string[], draggingFragments?: string[]): boolean;
|
|
6
|
+
export declare function determineCanAddChildren(field: BlokkliFieldElement, children: HTMLElement[], uuids: string[], currentCount: number, itemsToAdd: number, draggingBundles?: string[], draggingFragments?: string[], isBundlePermitted?: (bundle: string) => boolean): boolean;
|
|
@@ -31,7 +31,7 @@ export function getChildrenOrientation(element) {
|
|
|
31
31
|
}
|
|
32
32
|
return "vertical";
|
|
33
33
|
}
|
|
34
|
-
export function determineCanAddChildren(field, children, uuids, currentCount, itemsToAdd, draggingBundles, draggingFragments) {
|
|
34
|
+
export function determineCanAddChildren(field, children, uuids, currentCount, itemsToAdd, draggingBundles, draggingFragments, isBundlePermitted) {
|
|
35
35
|
if (field.cardinality !== -1) {
|
|
36
36
|
const childrenThatAreSelection = children.filter((child) => {
|
|
37
37
|
const uuid = child.dataset.bkUuid;
|
|
@@ -50,7 +50,7 @@ export function determineCanAddChildren(field, children, uuids, currentCount, it
|
|
|
50
50
|
}
|
|
51
51
|
if (!uuids.length) {
|
|
52
52
|
return draggingBundles.some(
|
|
53
|
-
(bundle) => field.allowedBundles.includes(bundle)
|
|
53
|
+
(bundle) => field.allowedBundles.includes(bundle) && (!isBundlePermitted || isBundlePermitted(bundle))
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
const bundlesAllowed = draggingBundles.every(
|
|
@@ -12,8 +12,11 @@ type __VLS_Props = {
|
|
|
12
12
|
title: string;
|
|
13
13
|
/**
|
|
14
14
|
* Whether the action is disabled.
|
|
15
|
+
*
|
|
16
|
+
* When a string is provided, the button is disabled and the string is
|
|
17
|
+
* displayed as the tooltip text explaining why.
|
|
15
18
|
*/
|
|
16
|
-
disabled?: boolean;
|
|
19
|
+
disabled?: boolean | string;
|
|
17
20
|
/**
|
|
18
21
|
* Whether the button should be displayed in an active state.
|
|
19
22
|
*
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
group="blocks"
|
|
26
26
|
@pressed="onClick"
|
|
27
27
|
/>
|
|
28
|
+
<div v-if="disabledReason" class="bk-item-action-disabled-reason">
|
|
29
|
+
<span>{{ disabledReason }}</span>
|
|
30
|
+
</div>
|
|
28
31
|
</div>
|
|
29
32
|
</button>
|
|
30
33
|
</Teleport>
|
|
@@ -41,7 +44,7 @@ const uuids = computed(() => selection.uuids.value);
|
|
|
41
44
|
const props = defineProps({
|
|
42
45
|
id: { type: String, required: true },
|
|
43
46
|
title: { type: String, required: true },
|
|
44
|
-
disabled: { type: Boolean, required: false },
|
|
47
|
+
disabled: { type: [Boolean, String], required: false },
|
|
45
48
|
active: { type: Boolean, required: false },
|
|
46
49
|
keyCode: { type: String, required: false },
|
|
47
50
|
meta: { type: Boolean, required: false },
|
|
@@ -52,7 +55,10 @@ const props = defineProps({
|
|
|
52
55
|
tourText: { type: String, required: false }
|
|
53
56
|
});
|
|
54
57
|
const isDisabled = computed(
|
|
55
|
-
() => props.disabled || !props.multiple && selection.items.value.length > 1
|
|
58
|
+
() => !!props.disabled || !props.multiple && selection.items.value.length > 1
|
|
59
|
+
);
|
|
60
|
+
const disabledReason = computed(
|
|
61
|
+
() => typeof props.disabled === "string" ? props.disabled : null
|
|
56
62
|
);
|
|
57
63
|
const shouldRender = computed(() => {
|
|
58
64
|
if (props.editOnly) {
|
|
@@ -72,7 +78,7 @@ defineCommands(() => ({
|
|
|
72
78
|
group: "selection",
|
|
73
79
|
label: props.title,
|
|
74
80
|
icon: props.icon,
|
|
75
|
-
disabled:
|
|
81
|
+
disabled: isDisabled.value || !selection.items.value.length,
|
|
76
82
|
callback: onClick
|
|
77
83
|
}));
|
|
78
84
|
defineTourItem(() => {
|
|
@@ -12,8 +12,11 @@ type __VLS_Props = {
|
|
|
12
12
|
title: string;
|
|
13
13
|
/**
|
|
14
14
|
* Whether the action is disabled.
|
|
15
|
+
*
|
|
16
|
+
* When a string is provided, the button is disabled and the string is
|
|
17
|
+
* displayed as the tooltip text explaining why.
|
|
15
18
|
*/
|
|
16
|
-
disabled?: boolean;
|
|
19
|
+
disabled?: boolean | string;
|
|
17
20
|
/**
|
|
18
21
|
* Whether the button should be displayed in an active state.
|
|
19
22
|
*
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import type { BlokkliAdapter } from '#blokkli/editor/adapter';
|
|
2
|
+
import type { BlockPermission } from '../types/definitions.js';
|
|
2
3
|
import type { UserPermissions } from '../types/permissions.js';
|
|
4
|
+
import type { BlocksProvider } from './blocks.js';
|
|
3
5
|
export type PermissionsProvider = {
|
|
4
6
|
hasPermission: (permission: UserPermissions) => boolean;
|
|
7
|
+
checkBlockBundlePermission: (bundle: string, operation: BlockPermission) => boolean;
|
|
8
|
+
filterDeniedBundles: (bundles: string[], operation: BlockPermission) => string[];
|
|
9
|
+
/**
|
|
10
|
+
* Check if a block has any ancestor whose bundle lacks 'edit' permission.
|
|
11
|
+
*
|
|
12
|
+
* When an ancestor is restricted, all descendants are considered restricted
|
|
13
|
+
* too — the user cannot add, edit, or delete blocks inside a restricted
|
|
14
|
+
* parent.
|
|
15
|
+
*/
|
|
16
|
+
blockHasRestrictedAncestor: (uuid: string) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Get the nearest ancestor block that restricts editing.
|
|
19
|
+
*
|
|
20
|
+
* Walks up the parent chain and returns the UUID of the first ancestor
|
|
21
|
+
* whose bundle lacks 'edit' permission, or undefined if no ancestor is
|
|
22
|
+
* restricted.
|
|
23
|
+
*/
|
|
24
|
+
getRestrictedAncestor: (uuid: string) => string | undefined;
|
|
25
|
+
getBlockBundlePermissions: (bundle: string) => BlockPermission[];
|
|
5
26
|
};
|
|
6
|
-
export default function (adapter: BlokkliAdapter<any
|
|
27
|
+
export default function (adapter: BlokkliAdapter<any>, blocks: BlocksProvider): Promise<PermissionsProvider>;
|