@blokkli/editor 2.0.0-alpha.61 → 2.0.0-alpha.63

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.
Files changed (128) hide show
  1. package/dist/global/types/colorOptions.d.ts +16 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +168 -14
  4. package/dist/modules/agent/runtime/app/components/Conversation/Item/Assistant/index.vue +14 -5
  5. package/dist/modules/agent/runtime/app/features/agent/ConversationsAdmin/ConversationsTab/Item.vue +2 -2
  6. package/dist/modules/agent/runtime/app/features/agent/ConversationsAdmin/RatingsTab/Item.vue +2 -2
  7. package/dist/modules/agent/runtime/app/features/agent/ConversationsAdmin/SplitView/ConversationDetail.vue +2 -2
  8. package/dist/modules/agent/runtime/app/features/agent/Transcript/MessageContent.vue +14 -1
  9. package/dist/modules/agent/runtime/app/features/agent/Transcript/index.vue +30 -12
  10. package/dist/modules/agent/runtime/app/helpers/linkifyBlockUuids.d.ts +11 -0
  11. package/dist/modules/agent/runtime/app/helpers/linkifyBlockUuids.js +47 -0
  12. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/Component.d.vue.ts +22 -0
  13. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/Component.vue +28 -26
  14. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/Component.vue.d.ts +22 -0
  15. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/index.d.ts +15 -0
  16. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/Component.d.vue.ts +22 -0
  17. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/Component.vue +59 -16
  18. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/Component.vue.d.ts +22 -0
  19. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/index.d.ts +15 -0
  20. package/dist/modules/agent/runtime/app/tools/fieldDiffApproval.d.ts +85 -15
  21. package/dist/modules/agent/runtime/app/tools/fieldDiffApproval.js +138 -28
  22. package/dist/modules/agent/runtime/app/tools/schemas.d.ts +15 -5
  23. package/dist/modules/agent/runtime/app/tools/schemas.js +29 -12
  24. package/dist/modules/agent/runtime/app/tools/search_text/index.js +1 -1
  25. package/dist/modules/agent/runtime/app/tools/update_text_fields/Component.d.vue.ts +22 -0
  26. package/dist/modules/agent/runtime/app/tools/update_text_fields/Component.vue +54 -30
  27. package/dist/modules/agent/runtime/app/tools/update_text_fields/Component.vue.d.ts +22 -0
  28. package/dist/modules/agent/runtime/app/tools/update_text_fields/index.d.ts +15 -0
  29. package/dist/modules/agent/runtime/server/default-system-prompts/architecture.js +1 -1
  30. package/dist/modules/agent/runtime/server/default-system-prompts/important-rules.js +1 -0
  31. package/dist/modules/charts/runtime/blokkli/tools/chart_schemas.d.ts +1 -1
  32. package/dist/modules/charts/runtime/blokkli/tools/chart_schemas.js +12 -5
  33. package/dist/modules/charts/runtime/components/ChartRenderer/index.vue +7 -16
  34. package/dist/modules/charts/runtime/features/charts/Editor/ColorDropdown/index.vue +7 -77
  35. package/dist/modules/charts/runtime/features/charts/Editor/CsvImport/csvHelpers.d.ts +1 -1
  36. package/dist/modules/charts/runtime/features/charts/Editor/useChartEditorState.d.ts +1 -1
  37. package/dist/modules/charts/runtime/helpers/index.d.ts +2 -1
  38. package/dist/modules/charts/runtime/helpers/index.js +4 -2
  39. package/dist/modules/drupal/graphql/base/fragment.paragraphsBlokkliMutationResult.graphql +3 -0
  40. package/dist/modules/drupal/runtime/adapter/index.js +38 -27
  41. package/dist/runtime/composables/useBlokkliRuntimeConfig.d.ts +50 -0
  42. package/dist/runtime/composables/useBlokkliRuntimeConfig.js +34 -0
  43. package/dist/runtime/editor/adapter/index.d.ts +2 -1
  44. package/dist/runtime/editor/components/ColorDropdown/index.d.vue.ts +26 -0
  45. package/dist/runtime/editor/components/ColorDropdown/index.vue +116 -0
  46. package/dist/runtime/editor/components/ColorDropdown/index.vue.d.ts +26 -0
  47. package/dist/runtime/editor/components/DiffApproval/Highlight/Item.d.vue.ts +14 -20
  48. package/dist/runtime/editor/components/DiffApproval/Highlight/Item.vue +97 -52
  49. package/dist/runtime/editor/components/DiffApproval/Highlight/Item.vue.d.ts +14 -20
  50. package/dist/runtime/editor/components/DiffApproval/Highlight/index.d.vue.ts +6 -6
  51. package/dist/runtime/editor/components/DiffApproval/Highlight/index.vue +30 -21
  52. package/dist/runtime/editor/components/DiffApproval/Highlight/index.vue.d.ts +6 -6
  53. package/dist/runtime/editor/components/DiffApproval/Toolbar/index.d.vue.ts +19 -9
  54. package/dist/runtime/editor/components/DiffApproval/Toolbar/index.vue +18 -9
  55. package/dist/runtime/editor/components/DiffApproval/Toolbar/index.vue.d.ts +19 -9
  56. package/dist/runtime/editor/components/DiffApproval/index.d.vue.ts +14 -6
  57. package/dist/runtime/editor/components/DiffApproval/index.vue +62 -35
  58. package/dist/runtime/editor/components/DiffApproval/index.vue.d.ts +14 -6
  59. package/dist/runtime/editor/components/DiffApproval/types.d.ts +32 -0
  60. package/dist/runtime/editor/components/DiffApproval/types.js +22 -0
  61. package/dist/runtime/editor/components/Dropdown/index.d.vue.ts +5 -4
  62. package/dist/runtime/editor/components/Dropdown/index.vue +89 -29
  63. package/dist/runtime/editor/components/Dropdown/index.vue.d.ts +5 -4
  64. package/dist/runtime/editor/components/FlexTextarea/index.d.vue.ts +1 -1
  65. package/dist/runtime/editor/components/FlexTextarea/index.vue.d.ts +1 -1
  66. package/dist/runtime/editor/components/NestedEditorOverlay/index.d.vue.ts +2 -2
  67. package/dist/runtime/editor/components/NestedEditorOverlay/index.vue +3 -3
  68. package/dist/runtime/editor/components/NestedEditorOverlay/index.vue.d.ts +2 -2
  69. package/dist/runtime/editor/components/PopupHost/index.d.vue.ts +13 -0
  70. package/dist/runtime/editor/components/PopupHost/index.vue +12 -0
  71. package/dist/runtime/editor/components/PopupHost/index.vue.d.ts +13 -0
  72. package/dist/runtime/editor/components/index.d.ts +2 -0
  73. package/dist/runtime/editor/components/index.js +2 -0
  74. package/dist/runtime/editor/css/output.css +1 -1
  75. package/dist/runtime/editor/features/analyze/Main.vue +15 -2
  76. package/dist/runtime/editor/features/analyze/Results/ResultsItemNodes.vue +4 -1
  77. package/dist/runtime/editor/features/analyze/Results/ResultsItemNodesTarget.vue +25 -16
  78. package/dist/runtime/editor/features/analyze/analyzers/defaults/validations.d.ts +10 -0
  79. package/dist/runtime/editor/features/analyze/analyzers/defaults/validations.js +39 -0
  80. package/dist/runtime/editor/features/analyze/analyzers/helpers/Context.d.ts +5 -1
  81. package/dist/runtime/editor/features/analyze/analyzers/helpers/Context.js +5 -0
  82. package/dist/runtime/editor/features/analyze/index.vue +0 -1
  83. package/dist/runtime/editor/features/changelog/changelog.json +9 -1
  84. package/dist/runtime/editor/features/comments/Comment/Meta/index.vue +1 -1
  85. package/dist/runtime/editor/features/comments/Comment/index.vue +1 -1
  86. package/dist/runtime/editor/features/publish/Dialog/Violations.d.vue.ts +12 -0
  87. package/dist/runtime/editor/features/publish/Dialog/Violations.vue +117 -0
  88. package/dist/runtime/editor/features/publish/Dialog/Violations.vue.d.ts +12 -0
  89. package/dist/runtime/editor/features/publish/Dialog/index.vue +61 -24
  90. package/dist/runtime/editor/features/publish/index.vue +2 -4
  91. package/dist/runtime/editor/features/publish/types.d.ts +0 -4
  92. package/dist/runtime/editor/helpers/color/index.d.ts +7 -0
  93. package/dist/runtime/editor/helpers/color/index.js +14 -0
  94. package/dist/runtime/editor/helpers/diff/index.d.ts +87 -0
  95. package/dist/runtime/editor/helpers/diff/index.js +256 -0
  96. package/dist/runtime/editor/helpers/injections.d.ts +11 -0
  97. package/dist/runtime/editor/helpers/injections.js +1 -0
  98. package/dist/runtime/editor/plugins/Sidebar/Detached/index.d.vue.ts +1 -1
  99. package/dist/runtime/editor/plugins/Sidebar/Detached/index.vue.d.ts +1 -1
  100. package/dist/runtime/editor/plugins/Sidebar/index.d.vue.ts +2 -2
  101. package/dist/runtime/editor/plugins/Sidebar/index.vue.d.ts +2 -2
  102. package/dist/runtime/editor/providers/analyze.js +5 -2
  103. package/dist/runtime/editor/providers/config.d.ts +3 -1
  104. package/dist/runtime/editor/providers/config.js +46 -15
  105. package/dist/runtime/editor/providers/state.d.ts +13 -0
  106. package/dist/runtime/editor/providers/state.js +7 -0
  107. package/dist/runtime/editor/translations/de.json +7 -4
  108. package/dist/runtime/editor/translations/fr.json +2 -4
  109. package/dist/runtime/editor/translations/gsw_CH.json +7 -4
  110. package/dist/runtime/editor/translations/it.json +2 -4
  111. package/dist/runtime/helpers/colors.d.ts +39 -0
  112. package/dist/runtime/helpers/colors.js +28 -0
  113. package/dist/runtime/types/colors.d.ts +11 -0
  114. package/package.json +1 -1
  115. package/dist/runtime/editor/features/validations/Overlay/Item.d.vue.ts +0 -7
  116. package/dist/runtime/editor/features/validations/Overlay/Item.vue +0 -36
  117. package/dist/runtime/editor/features/validations/Overlay/Item.vue.d.ts +0 -7
  118. package/dist/runtime/editor/features/validations/Overlay/index.d.vue.ts +0 -7
  119. package/dist/runtime/editor/features/validations/Overlay/index.vue +0 -115
  120. package/dist/runtime/editor/features/validations/Overlay/index.vue.d.ts +0 -7
  121. package/dist/runtime/editor/features/validations/SidebarItem/index.d.vue.ts +0 -10
  122. package/dist/runtime/editor/features/validations/SidebarItem/index.vue +0 -41
  123. package/dist/runtime/editor/features/validations/SidebarItem/index.vue.d.ts +0 -10
  124. package/dist/runtime/editor/features/validations/index.d.vue.ts +0 -3
  125. package/dist/runtime/editor/features/validations/index.vue +0 -91
  126. package/dist/runtime/editor/features/validations/index.vue.d.ts +0 -3
  127. package/dist/runtime/editor/types/config.d.ts +0 -5
  128. /package/dist/runtime/{editor/types/config.js → types/colors.js} +0 -0
@@ -22,7 +22,7 @@
22
22
  </p>
23
23
  </FormItem>
24
24
 
25
- <FormItem class="_bk_relative">
25
+ <FormItem class="_bk_relative _bk_z-50">
26
26
  <FormToggle
27
27
  v-model="keepVisible"
28
28
  :label="$t('analyzeKeepVisible', 'Keep results visible')"
@@ -122,6 +122,18 @@ const { getCategoryLabel } = useAnalyzeHelper();
122
122
  const refreshKey = computed(() => {
123
123
  return `dom:${dom.settleKey.value}_directive:${directive.settleKey.value}_state:${state.refreshKey.value}`;
124
124
  });
125
+ watch(
126
+ () => props.isShown,
127
+ (isShown) => {
128
+ if (isShown) return;
129
+ const activeId2 = ui.activeHighlightId.value;
130
+ if (!activeId2) return;
131
+ const resultId = activeId2.split("_____")[0];
132
+ if (allResults.value.some((r) => r.id === resultId)) {
133
+ ui.activeHighlightId.value = "";
134
+ }
135
+ }
136
+ );
125
137
  const isRunning = defineModel({ type: Boolean, ...{ default: false } });
126
138
  const issueCount = defineModel("issueCount", { type: Number, ...{ default: 0 } });
127
139
  const hasViolation = defineModel("hasViolation", { type: Boolean, ...{ default: false } });
@@ -272,7 +284,8 @@ function filterResultsByIgnored(items, wantIgnored) {
272
284
  const filtered = [];
273
285
  for (const result of items) {
274
286
  const nodes = result.nodes.filter((n) => n.ignored === wantIgnored);
275
- if (nodes.length) {
287
+ const isStatusOnly = result.nodes.length === 0;
288
+ if (nodes.length || isStatusOnly && !wantIgnored) {
276
289
  filtered.push({ ...result, nodes });
277
290
  }
278
291
  }
@@ -95,6 +95,9 @@ watch(
95
95
  shouldRender.value = true;
96
96
  isOpen.value = true;
97
97
  }
98
- }
98
+ },
99
+ // Immediate so a panel mounting AFTER a highlight click (e.g. cold-load
100
+ // sidebar open) still sees the already-set `activeHighlightId` and expands.
101
+ { immediate: true }
99
102
  );
100
103
  </script>
@@ -49,7 +49,13 @@
49
49
  </template>
50
50
 
51
51
  <script setup>
52
- import { computed, useBlokkli, useTemplateRef, watch } from "#imports";
52
+ import {
53
+ computed,
54
+ onMounted,
55
+ useBlokkli,
56
+ useTemplateRef,
57
+ watch
58
+ } from "#imports";
53
59
  import { renderCycle } from "#blokkli/editor/helpers/vue";
54
60
  import { Icon, Tooltip, Pill } from "#blokkli/editor/components";
55
61
  const props = defineProps({
@@ -143,19 +149,22 @@ async function onClick() {
143
149
  });
144
150
  ui.activeHighlightId.value = props.resultId + "_____" + props.target.globalIndex;
145
151
  }
146
- watch(
147
- isFocused,
148
- (isFocused2) => {
149
- if (!isFocused2) {
150
- return;
151
- }
152
- if (elButton.value) {
153
- elButton.value.scrollIntoView({
154
- behavior: "smooth",
155
- block: "nearest"
156
- });
157
- }
158
- },
159
- { immediate: true }
160
- );
152
+ function scrollFocusedIntoView() {
153
+ if (elButton.value) {
154
+ elButton.value.scrollIntoView({
155
+ behavior: "smooth",
156
+ block: "nearest"
157
+ });
158
+ }
159
+ }
160
+ watch(isFocused, (isFocused2) => {
161
+ if (isFocused2) {
162
+ scrollFocusedIntoView();
163
+ }
164
+ });
165
+ onMounted(() => {
166
+ if (isFocused.value) {
167
+ scrollFocusedIntoView();
168
+ }
169
+ });
161
170
  </script>
@@ -0,0 +1,10 @@
1
+ import type { Analyzer } from '../types.js';
2
+ /**
3
+ * Built-in analyzer that surfaces backend-reported validation violations
4
+ * (`state.violations`) inside the analyze sidebar. Always registered by the
5
+ * analyze provider — adapters do not need to opt in.
6
+ *
7
+ * Violations intentionally carry no `identifier` so they cannot be ignored:
8
+ * a validation error must be resolved, not silenced.
9
+ */
10
+ export declare const validationsAnalyzer: Analyzer;
@@ -0,0 +1,39 @@
1
+ export const validationsAnalyzer = {
2
+ id: "blokkli:validations",
3
+ type: "generic",
4
+ label: (_langcode, $t) => $t("analyzerValidationsLabel", "Validations"),
5
+ continuous: true,
6
+ run(context) {
7
+ const { violations, $t } = context;
8
+ const title = $t("analyzerValidationsLabel", "Validations");
9
+ if (!violations.length) {
10
+ return {
11
+ id: "blokkli:validations:pass",
12
+ title,
13
+ category: "content",
14
+ description: $t(
15
+ "analyzerValidationsPassDescription",
16
+ "No validation errors for the current state."
17
+ ),
18
+ status: "pass",
19
+ nodes: []
20
+ };
21
+ }
22
+ const nodes = violations.map((violation) => ({
23
+ description: violation.message,
24
+ uuid: violation.entityUuid,
25
+ targets: violation.entityUuid ? [{ uuid: violation.entityUuid }] : []
26
+ }));
27
+ return {
28
+ id: "blokkli:validations:failed",
29
+ title,
30
+ category: "content",
31
+ description: $t(
32
+ "analyzerValidationsDescription",
33
+ "Validation errors reported for the current state."
34
+ ),
35
+ status: "violation",
36
+ nodes
37
+ };
38
+ }
39
+ };
@@ -2,7 +2,7 @@ import type { FieldListItemTyped } from '#blokkli-build/generated-types';
2
2
  import type { StateProvider } from '#blokkli/editor/providers/state';
3
3
  import type { ReadabilityProvider } from '#blokkli/editor/providers/readability';
4
4
  import type { TextProvider } from '#blokkli/editor/providers/texts';
5
- import type { MutatedField } from '#blokkli/editor/types/state';
5
+ import type { MutatedField, Validation } from '#blokkli/editor/types/state';
6
6
  import type { AnalyzeCategory, AnalyzeNode, AnalyzeResult, AnalyzeStatus } from '../types.js';
7
7
  import { type TextElement } from './collectTextElements.js';
8
8
  export declare class AnalyzerContext {
@@ -13,6 +13,10 @@ export declare class AnalyzerContext {
13
13
  readonly $t: TextProvider;
14
14
  readonly signal: AbortSignal | undefined;
15
15
  readonly mutatedFields: Readonly<MutatedField[]>;
16
+ /**
17
+ * Backend-reported validation violations for the current state.
18
+ */
19
+ readonly violations: Readonly<Validation[]>;
16
20
  /**
17
21
  * The readability provider.
18
22
  */
@@ -9,8 +9,13 @@ export class AnalyzerContext {
9
9
  this.signal = signal;
10
10
  this.readability = readability;
11
11
  this.mutatedFields = JSON.parse(JSON.stringify(state.mutatedFields.value));
12
+ this.violations = JSON.parse(JSON.stringify(state.violations.value));
12
13
  }
13
14
  mutatedFields;
15
+ /**
16
+ * Backend-reported validation violations for the current state.
17
+ */
18
+ violations;
14
19
  /**
15
20
  * The readability provider.
16
21
  */
@@ -56,7 +56,6 @@ defineBlokkliFeature({
56
56
  id: "analyze",
57
57
  label: "Analyze",
58
58
  icon: "bk_mdi_speed",
59
- requiredAdapterMethods: ["getAnalyzers"],
60
59
  description: "Analyze blocks and page for SEO, accessibility, etc.",
61
60
  viewports: []
62
61
  });
@@ -1,9 +1,17 @@
1
1
  [
2
+ {
3
+ "version": "2.0.0-alpha.63",
4
+ "date": "2026-06-11",
5
+ "body": {
6
+ "en": "<h3>New Features</h3>\n<h4>Accept AI text changes paragraph by paragraph</h4>\n<p>When the AI assistant proposes changes to a longer text, you no longer have to\naccept or reject the entire text: individual paragraphs or list items can now be\naccepted or discarded selectively – by click or with the keyboard.</p>\n<h4>Validations in the page analysis</h4>\n<p>Validation errors are now shown directly in the page analysis – alongside the\nother analysis results such as readability or accessibility. The separate\nvalidations view is gone; in return, the page analysis is always available.</p>\n<h4>Validation errors when saving/publishing</h4>\n<p>If publishing fails because of validation errors, the dialog now shows the\nspecific problems so you can fix them right away.</p>\n<h4>Blocks linked in AI assistant answers</h4>\n<p>When the AI assistant refers to a block in its answers, its label now appears as\na clickable link that jumps directly to the block on the page. Previously, a\ncryptic ID was sometimes shown instead.</p>\n<h3>Improvements</h3>\n<ul>\n<li>The color picker in the chart editor has been reworked: all colors and their\nshades are now presented as a clear palette.</li>\n</ul>\n<h3>Fixes</h3>\n<ul>\n<li>Clicking a page analysis highlight now reliably jumps to the affected spot,\neven when the sidebar is closed. Highlights are correctly reset when closing\nthe page analysis.</li>\n<li>The AI assistant&#39;s text search now attributes text to the correct block when\nblocks are nested inside each other.</li>\n</ul>\n",
7
+ "de": "<h3>Neue Funktionen</h3>\n<h4>Textänderungen des KI-Assistenten absatzweise übernehmen</h4>\n<p>Schlägt der KI-Assistent Änderungen an einem längeren Text vor, müssen Sie nicht\nmehr den gesamten Text übernehmen oder ablehnen: Einzelne Absätze oder\nAufzählungspunkte lassen sich jetzt gezielt übernehmen oder verwerfen – per\nKlick oder mit der Tastatur.</p>\n<h4>Validierungen in der Seitenanalyse</h4>\n<p>Validierungsfehler werden neu direkt in der Seitenanalyse angezeigt – zusammen\nmit den anderen Analyse-Ergebnissen wie Lesbarkeit oder Barrierefreiheit. Die\nseparate Ansicht für Validierungen entfällt, die Seitenanalyse ist dafür immer\nverfügbar.</p>\n<h4>Validierungsfehler beim Speichern/Publizieren</h4>\n<p>Schlägt die Publikation wegen Validierungsfehlern fehl, zeigt der Dialog neu die\nkonkreten Probleme an, damit Sie sie direkt beheben können.</p>\n<h4>Blöcke in Antworten des KI-Assistenten verlinkt</h4>\n<p>Wenn der KI-Assistent in seinen Antworten auf einen Block verweist, erscheint\nneu dessen Bezeichnung als klickbarer Link, der direkt zum Block auf der Seite\nspringt. Bisher wurde an dieser Stelle teilweise eine kryptische ID angezeigt.</p>\n<h3>Verbesserungen</h3>\n<ul>\n<li>Die Farbauswahl im Diagramm-Editor wurde überarbeitet: Alle Farben und ihre\nAbstufungen sind neu übersichtlich als Palette dargestellt.</li>\n</ul>\n<h3>Fehlerbehebungen</h3>\n<ul>\n<li>Ein Klick auf eine Hervorhebung der Seitenanalyse springt jetzt zuverlässig\nzur betroffenen Stelle, auch wenn die Sidebar geschlossen ist. Beim Schliessen\nder Seitenanalyse werden Hervorhebungen korrekt zurückgesetzt.</li>\n<li>Die Textsuche des KI-Assistenten ordnet Texte jetzt dem richtigen Block zu,\nwenn Blöcke ineinander verschachtelt sind.</li>\n</ul>\n"
8
+ }
9
+ },
2
10
  {
3
11
  "version": "2.0.0-alpha.61",
4
12
  "date": "2026-06-03",
5
13
  "body": {
6
- "en": "<h3>Improvements</h3>\n<h4>The AI assistant sees your selection from the very first message</h4>\n<p>When you select a block and immediately ask the AI assistant something like\n&quot;Convert this into an accordion&quot;, it now knows right away what you mean.\nPreviously it often had to ask back or look up the selection in a separate\nstep.</p>\n<h4>The AI assistant handles nested blocks correctly</h4>\n<p>When the AI assistant creates a structure made of several blocks, it now\nreliably understands which blocks sit inside which. Previously it sometimes\nmisread its own additions and partially deleted what it had just created.</p>\n<h4>Other improvements</h4>\n<ul>\n<li>The list of changes that the AI assistant proposes for confirmation can now\nbe navigated entirely with the keyboard.</li>\n</ul>\n",
14
+ "en": "<h3>Improvements</h3>\n<h4>The AI assistant sees your selection from the very first message</h4>\n<p>When you select a block and immediately ask the AI assistant something like\n&quot;Convert this into an accordion&quot;, it now knows right away what you mean.\nPreviously it often had to ask back or look up the selection in a separate step.</p>\n<h4>The AI assistant handles nested blocks correctly</h4>\n<p>When the AI assistant creates a structure made of several blocks, it now\nreliably understands which blocks sit inside which. Previously it sometimes\nmisread its own additions and partially deleted what it had just created.</p>\n<h4>Other improvements</h4>\n<ul>\n<li>The list of changes that the AI assistant proposes for confirmation can now be\nnavigated entirely with the keyboard.</li>\n</ul>\n",
7
15
  "de": "<h3>Verbesserungen</h3>\n<h4>KI-Assistent erkennt markierte Blöcke ab der ersten Nachricht</h4>\n<p>Wenn Sie einen Block markieren und dem KI-Assistenten direkt eine Anweisung wie\n«Wandle das in ein Akkordeon um» geben, weiss er sofort, was gemeint ist. Bisher\nmusste er häufig nachfragen oder die Auswahl in einem separaten Schritt\nnachlesen.</p>\n<h4>KI-Assistent ordnet verschachtelte Blöcke korrekt ein</h4>\n<p>Erstellt der KI-Assistent eine Struktur mit mehreren Blöcken, erkennt er nun\nzuverlässig, welche Blöcke ineinander liegen. Vorher kam es vor, dass er die\ngerade erstellte Struktur falsch interpretierte und teilweise wieder löschte.</p>\n<h4>Weitere Verbesserungen</h4>\n<ul>\n<li>Die Liste der Änderungen, die der KI-Assistent zur Bestätigung vorschlägt,\nlässt sich jetzt vollständig mit der Tastatur durchgehen.</li>\n</ul>\n"
8
16
  }
9
17
  },
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="_bk_flex _bk_items-baseline _bk_gap-3 _bk_flex-wrap _bk_min-w-0 _bk_text-sm _bk_-mt-2">
3
3
  <span class="_bk_font-semibold _bk_text-mono-900 _bk_truncate">
4
- {{ user?.name || $t("userDeleted", "[deleted]") }}
4
+ {{ user?.name || `[${$t("deleted", "deleted")}]` }}
5
5
  </span>
6
6
  <RelativeTime v-slot="{ formatted }" :timestamp="created">
7
7
  <span class="_bk_text-xs _bk_text-mono-500">{{ formatted }}</span>
@@ -26,7 +26,7 @@
26
26
  >
27
27
  <Avatar
28
28
  :deleted="!comment.user"
29
- :name="comment.user?.name || $t('userDeleted', '[deleted]')"
29
+ :name="comment.user?.name || `[${$t('deleted', 'deleted')}]`"
30
30
  :seed="comment.user?.id"
31
31
  :image-url="comment.user?.imageUrl"
32
32
  />
@@ -0,0 +1,12 @@
1
+ import type { Validation } from '#blokkli/editor/types/state';
2
+ type __VLS_Props = {
3
+ violations: Validation[];
4
+ errors: string[];
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
7
+ close: () => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
+ onClose?: (() => any) | undefined;
10
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <PanelSection
3
+ :title="
4
+ $t('publishValidationErrorsTitle', 'Cannot publish: validation errors')
5
+ "
6
+ padded
7
+ >
8
+ <InfoBox
9
+ :text="
10
+ $t(
11
+ 'publishValidationErrorsDescription',
12
+ 'Fix the following issues and try publishing again.'
13
+ )
14
+ "
15
+ icon="bk_mdi_error-fill"
16
+ color="red"
17
+ />
18
+
19
+ <ul
20
+ v-if="resolvedViolations.length"
21
+ class="bk-publish-violations bk-control _bk_mt-15"
22
+ >
23
+ <li
24
+ v-for="(item, i) in resolvedViolations"
25
+ :key="item.violation.message + i"
26
+ >
27
+ <button
28
+ type="button"
29
+ class="bk-validation-item"
30
+ @click.prevent="onClick(item)"
31
+ >
32
+ <div v-if="item.block" class="bk-validation-item-header">
33
+ <ItemIcon :bundle="item.block.bundle" />
34
+ <div>{{ item.bundleLabel }}</div>
35
+ </div>
36
+ <div
37
+ v-if="item.fieldLabel"
38
+ class="_bk_text-xs _bk_text-mono-600 _bk_font-medium _bk_mb-3"
39
+ >
40
+ {{ item.fieldLabel }}
41
+ </div>
42
+ <div v-html="item.violation.message" />
43
+ </button>
44
+ </li>
45
+ </ul>
46
+
47
+ <ul
48
+ v-if="errors.length"
49
+ class="bk-publish-errors _bk_list-disc _bk_pl-20 _bk_mt-15 _bk_text-base"
50
+ >
51
+ <li
52
+ v-for="(error, i) in errors"
53
+ :key="'err' + i"
54
+ class="_bk_text-red-dark"
55
+ >
56
+ {{ error }}
57
+ </li>
58
+ </ul>
59
+ </PanelSection>
60
+ </template>
61
+
62
+ <script setup>
63
+ import { computed, useBlokkli } from "#imports";
64
+ import { InfoBox, ItemIcon } from "#blokkli/editor/components";
65
+ import PanelSection from "#blokkli/editor/components/Panel/Section/index.vue";
66
+ import { itemEntityType } from "#blokkli-build/config";
67
+ const props = defineProps({
68
+ violations: { type: Array, required: true },
69
+ errors: { type: Array, required: true }
70
+ });
71
+ const emit = defineEmits(["close"]);
72
+ const { types, blocks, context, eventBus, $t } = useBlokkli();
73
+ function firstFieldSegment(propertyPath) {
74
+ for (const segment of propertyPath.split(".")) {
75
+ if (segment && !/^\d+$/.test(segment)) {
76
+ return segment;
77
+ }
78
+ }
79
+ return void 0;
80
+ }
81
+ function resolveBundle(violation, block) {
82
+ if (block) return block.bundle;
83
+ if (!violation.entityType || violation.entityType === context.value.entityType) {
84
+ return context.value.entityBundle;
85
+ }
86
+ return void 0;
87
+ }
88
+ function resolveFieldLabel(violation, block) {
89
+ if (!violation.propertyPath) return void 0;
90
+ const fieldName = firstFieldSegment(violation.propertyPath);
91
+ if (!fieldName) return void 0;
92
+ const entityType = violation.entityType ?? context.value.entityType;
93
+ const bundle = resolveBundle(violation, block);
94
+ if (!entityType || !bundle) return void 0;
95
+ return types.getFieldConfig(entityType, bundle, fieldName)?.label;
96
+ }
97
+ const resolvedViolations = computed(
98
+ () => props.violations.map((violation) => {
99
+ const block = violation.entityUuid ? blocks.getBlock(violation.entityUuid) ?? null : null;
100
+ const bundleLabel = block ? types.getBlockBundleDefinition(block.bundle)?.label : void 0;
101
+ return {
102
+ violation,
103
+ block,
104
+ bundleLabel,
105
+ fieldLabel: resolveFieldLabel(violation, block)
106
+ };
107
+ })
108
+ );
109
+ function onClick(item) {
110
+ if (item.violation.entityType !== itemEntityType || !item.violation.entityUuid) {
111
+ return;
112
+ }
113
+ eventBus.emit("select", item.violation.entityUuid);
114
+ eventBus.emit("scrollIntoView", { uuid: item.violation.entityUuid });
115
+ emit("close");
116
+ }
117
+ </script>
@@ -0,0 +1,12 @@
1
+ import type { Validation } from '#blokkli/editor/types/state';
2
+ type __VLS_Props = {
3
+ violations: Validation[];
4
+ errors: string[];
5
+ };
6
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
7
+ close: () => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
+ onClose?: (() => any) | undefined;
10
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
@@ -116,6 +116,12 @@
116
116
  </InfoBox>
117
117
  </FormItem>
118
118
  </PanelSection>
119
+ <Violations
120
+ v-if="hasFailures"
121
+ :violations="failedViolations"
122
+ :errors="failedErrors"
123
+ @close="$emit('close')"
124
+ />
119
125
  <div>
120
126
  <FormItem v-if="successItems.length && showTable">
121
127
  <h2 class="bk-heading-2">
@@ -200,6 +206,7 @@ import { emitMessage } from "#blokkli/editor/events";
200
206
  import Item from "./Item.vue";
201
207
  import PublishOption, {} from "./PublishOption.vue";
202
208
  import Summary from "./Summary.vue";
209
+ import Violations from "./Violations.vue";
203
210
  import PanelSection from "#blokkli/editor/components/Panel/Section/index.vue";
204
211
  const showTable = false;
205
212
  const { adapter, $t, state, context, ui } = useBlokkli();
@@ -393,6 +400,17 @@ const selectedToPublishItems = computed(
393
400
  )
394
401
  );
395
402
  const isLoading = computed(() => status.value === "pending" || isMutating.value);
403
+ const failedStatuses = computed(
404
+ () => Object.values(mutationStatusItems.value).filter((s) => !s.success)
405
+ );
406
+ const failedViolations = computed(
407
+ () => failedStatuses.value.flatMap((s) => s.violations ?? [])
408
+ );
409
+ const failedErrors = computed(() => {
410
+ const messages = failedStatuses.value.flatMap((s) => s.errors ?? []);
411
+ return Array.from(new Set(messages));
412
+ });
413
+ const hasFailures = computed(() => failedStatuses.value.length > 0);
396
414
  const scheduleDateError = computed(() => {
397
415
  if (publishMode.value !== "scheduled" || !scheduleDate.value) {
398
416
  return "";
@@ -499,12 +517,19 @@ async function removeScheduledDate() {
499
517
  }
500
518
  isMutating.value = true;
501
519
  const previousScheduleDate = scheduleDate.value;
502
- const success = await state.mutateWithLoadingState(
503
- () => adapter.unscheduleEditState({
520
+ let success = false;
521
+ try {
522
+ const response = await adapter.unscheduleEditState({
504
523
  hostEntityType: context.value.entityType,
505
524
  hostEntityUuid: context.value.entityUuid
506
- })
507
- );
525
+ });
526
+ success = !!response?.success;
527
+ if (response?.state) {
528
+ state.applyMutationState(response.state);
529
+ }
530
+ } catch {
531
+ success = false;
532
+ }
508
533
  if (success) {
509
534
  await refresh();
510
535
  scheduleDate.value = previousScheduleDate;
@@ -518,8 +543,7 @@ async function onSubmit() {
518
543
  }
519
544
  isMutating.value = true;
520
545
  const items = stateItems.value;
521
- const hasAnyError = false;
522
- let mutationResult = false;
546
+ const selectedItems = [];
523
547
  for (const item of items) {
524
548
  const isSelected = states.value.includes(item.id) || item.hostEntityType === context.value.entityType && item.hostEntityUuid === context.value.entityUuid;
525
549
  if (!isSelected) {
@@ -528,29 +552,41 @@ async function onSubmit() {
528
552
  if (publishedIds.value.includes(item.id)) {
529
553
  continue;
530
554
  }
555
+ selectedItems.push(item);
531
556
  try {
557
+ let response;
532
558
  if (publishMode.value === "scheduled") {
533
559
  if (!adapter.scheduleEditState) {
534
560
  throw new Error("scheduleEditState method not available");
535
561
  }
536
- mutationResult = await state.mutateWithLoadingState(
537
- () => adapter.scheduleEditState({
538
- hostEntityType: item.hostEntityType,
539
- hostEntityUuid: item.hostEntityUuid,
540
- revisionLogMessage: revisionMessage.value,
541
- date: scheduleDate.value
542
- })
543
- );
562
+ response = await adapter.scheduleEditState({
563
+ hostEntityType: item.hostEntityType,
564
+ hostEntityUuid: item.hostEntityUuid,
565
+ revisionLogMessage: revisionMessage.value,
566
+ date: scheduleDate.value
567
+ });
544
568
  } else {
545
- mutationResult = await state.mutateWithLoadingState(
546
- () => adapter.publish({
547
- hostEntityType: item.hostEntityType,
548
- hostEntityUuid: item.hostEntityUuid,
549
- closeAfterPublish: true,
550
- revisionLogMessage: revisionMessage.value,
551
- publishIfUnpublished: shouldPublish.value
552
- })
553
- );
569
+ response = await adapter.publish({
570
+ hostEntityType: item.hostEntityType,
571
+ hostEntityUuid: item.hostEntityUuid,
572
+ closeAfterPublish: true,
573
+ revisionLogMessage: revisionMessage.value,
574
+ publishIfUnpublished: shouldPublish.value
575
+ });
576
+ }
577
+ if (response?.success) {
578
+ mutationStatusItems.value[item.id] = { id: item.id, success: true };
579
+ publishedIds.value.push(item.id);
580
+ } else {
581
+ mutationStatusItems.value[item.id] = {
582
+ id: item.id,
583
+ success: false,
584
+ errors: response?.errors,
585
+ violations: response?.violations
586
+ };
587
+ }
588
+ if (response?.state && item.id === currentId.value) {
589
+ state.applyMutationState(response.state);
554
590
  }
555
591
  } catch {
556
592
  mutationStatusItems.value[item.id] = {
@@ -563,7 +599,8 @@ async function onSubmit() {
563
599
  }
564
600
  }
565
601
  isMutating.value = false;
566
- if (hasAnyError || !mutationResult) {
602
+ const allSucceeded = selectedItems.length > 0 && selectedItems.every((item) => mutationStatusItems.value[item.id]?.success);
603
+ if (!allSucceeded) {
567
604
  return;
568
605
  }
569
606
  if (publishMode.value === "scheduled") {
@@ -93,10 +93,8 @@ async function publishCurrent() {
93
93
  $t("publishSuccess", "Changes published successfully.")
94
94
  );
95
95
  if (!success) {
96
- const validations = state.violations.value;
97
- if (validations.length) {
98
- eventBus.emit("publish:failed");
99
- eventBus.emit("sidebar:open", "violations");
96
+ if (state.violations.value.length) {
97
+ eventBus.emit("sidebar:open", "analyze");
100
98
  }
101
99
  return;
102
100
  }
@@ -101,10 +101,6 @@ declare module '#blokkli/editor/adapter' {
101
101
  }
102
102
  declare module '#blokkli/editor/events' {
103
103
  interface EventbusEvents {
104
- /**
105
- * Emitted when publishing failed.
106
- */
107
- 'publish:failed': undefined;
108
104
  /**
109
105
  * Show the publish dialog.
110
106
  */
@@ -3,4 +3,11 @@ export declare function toShaderColor(rgba: RGB): RGB;
3
3
  export declare const rgbaToString: (color: RGB, alpha?: number) => string;
4
4
  export declare function getContrastRatio(color1: RGB, color2: RGB): number;
5
5
  export declare function findHighestContrastColor(colors: RGB[], backgroundColor?: RGB): RGB;
6
+ export declare const hexToRgb: (hex: string) => RGB | undefined;
7
+ /**
8
+ * Whether the given hex color is "light" — i.e. dark text on top of it would
9
+ * be more readable than light text. Use this to decide between black/white
10
+ * foreground content on a colored background. Unparseable hex → `false`.
11
+ */
12
+ export declare const isLightHex: (hex: string) => boolean;
6
13
  export declare const parseColorString: (color: string) => RGB | undefined;
@@ -28,6 +28,20 @@ export function findHighestContrastColor(colors, backgroundColor = [255, 255, 25
28
28
  }
29
29
  return maxContrastColor;
30
30
  }
31
+ export const hexToRgb = (hex) => {
32
+ const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
33
+ if (!match) return;
34
+ return [
35
+ Number.parseInt(match[1], 16),
36
+ Number.parseInt(match[2], 16),
37
+ Number.parseInt(match[3], 16)
38
+ ];
39
+ };
40
+ export const isLightHex = (hex) => {
41
+ const rgb = hexToRgb(hex);
42
+ if (!rgb) return false;
43
+ return getContrastRatio(rgb, [0, 0, 0]) > getContrastRatio(rgb, [255, 255, 255]);
44
+ };
31
45
  export const parseColorString = (color) => {
32
46
  const rgbaRegex = /^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(0|1|0?\.\d+))?\)$/;
33
47
  const match = color.match(rgbaRegex);