@lblod/ember-rdfa-editor-lblod-plugins 33.0.0 → 33.1.0

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 (28) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/addon/components/besluit-topic-plugin/besluit-topic-toolbar-dropdown.ts +1 -1
  3. package/addon/components/besluit-type-plugin/toolbar-dropdown.gts +1 -1
  4. package/addon/components/decision-plugin/decision-plugin-card.gts +3 -2
  5. package/addon/components/decision-plugin/insert-article.gts +1 -1
  6. package/addon/components/document-validation-plugin/card.gts +130 -33
  7. package/addon/components/lpdc-plugin/lpdc-insert.ts +1 -1
  8. package/addon/components/roadsign-regulation-plugin/roadsign-regulation-card.gts +1 -1
  9. package/addon/components/roadsign-regulation-plugin/roadsigns-modal.gts +1 -1
  10. package/addon/plugins/besluit-type-plugin/utils/besluit-type-instances.ts +1 -1
  11. package/addon/plugins/besluit-type-plugin/utils/set-besluit-type.ts +1 -1
  12. package/addon/plugins/decision-plugin/commands/insert-article-container.ts +14 -1
  13. package/addon/plugins/decision-plugin/commands/insert-description.ts +9 -1
  14. package/addon/plugins/decision-plugin/commands/insert-motivation.ts +9 -1
  15. package/addon/plugins/decision-plugin/commands/insert-title.ts +9 -1
  16. package/addon/plugins/document-validation-plugin/common-fixes.ts +125 -0
  17. package/addon/plugins/document-validation-plugin/index.ts +61 -32
  18. package/addon/plugins/roadsign-regulation-plugin/queries/mobility-measure-concept.ts +9 -1
  19. package/addon/{plugins/besluit-topic-plugin/utils/helpers.ts → utils/decision-utils.ts} +33 -9
  20. package/declarations/addon/components/document-validation-plugin/card.d.ts +51 -1
  21. package/declarations/addon/plugins/decision-plugin/commands/insert-article-container.d.ts +3 -1
  22. package/declarations/addon/plugins/document-validation-plugin/common-fixes.d.ts +6 -0
  23. package/declarations/addon/plugins/document-validation-plugin/index.d.ts +30 -6
  24. package/declarations/addon/{plugins/besluit-topic-plugin/utils/helpers.d.ts → utils/decision-utils.d.ts} +5 -1
  25. package/package.json +2 -2
  26. package/pnpm-lock.yaml +5 -5
  27. package/translations/en-US.yaml +1 -0
  28. package/translations/nl-BE.yaml +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @lblod/ember-rdfa-editor-lblod-plugins
2
2
 
3
+ ## 33.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#589](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/pull/589) [`380f999`](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/commit/380f9998e6c8f37f1c7be5bf2be528884f24a850) Thanks [@lagartoverde](https://github.com/lagartoverde)! - Add actions to document validation plugin
8
+
9
+ ### Patch Changes
10
+
11
+ - [#608](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/pull/608) [`7d01346`](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/commit/7d01346456985efa3fc4438c828b4fbc31de297e) Thanks [@lagartoverde](https://github.com/lagartoverde)! - Fix ordering of measures in IRGN plugin
12
+
3
13
  ## 33.0.0
4
14
 
5
15
  ### Major Changes
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  getCurrentBesluitRange,
17
17
  getCurrentBesluitURI,
18
- } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
18
+ } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
19
19
  import {
20
20
  updateBesluitTopicResource,
21
21
  TOPIC_PREDICATE,
@@ -16,7 +16,7 @@ import { CircleXIcon } from '@appuniversum/ember-appuniversum/components/icons/c
16
16
  import { SayController } from '@lblod/ember-rdfa-editor';
17
17
  import fetchBesluitTypes from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-type-plugin/utils/fetchBesluitTypes';
18
18
  import { BesluitTypePluginOptions } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-type-plugin';
19
- import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
19
+ import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
20
20
  import {
21
21
  BesluitTypeInstance,
22
22
  checkBesluitTypeInstance,
@@ -23,7 +23,7 @@ import { on } from '@ember/modifier';
23
23
  import { not } from 'ember-truth-helpers';
24
24
  import t from 'ember-intl/helpers/t';
25
25
  import { TemplateOnlyComponent } from '@ember/component/template-only';
26
- import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
26
+ import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
27
27
 
28
28
  interface DecisionCardOptions {
29
29
  articleUriGenerator?: () => string;
@@ -145,12 +145,13 @@ export default class DecisionPluginCard extends Component<Sig> {
145
145
  }
146
146
  @action
147
147
  insertArticleBlock() {
148
- if (this.decisionUri) {
148
+ if (this.decisionUri && this.decisionNodeLocation) {
149
149
  this.controller.doCommand(
150
150
  insertArticleContainer({
151
151
  intl: this.intl,
152
152
  decisionUri: this.decisionUri,
153
153
  articleUriGenerator: this.args.options?.articleUriGenerator,
154
+ decisionLocation: this.decisionNodeLocation,
154
155
  }),
155
156
  {
156
157
  view: this.controller.mainEditorView,
@@ -4,7 +4,7 @@ import { action } from '@ember/object';
4
4
  import Component from '@glimmer/component';
5
5
  import { AddIcon } from '@appuniversum/ember-appuniversum/components/icons/add';
6
6
  import { PNode, SayController } from '@lblod/ember-rdfa-editor';
7
- import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
7
+ import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
8
8
  import { buildArticleStructure } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/decision-plugin/utils/build-article-structure';
9
9
  import { not } from 'ember-truth-helpers';
10
10
  import { service } from '@ember/service';
@@ -12,6 +12,8 @@ import AuButton from '@appuniversum/ember-appuniversum/components/au-button';
12
12
  import { ExternalLinkIcon } from '@appuniversum/ember-appuniversum/components/icons/external-link';
13
13
  import t from 'ember-intl/helpers/t';
14
14
  import { eq } from 'ember-truth-helpers';
15
+ import ValidationReport from 'rdf-validate-shacl/src/validation-report';
16
+ import { CircleInfoIcon } from '@appuniversum/ember-appuniversum/components/icons/circle-info';
15
17
 
16
18
  interface Sig {
17
19
  Args: {
@@ -19,6 +21,17 @@ interface Sig {
19
21
  };
20
22
  }
21
23
 
24
+ type ExtractWithKey<T, K extends PropertyKey> = T extends Record<K, unknown>
25
+ ? T
26
+ : never;
27
+
28
+ function hasProperty<
29
+ O extends object = object,
30
+ K extends PropertyKey = PropertyKey,
31
+ >(obj: O, key: K): obj is ExtractWithKey<O, K> {
32
+ return key in obj;
33
+ }
34
+
22
35
  export default class DocumentValidationPluginCard extends Component<Sig> {
23
36
  get controller() {
24
37
  return this.args.controller;
@@ -37,10 +50,26 @@ export default class DocumentValidationPluginCard extends Component<Sig> {
37
50
  }
38
51
  get documentValidationErrors() {
39
52
  if (!this.validationState) return [];
40
- const { propertiesWithErrors } = this.validationState;
53
+
54
+ const { propertiesWithErrors, rules } = this.validationState;
41
55
  if (!propertiesWithErrors) return undefined;
42
56
 
43
- return propertiesWithErrors;
57
+ const documentValidationErrors = propertiesWithErrors.map((property) => {
58
+ const rule = rules.find((rule) => property?.shape === rule.shaclRule);
59
+ if (rule && 'violations' in rule) {
60
+ const rulePerConstraint = rule.violations[property?.constraint];
61
+ return {
62
+ ...property,
63
+ rule: rulePerConstraint,
64
+ };
65
+ } else {
66
+ return {
67
+ ...property,
68
+ rule: rule,
69
+ };
70
+ }
71
+ });
72
+ return documentValidationErrors;
44
73
  }
45
74
  get propertiesWithoutErrors() {
46
75
  if (!this.validationState) return [];
@@ -65,6 +94,53 @@ export default class DocumentValidationPluginCard extends Component<Sig> {
65
94
  return ['valid', 'no-matches'].includes(this.status);
66
95
  }
67
96
 
97
+ doActionAndTriggerValidation = async (
98
+ action: (controller: SayController, report: ValidationReport) => void,
99
+ ) => {
100
+ action(this.controller, this.validationState?.report as ValidationReport);
101
+ const pluginState = documentValidationPluginKey.getState(
102
+ this.controller.mainEditorView.state,
103
+ );
104
+ if (!pluginState) return;
105
+ const { validationCallback } = pluginState;
106
+ await validationCallback(
107
+ this.controller.mainEditorView,
108
+ this.controller.htmlContent,
109
+ );
110
+ };
111
+
112
+ oldVal: typeof this.documentValidationErrors;
113
+
114
+ dedupeDocumentValidationErrors = (
115
+ val: typeof this.documentValidationErrors,
116
+ ) => {
117
+ if (this.compareDocumentValidationErrors(val, this.oldVal)) {
118
+ return this.oldVal;
119
+ } else {
120
+ this.oldVal = val;
121
+ return val;
122
+ }
123
+ };
124
+
125
+ compareDocumentValidationErrors = (
126
+ val1: typeof this.documentValidationErrors,
127
+ val2: typeof this.documentValidationErrors,
128
+ ) => {
129
+ if (!val1?.length) return false;
130
+ if (val1.length !== val2?.length) return false;
131
+ for (let i = 0; i < val1.length; i++) {
132
+ if (
133
+ !val1 ||
134
+ !val2 ||
135
+ !val1[i] ||
136
+ !val2[i] ||
137
+ val1[i].shape !== val2[i].shape
138
+ )
139
+ return false;
140
+ }
141
+ return true;
142
+ };
143
+
68
144
  <template>
69
145
  <AuCard
70
146
  @flex={{true}}
@@ -94,42 +170,63 @@ export default class DocumentValidationPluginCard extends Component<Sig> {
94
170
  </p>
95
171
  </c.header>
96
172
  <c.content>
97
- <p class='au-u-medium au-u-para-small'>{{t
98
- 'document-validation-plugin.description'
99
- }}</p>
100
- {{#each this.documentValidationErrors as |error|}}
101
- <div class='say-document-validation__error-container'>
102
- <AuIcon
103
- @icon={{CloseFilledIcon}}
104
- @size='large'
105
- @ariaHidden={{true}}
106
- class='say-document-validation__icon-error au-u-margin-right-small'
107
- />
108
- <div>
109
- {{error.message}}
110
- <AuButton
111
- class='au-u-padding-left-none au-u-padding-right-none'
112
- @icon={{ExternalLinkIcon}}
113
- @skin='link'
114
- title={{error.subject}}
115
- {{on 'click' (fn this.goToSubject error.subject)}}
116
- >{{t 'document-validation-plugin.see-related-node'}}</AuButton>
117
- </div>
118
- </div>
119
- {{/each}}
120
- {{#each this.propertiesWithoutErrors as |property|}}
121
- <div class='say-document-validation__error-container'>
122
- <div class='au-u-margin-right-small'>
173
+ <div>
174
+ {{#each
175
+ (this.dedupeDocumentValidationErrors this.documentValidationErrors)
176
+ as |error|
177
+ }}
178
+ <div class='say-document-validation__error-container'>
123
179
  <AuIcon
124
- @icon={{CheckFilledIcon}}
180
+ @icon={{CloseFilledIcon}}
125
181
  @size='large'
126
182
  @ariaHidden={{true}}
127
- class='say-document-validation__icon-success'
183
+ class='say-document-validation__icon-error au-u-margin-right-small'
128
184
  />
185
+ <div>
186
+ {{error.message}}
187
+ <AuButton
188
+ class='au-u-padding-left-none au-u-padding-right-none'
189
+ @icon={{ExternalLinkIcon}}
190
+ @skin='link'
191
+ title={{error.subject}}
192
+ {{on 'click' (fn this.goToSubject error.subject)}}
193
+ >{{t 'document-validation-plugin.see-related-node'}}</AuButton>
194
+ {{#if error.rule}}
195
+ {{#if (hasProperty error.rule 'action')}}
196
+ <AuButton
197
+ class='au-u-padding-left-none au-u-padding-right-none'
198
+ @icon={{ExternalLinkIcon}}
199
+ @skin='link'
200
+ title={{error.subject}}
201
+ {{on
202
+ 'click'
203
+ (fn this.doActionAndTriggerValidation error.rule.action)
204
+ }}
205
+ >{{error.rule.buttonTitle}}</AuButton>
206
+ {{/if}}
207
+ {{#if (hasProperty error.rule 'helpText')}}
208
+ <span title={{error.rule.helpText}}>
209
+ <AuIcon @icon={{CircleInfoIcon}} />
210
+ </span>
211
+ {{/if}}
212
+ {{/if}}
213
+ </div>
214
+ </div>
215
+ {{/each}}
216
+ {{#each this.propertiesWithoutErrors as |property|}}
217
+ <div class='say-document-validation__error-container'>
218
+ <div class='au-u-margin-right-small'>
219
+ <AuIcon
220
+ @icon={{CheckFilledIcon}}
221
+ @size='large'
222
+ @ariaHidden={{true}}
223
+ class='say-document-validation__icon-success'
224
+ />
225
+ </div>
226
+ {{property.message}}
129
227
  </div>
130
- {{property.message}}
131
- </div>
132
- {{/each}}
228
+ {{/each}}
229
+ </div>
133
230
  </c.content>
134
231
 
135
232
  </AuCard>
@@ -11,7 +11,7 @@ import {
11
11
  type LpdcPluginConfig,
12
12
  } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/lpdc-plugin';
13
13
  import { v4 as uuidv4 } from 'uuid';
14
- import { getCurrentBesluitURI } from '../../plugins/besluit-topic-plugin/utils/helpers';
14
+ import { getCurrentBesluitURI } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
15
15
  import { SRO } from '../../utils/constants';
16
16
 
17
17
  interface Args {
@@ -3,7 +3,7 @@ import { action } from '@ember/object';
3
3
  import Component from '@glimmer/component';
4
4
  import { tracked } from '@glimmer/tracking';
5
5
  import { SayController } from '@lblod/ember-rdfa-editor';
6
- import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
6
+ import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
7
7
  import { RoadsignRegulationPluginOptions } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/roadsign-regulation-plugin';
8
8
  import { RDF } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
9
9
  import { OutgoingTriple } from '@lblod/ember-rdfa-editor/core/rdfa-processor';
@@ -6,7 +6,7 @@ import { action } from '@ember/object';
6
6
  import Component from '@glimmer/component';
7
7
  import { tracked } from '@glimmer/tracking';
8
8
  import { SayController } from '@lblod/ember-rdfa-editor';
9
- import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
9
+ import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
10
10
  import { RoadsignRegulationPluginOptions } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/roadsign-regulation-plugin';
11
11
  import {
12
12
  countMobilityMeasures,
@@ -1,6 +1,6 @@
1
1
  import { type EditorState } from '@lblod/ember-rdfa-editor';
2
2
  import { getOutgoingTripleList } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/namespace';
3
- import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
3
+ import { getCurrentBesluitRange } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
4
4
  import { RDF } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
5
5
  import { type BesluitType } from './fetchBesluitTypes';
6
6
  import { type NamedNodeTriple } from '@lblod/ember-rdfa-editor/core/rdfa-processor';
@@ -8,7 +8,7 @@ import {
8
8
  removePropertyFromNode,
9
9
  } from '@lblod/ember-rdfa-editor/utils/rdfa-utils';
10
10
  import { sayDataFactory } from '@lblod/ember-rdfa-editor/core/say-data-factory';
11
- import { getCurrentBesluitURI } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/besluit-topic-plugin/utils/helpers';
11
+ import { getCurrentBesluitURI } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
12
12
  import {
13
13
  extractBesluitTypeUris,
14
14
  isValidTypeChoice,
@@ -8,16 +8,19 @@ import { buildArticleStructure } from '../utils/build-article-structure';
8
8
  import { transactionCombinator } from '@lblod/ember-rdfa-editor/utils/transaction-utils';
9
9
  import { insertArticle } from '../actions/insert-article';
10
10
  import { recalculateNumbers } from '../../structure-plugin/monads/recalculate-structure-numbers';
11
+ import { NodeWithPos } from '@curvenote/prosemirror-utils';
11
12
 
12
13
  interface InsertArticleContainerArgs {
13
14
  intl: IntlService;
14
15
  decisionUri: string;
15
16
  articleUriGenerator?: () => string;
17
+ decisionLocation: NodeWithPos;
16
18
  }
17
19
 
18
20
  export default function insertArticleContainer({
19
21
  decisionUri,
20
22
  articleUriGenerator,
23
+ decisionLocation,
21
24
  }: InsertArticleContainerArgs): Command {
22
25
  return function (state: EditorState, dispatch?: (tr: Transaction) => void) {
23
26
  const { schema } = state;
@@ -33,9 +36,19 @@ export default function insertArticleContainer({
33
36
  const articleNode = buildArticleStructure(schema, articleUriGenerator);
34
37
 
35
38
  const factory = new SayDataFactory();
39
+ let replaceTr;
40
+ if (state.selection.$from.pos === decisionLocation.pos) {
41
+ replaceTr = state.tr.replaceRangeWith(
42
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
43
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
44
+ containerNode,
45
+ );
46
+ } else {
47
+ replaceTr = state.tr.replaceSelectionWith(containerNode);
48
+ }
36
49
  const { transaction: newTr, result } = transactionCombinator<boolean>(
37
50
  state,
38
- state.tr.replaceSelectionWith(containerNode),
51
+ replaceTr,
39
52
  )([
40
53
  addPropertyToNode({
41
54
  resource: decisionUri,
@@ -31,7 +31,15 @@ export default function insertDescription({
31
31
  );
32
32
  const tr = state.tr;
33
33
 
34
- tr.replaceSelectionWith(nodeToInsert);
34
+ if (state.selection.$from.pos === decisionLocation.pos) {
35
+ tr.replaceRangeWith(
36
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
37
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
38
+ nodeToInsert,
39
+ );
40
+ } else {
41
+ tr.replaceSelectionWith(nodeToInsert);
42
+ }
35
43
 
36
44
  const factory = new SayDataFactory();
37
45
  const { transaction: newTr, result } = transactionCombinator<boolean>(
@@ -110,7 +110,15 @@ export default function insertMotivation({
110
110
  ],
111
111
  );
112
112
  const tr = state.tr;
113
- tr.replaceSelectionWith(nodeToInsert);
113
+ if (state.selection.$from.pos === decisionLocation.pos) {
114
+ tr.replaceRangeWith(
115
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
116
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
117
+ nodeToInsert,
118
+ );
119
+ } else {
120
+ tr.replaceSelectionWith(nodeToInsert);
121
+ }
114
122
  const factory = new SayDataFactory();
115
123
 
116
124
  const { transaction: newTr, result } = transactionCombinator<boolean>(
@@ -31,7 +31,15 @@ export default function insertTitle({
31
31
  );
32
32
 
33
33
  const tr = state.tr;
34
- tr.replaceSelectionWith(nodeToInsert);
34
+ if (state.selection.$from.pos === decisionLocation.pos) {
35
+ tr.replaceRangeWith(
36
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
37
+ decisionLocation.pos + decisionLocation.node.nodeSize - 1,
38
+ nodeToInsert,
39
+ );
40
+ } else {
41
+ tr.replaceSelectionWith(nodeToInsert);
42
+ }
35
43
 
36
44
  const factory = new SayDataFactory();
37
45
  const { transaction: newTr, result } = transactionCombinator<boolean>(
@@ -0,0 +1,125 @@
1
+ import { SayController } from '@lblod/ember-rdfa-editor';
2
+ import { getDecisionNodeLocation } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/decision-utils';
3
+ import IntlService from 'ember-intl/services/intl';
4
+ import {
5
+ Notification,
6
+ notificationPluginKey,
7
+ } from '@lblod/ember-rdfa-editor/plugins/notification';
8
+ import {
9
+ insertMotivation,
10
+ insertArticleContainer,
11
+ insertDescription,
12
+ insertTitle,
13
+ } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/decision-plugin/commands';
14
+ import { CircleXIcon } from '@appuniversum/ember-appuniversum/components/icons/circle-x';
15
+
16
+ export function insertTitleAtCursor(
17
+ controller: SayController,
18
+ intl: IntlService,
19
+ ) {
20
+ const decisionNodeLocation = getDecisionNodeLocation(controller);
21
+ if (!decisionNodeLocation) {
22
+ return sendNotificationError(
23
+ controller,
24
+ intl.t('document-validation-plugin.decision-node-not-found'),
25
+ );
26
+ }
27
+ controller.doCommand(
28
+ insertTitle({
29
+ placeholderText: intl.t('besluit-plugin.placeholder.decision-title'),
30
+ decisionLocation: decisionNodeLocation,
31
+ }),
32
+ { view: controller.mainEditorView },
33
+ );
34
+ controller.focus();
35
+ }
36
+
37
+ export function insertDescriptionAtCursor(
38
+ controller: SayController,
39
+ intl: IntlService,
40
+ ) {
41
+ const decisionNodeLocation = getDecisionNodeLocation(controller);
42
+ if (!decisionNodeLocation) {
43
+ return sendNotificationError(
44
+ controller,
45
+ intl.t('document-validation-plugin.decision-node-not-found'),
46
+ );
47
+ }
48
+ controller.doCommand(
49
+ insertDescription({
50
+ placeholderText: intl.t(
51
+ 'besluit-plugin.placeholder.decision-description',
52
+ ),
53
+ decisionLocation: decisionNodeLocation,
54
+ }),
55
+ {
56
+ view: controller.mainEditorView,
57
+ },
58
+ );
59
+ controller.focus();
60
+ }
61
+
62
+ export function insertMotivationAtCursor(
63
+ controller: SayController,
64
+ intl: IntlService,
65
+ ) {
66
+ const decisionNodeLocation = getDecisionNodeLocation(controller);
67
+ if (!decisionNodeLocation) {
68
+ return sendNotificationError(
69
+ controller,
70
+ intl.t('document-validation-plugin.decision-node-not-found'),
71
+ );
72
+ }
73
+ controller.doCommand(
74
+ insertMotivation({
75
+ intl: intl,
76
+ decisionLocation: decisionNodeLocation,
77
+ }),
78
+ {
79
+ view: controller.mainEditorView,
80
+ },
81
+ );
82
+ controller.focus();
83
+ }
84
+
85
+ export function insertArticleContainerAtCursor(
86
+ controller: SayController,
87
+ intl: IntlService,
88
+ articleUriGenerator?: () => string,
89
+ ) {
90
+ const decisionNodeLocation = getDecisionNodeLocation(controller);
91
+ if (!decisionNodeLocation) {
92
+ return sendNotificationError(
93
+ controller,
94
+ intl.t('document-validation-plugin.decision-node-not-found'),
95
+ );
96
+ }
97
+ controller.doCommand(
98
+ insertArticleContainer({
99
+ intl: intl,
100
+ decisionUri: decisionNodeLocation?.node.attrs.subject,
101
+ articleUriGenerator: articleUriGenerator,
102
+ decisionLocation: decisionNodeLocation,
103
+ }),
104
+ {
105
+ view: controller.mainEditorView,
106
+ },
107
+ );
108
+ }
109
+
110
+ function sendNotificationError(controller: SayController, text: string) {
111
+ // Show a notification via the notification plugin
112
+ const { notificationCallback } = notificationPluginKey.getState(
113
+ controller.mainEditorState,
114
+ ) as {
115
+ notificationCallback: (notification: Notification) => void;
116
+ intl: IntlService;
117
+ };
118
+ notificationCallback({
119
+ title: text,
120
+ options: {
121
+ type: 'error',
122
+ icon: CircleXIcon,
123
+ },
124
+ });
125
+ }
@@ -2,14 +2,17 @@ import factory from '@rdfjs/dataset';
2
2
  import SHACLValidator from 'rdf-validate-shacl';
3
3
  import { Parser as ParserN3 } from 'n3';
4
4
  import { RdfaParser } from 'rdfa-streaming-parser';
5
- import { ProsePlugin, PluginKey, EditorView } from '@lblod/ember-rdfa-editor';
5
+ import {
6
+ ProsePlugin,
7
+ PluginKey,
8
+ EditorView,
9
+ SayController,
10
+ } from '@lblod/ember-rdfa-editor';
6
11
  import removeQuotes from '@lblod/ember-rdfa-editor-lblod-plugins/utils/remove-quotes';
7
12
  import {
8
- BlankNode,
9
13
  DataFactory,
10
14
  DatasetCore,
11
15
  DatasetCoreFactory,
12
- NamedNode,
13
16
  Quad,
14
17
  } from '@rdfjs/types';
15
18
  import ValidationReport from 'rdf-validate-shacl/src/validation-report';
@@ -18,8 +21,34 @@ import { SayDataFactory } from '@lblod/ember-rdfa-editor/core/say-data-factory';
18
21
  export const documentValidationPluginKey =
19
22
  new PluginKey<DocumentValidationPluginState>('DOCUMENT_VALIDATION');
20
23
 
21
- interface DocumentValidationPluginArgs {
24
+ type Violation =
25
+ | {
26
+ action: (controller: SayController, report: ValidationReport) => void;
27
+ buttonTitle: string;
28
+ }
29
+ | {
30
+ helpText: string;
31
+ };
32
+ type Rule =
33
+ | {
34
+ shaclRule: string;
35
+ violations: {
36
+ [key: string]: Violation;
37
+ };
38
+ }
39
+ | {
40
+ shaclRule: string;
41
+ action: (controller: SayController, report: ValidationReport) => void;
42
+ buttonTitle: string;
43
+ }
44
+ | {
45
+ shaclRule: string;
46
+ helpText: string;
47
+ };
48
+
49
+ export interface DocumentValidationPluginArgs {
22
50
  documentShape: string;
51
+ rules: Rule[];
23
52
  }
24
53
 
25
54
  export type ShaclValidationReport = ValidationReport.ValidationReport<
@@ -27,17 +56,16 @@ export type ShaclValidationReport = ValidationReport.ValidationReport<
27
56
  DatasetCoreFactory<Quad, Quad, DatasetCore<Quad, Quad>>
28
57
  >;
29
58
 
59
+ type PropertyWithError = {
60
+ message: string;
61
+ subject: string | undefined;
62
+ shape: string;
63
+ constraint: string;
64
+ };
30
65
  interface DocumentValidationResult {
31
66
  report?: ValidationReport;
32
67
  propertiesWithoutErrors: { message: string }[];
33
- propertiesWithErrors: (
34
- | {
35
- message: string;
36
- subject: string | undefined;
37
- }
38
- // TODO get rid of this?
39
- | undefined
40
- )[];
68
+ propertiesWithErrors: PropertyWithError[];
41
69
  }
42
70
  export interface DocumentValidationTransactionMeta
43
71
  extends DocumentValidationResult {
@@ -47,6 +75,7 @@ export interface DocumentValidationPluginState
47
75
  extends DocumentValidationResult {
48
76
  documentShape: string;
49
77
  validationCallback: typeof validationCallback;
78
+ rules: Rule[];
50
79
  }
51
80
 
52
81
  export const documentValidationPlugin = (
@@ -61,6 +90,7 @@ export const documentValidationPlugin = (
61
90
  documentShape: options.documentShape,
62
91
  propertiesWithoutErrors: [],
63
92
  propertiesWithErrors: [],
93
+ rules: options.rules,
64
94
  };
65
95
  },
66
96
  apply(tr, state) {
@@ -107,31 +137,30 @@ async function validationCallback(view: EditorView, documentHtml: string) {
107
137
  ...shacl.match(undefined, propertyPred, undefined),
108
138
  ].map((quad: Quad) => quad.object);
109
139
 
110
- const propertiesWithErrors: {
111
- sourceShape: BlankNode | NamedNode<string>;
112
- focusNode: BlankNode | NamedNode<string> | null;
113
- }[] = [];
114
- for (const r of report.results) {
115
- const sourceShape = r.sourceShape;
116
- if (sourceShape)
117
- propertiesWithErrors.push({ sourceShape, focusNode: r.focusNode });
118
- }
119
140
  const errorMessagePred = sayFactory.namedNode(
120
141
  'http://www.w3.org/ns/shacl#resultMessage',
121
142
  );
122
- const propertiesWithErrorsMessages = propertiesWithErrors
123
- .map(({ sourceShape, focusNode }) => {
143
+ const propertiesWithErrors: PropertyWithError[] = [];
144
+ for (const r of report.results) {
145
+ const sourceShape = r.sourceShape;
146
+ if (sourceShape) {
124
147
  const match = shacl.match(sourceShape, errorMessagePred, undefined);
125
148
  const message = [...match][0]?.object.value;
126
- return message
127
- ? { message: removeQuotes(message), subject: focusNode?.value }
128
- : undefined;
129
- })
130
- .filter((message) => message);
149
+ if (message) {
150
+ propertiesWithErrors.push({
151
+ message: removeQuotes(message),
152
+ subject: r.focusNode?.value,
153
+ shape: sourceShape.value,
154
+ constraint: r.sourceConstraintComponent?.value as string,
155
+ });
156
+ }
157
+ }
158
+ }
159
+
131
160
  const propertiesWithoutErrorsArray = propertyNodes.filter((propertyNode) =>
132
- propertiesWithErrors.some((propertyWithError) => {
133
- return propertyWithError.sourceShape.value !== propertyNode.value;
134
- }),
161
+ propertiesWithErrors.every(
162
+ (propertyWithError) => propertyWithError.shape !== propertyNode.value,
163
+ ),
135
164
  );
136
165
 
137
166
  const successMessagePred = sayFactory.namedNode(
@@ -149,7 +178,7 @@ async function validationCallback(view: EditorView, documentHtml: string) {
149
178
  type: 'setNewReport',
150
179
  report,
151
180
  propertiesWithoutErrors,
152
- propertiesWithErrors: propertiesWithErrorsMessages,
181
+ propertiesWithErrors: propertiesWithErrors,
153
182
  });
154
183
  transaction.setMeta('addToHistory', false);
155
184
  view.dispatch(transaction);
@@ -92,8 +92,15 @@ async function _queryMobilityMeasures<Count extends boolean>(
92
92
  : /* sparql */ `SELECT DISTINCT ?uri ?label ?preview ?zonality ?variableSignage`;
93
93
 
94
94
  const filterStatement = _buildFilters(options).join('\n');
95
+ const orderBindings = !count
96
+ ? `
97
+ BIND(REPLACE(?label, "^(\\\\D+).*", "$1", "i") AS ?firstLetters)
98
+ BIND(xsd:decimal(REPLACE(?label, "^\\\\D+(\\\\d*\\\\.?\\\\d*).*", "$1", "i")) AS ?number)
99
+ BIND(REPLACE(?label, "^\\\\D+\\\\d*\\\\.?\\\\d*(.*)", "$1", "i") AS ?secondLetters)
100
+ `
101
+ : '';
95
102
  const orderByStatement = !count
96
- ? /* sparql */ `ORDER BY ASC(strlen(str(?label))) ASC(?label)`
103
+ ? /* sparql */ `ORDER BY ASC(UCASE(?firstLetters)) ASC(?number) ASC(LCASE(?secondLetters))`
97
104
  : '';
98
105
  const paginationStatement = !count
99
106
  ? /* sparql */ `LIMIT ${pageSize} OFFSET ${page * pageSize}`
@@ -126,6 +133,7 @@ async function _queryMobilityMeasures<Count extends boolean>(
126
133
  ?signUri dct:type ?signClassification.
127
134
  }
128
135
  ${filterStatement}
136
+ ${orderBindings}
129
137
  }
130
138
  ${orderByStatement}
131
139
  ${paginationStatement}
@@ -7,6 +7,16 @@ import { hasOutgoingNamedNodeTriple } from '@lblod/ember-rdfa-editor-lblod-plugi
7
7
  import { ElementPNode } from '@lblod/ember-rdfa-editor/plugins/datastore';
8
8
  import { findAncestors } from '@lblod/ember-rdfa-editor/utils/position-utils';
9
9
 
10
+ export function getDecisionNodeLocation(controller: SayController) {
11
+ const besluitRange = getCurrentBesluitRange(controller);
12
+ if (!besluitRange) return;
13
+ const decisionNodeLocation = {
14
+ pos: besluitRange.from,
15
+ node: besluitRange.node,
16
+ };
17
+ return decisionNodeLocation;
18
+ }
19
+
10
20
  export const getCurrentBesluitRange = (
11
21
  controllerOrState: SayController | EditorState,
12
22
  ): ElementPNode | undefined => {
@@ -15,15 +25,29 @@ export const getCurrentBesluitRange = (
15
25
  ? controllerOrState.mainEditorState
16
26
  : controllerOrState;
17
27
  const selection = state.selection;
18
-
19
- const besluit =
20
- findAncestors(selection.$from, (node: PNode) => {
21
- return hasOutgoingNamedNodeTriple(
22
- node.attrs,
23
- RDF('type'),
24
- BESLUIT('Besluit'),
25
- );
26
- })[0] ?? null;
28
+ let besluit;
29
+ if (
30
+ selection.$from.nodeAfter &&
31
+ hasOutgoingNamedNodeTriple(
32
+ selection.$from.nodeAfter.attrs,
33
+ RDF('type'),
34
+ BESLUIT('Besluit'),
35
+ )
36
+ ) {
37
+ besluit = {
38
+ node: selection.$from.nodeAfter,
39
+ pos: selection.$from.pos,
40
+ };
41
+ } else {
42
+ besluit =
43
+ findAncestors(selection.$from, (node: PNode) => {
44
+ return hasOutgoingNamedNodeTriple(
45
+ node.attrs,
46
+ RDF('type'),
47
+ BESLUIT('Besluit'),
48
+ );
49
+ })[0] ?? null;
50
+ }
27
51
  if (!besluit) {
28
52
  return undefined;
29
53
  }
@@ -1,5 +1,6 @@
1
1
  import Component from '@glimmer/component';
2
2
  import { SayController } from '@lblod/ember-rdfa-editor';
3
+ import ValidationReport from 'rdf-validate-shacl/src/validation-report';
3
4
  interface Sig {
4
5
  Args: {
5
6
  controller: SayController;
@@ -9,14 +10,63 @@ export default class DocumentValidationPluginCard extends Component<Sig> {
9
10
  get controller(): SayController;
10
11
  get validationState(): import("@lblod/ember-rdfa-editor-lblod-plugins/plugins/document-validation-plugin").DocumentValidationPluginState | undefined;
11
12
  get documentValidationErrors(): ({
13
+ rule: {
14
+ action: (controller: SayController, report: ValidationReport) => void;
15
+ buttonTitle: string;
16
+ } | {
17
+ helpText: string;
18
+ };
12
19
  message: string;
13
20
  subject: string | undefined;
14
- } | undefined)[] | undefined;
21
+ shape: string;
22
+ constraint: string;
23
+ } | {
24
+ rule: {
25
+ shaclRule: string;
26
+ action: (controller: SayController, report: ValidationReport) => void;
27
+ buttonTitle: string;
28
+ } | {
29
+ shaclRule: string;
30
+ helpText: string;
31
+ } | undefined;
32
+ message: string;
33
+ subject: string | undefined;
34
+ shape: string;
35
+ constraint: string;
36
+ })[] | undefined;
15
37
  get propertiesWithoutErrors(): {
16
38
  message: string;
17
39
  }[];
18
40
  goToSubject: (subject: string) => void;
19
41
  get status(): "not-run" | "no-matches" | "valid" | "invalid";
20
42
  get isSuccesslike(): boolean;
43
+ doActionAndTriggerValidation: (action: (controller: SayController, report: ValidationReport) => void) => Promise<void>;
44
+ oldVal: typeof this.documentValidationErrors;
45
+ dedupeDocumentValidationErrors: (val: typeof this.documentValidationErrors) => ({
46
+ rule: {
47
+ action: (controller: SayController, report: ValidationReport) => void;
48
+ buttonTitle: string;
49
+ } | {
50
+ helpText: string;
51
+ };
52
+ message: string;
53
+ subject: string | undefined;
54
+ shape: string;
55
+ constraint: string;
56
+ } | {
57
+ rule: {
58
+ shaclRule: string;
59
+ action: (controller: SayController, report: ValidationReport) => void;
60
+ buttonTitle: string;
61
+ } | {
62
+ shaclRule: string;
63
+ helpText: string;
64
+ } | undefined;
65
+ message: string;
66
+ subject: string | undefined;
67
+ shape: string;
68
+ constraint: string;
69
+ })[] | undefined;
70
+ compareDocumentValidationErrors: (val1: typeof this.documentValidationErrors, val2: typeof this.documentValidationErrors) => boolean;
21
71
  }
22
72
  export {};
@@ -1,9 +1,11 @@
1
1
  import { Command } from '@lblod/ember-rdfa-editor';
2
2
  import IntlService from 'ember-intl/services/intl';
3
+ import { NodeWithPos } from '@curvenote/prosemirror-utils';
3
4
  interface InsertArticleContainerArgs {
4
5
  intl: IntlService;
5
6
  decisionUri: string;
6
7
  articleUriGenerator?: () => string;
8
+ decisionLocation: NodeWithPos;
7
9
  }
8
- export default function insertArticleContainer({ decisionUri, articleUriGenerator, }: InsertArticleContainerArgs): Command;
10
+ export default function insertArticleContainer({ decisionUri, articleUriGenerator, decisionLocation, }: InsertArticleContainerArgs): Command;
9
11
  export {};
@@ -0,0 +1,6 @@
1
+ import { SayController } from '@lblod/ember-rdfa-editor';
2
+ import IntlService from 'ember-intl/services/intl';
3
+ export declare function insertTitleAtCursor(controller: SayController, intl: IntlService): void;
4
+ export declare function insertDescriptionAtCursor(controller: SayController, intl: IntlService): void;
5
+ export declare function insertMotivationAtCursor(controller: SayController, intl: IntlService): void;
6
+ export declare function insertArticleContainerAtCursor(controller: SayController, intl: IntlService, articleUriGenerator?: () => string): void;
@@ -1,20 +1,43 @@
1
- import { ProsePlugin, PluginKey, EditorView } from '@lblod/ember-rdfa-editor';
1
+ import { ProsePlugin, PluginKey, EditorView, SayController } from '@lblod/ember-rdfa-editor';
2
2
  import { DataFactory, DatasetCore, DatasetCoreFactory, Quad } from '@rdfjs/types';
3
3
  import ValidationReport from 'rdf-validate-shacl/src/validation-report';
4
4
  export declare const documentValidationPluginKey: PluginKey<DocumentValidationPluginState>;
5
- interface DocumentValidationPluginArgs {
5
+ type Violation = {
6
+ action: (controller: SayController, report: ValidationReport) => void;
7
+ buttonTitle: string;
8
+ } | {
9
+ helpText: string;
10
+ };
11
+ type Rule = {
12
+ shaclRule: string;
13
+ violations: {
14
+ [key: string]: Violation;
15
+ };
16
+ } | {
17
+ shaclRule: string;
18
+ action: (controller: SayController, report: ValidationReport) => void;
19
+ buttonTitle: string;
20
+ } | {
21
+ shaclRule: string;
22
+ helpText: string;
23
+ };
24
+ export interface DocumentValidationPluginArgs {
6
25
  documentShape: string;
26
+ rules: Rule[];
7
27
  }
8
28
  export type ShaclValidationReport = ValidationReport.ValidationReport<DataFactory<Quad, Quad> & DatasetCoreFactory<Quad, Quad, DatasetCore<Quad, Quad>>>;
29
+ type PropertyWithError = {
30
+ message: string;
31
+ subject: string | undefined;
32
+ shape: string;
33
+ constraint: string;
34
+ };
9
35
  interface DocumentValidationResult {
10
36
  report?: ValidationReport;
11
37
  propertiesWithoutErrors: {
12
38
  message: string;
13
39
  }[];
14
- propertiesWithErrors: ({
15
- message: string;
16
- subject: string | undefined;
17
- } | undefined)[];
40
+ propertiesWithErrors: PropertyWithError[];
18
41
  }
19
42
  export interface DocumentValidationTransactionMeta extends DocumentValidationResult {
20
43
  type: string;
@@ -22,6 +45,7 @@ export interface DocumentValidationTransactionMeta extends DocumentValidationRes
22
45
  export interface DocumentValidationPluginState extends DocumentValidationResult {
23
46
  documentShape: string;
24
47
  validationCallback: typeof validationCallback;
48
+ rules: Rule[];
25
49
  }
26
50
  export declare const documentValidationPlugin: (options: DocumentValidationPluginArgs) => ProsePlugin<DocumentValidationPluginState>;
27
51
  declare function validationCallback(view: EditorView, documentHtml: string): Promise<void>;
@@ -1,4 +1,8 @@
1
- import { EditorState, SayController } from '@lblod/ember-rdfa-editor';
1
+ import { EditorState, PNode, SayController } from '@lblod/ember-rdfa-editor';
2
2
  import { ElementPNode } from '@lblod/ember-rdfa-editor/plugins/datastore';
3
+ export declare function getDecisionNodeLocation(controller: SayController): {
4
+ pos: number;
5
+ node: PNode;
6
+ } | undefined;
3
7
  export declare const getCurrentBesluitRange: (controllerOrState: SayController | EditorState) => ElementPNode | undefined;
4
8
  export declare const getCurrentBesluitURI: (controllerOrState: SayController | EditorState) => string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lblod/ember-rdfa-editor-lblod-plugins",
3
- "version": "33.0.0",
3
+ "version": "33.1.0",
4
4
  "description": "Ember addon providing lblod specific plugins for the ember-rdfa-editor",
5
5
  "keywords": [
6
6
  "ember-addon",
@@ -75,7 +75,7 @@
75
75
  "ember-resources": "^7.0.2",
76
76
  "ember-template-imports": "^4.3.0",
77
77
  "ember-velcro": "^2.2.0",
78
- "@lblod/lib-decision-shapes": "^0.0.22",
78
+ "@lblod/lib-decision-shapes": "^0.0.25",
79
79
  "n2words": "^1.21.0",
80
80
  "n3": "^1.26.0",
81
81
  "process": "0.11.10",
package/pnpm-lock.yaml CHANGED
@@ -30,8 +30,8 @@ importers:
30
30
  specifier: ^1.16.5
31
31
  version: 1.19.2(@glint/template@1.5.1)
32
32
  '@lblod/lib-decision-shapes':
33
- specifier: ^0.0.22
34
- version: 0.0.22(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.14.9)(node-notifier@10.0.1))(typescript@5.7.3)
33
+ specifier: ^0.0.25
34
+ version: 0.0.25(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.14.9)(node-notifier@10.0.1))(typescript@5.7.3)
35
35
  '@lblod/marawa':
36
36
  specifier: 0.8.0-beta.6
37
37
  version: 0.8.0-beta.6
@@ -1753,8 +1753,8 @@ packages:
1753
1753
  '@glint/template':
1754
1754
  optional: true
1755
1755
 
1756
- '@lblod/lib-decision-shapes@0.0.22':
1757
- resolution: {integrity: sha512-8jNbj26lp75j1fEdx1PvFDZsEpw2a17unrmzYY4rRTfDqGbVnQwYyJLxfhBEudnMzYmjMXN+lDL00p/lavIW/Q==}
1756
+ '@lblod/lib-decision-shapes@0.0.25':
1757
+ resolution: {integrity: sha512-4pTmFBro+RLPtaN4SiWt5EYqZ2bbtDqpo+EYsNEDe1wtiMLg6RhOD4RwQwLQrH3mjbuJCyQd3DF3TaZBDcOiSQ==}
1758
1758
 
1759
1759
  '@lblod/marawa@0.8.0-beta.6':
1760
1760
  resolution: {integrity: sha512-BW3yCpeQlk9EPnQAEQnM9uViA8RC5DkuoiaPJzbuhMcLvKHfI4cjc6dTg9i8qAijbL/xObmQ/Aexko2Xcj9ktw==}
@@ -12152,7 +12152,7 @@ snapshots:
12152
12152
  - socks
12153
12153
  - supports-color
12154
12154
 
12155
- '@lblod/lib-decision-shapes@0.0.22(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.14.9)(node-notifier@10.0.1))(typescript@5.7.3)':
12155
+ '@lblod/lib-decision-shapes@0.0.25(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.14.9)(node-notifier@10.0.1))(typescript@5.7.3)':
12156
12156
  dependencies:
12157
12157
  ts-jest: 29.3.4(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.14.9)(node-notifier@10.0.1))(typescript@5.7.3)
12158
12158
  optionalDependencies:
@@ -515,3 +515,4 @@ document-validation-plugin:
515
515
  no-matching-rules: There is nothing to validate in the document
516
516
  see-related-node: See related node
517
517
  description: Some parts are required to allow our system to parse this document.
518
+ decision-node-not-found: Please put the cursor inside a decision node
@@ -512,3 +512,4 @@ document-validation-plugin:
512
512
  no-matching-rules: Er is niets te valideren in dit document
513
513
  see-related-node: Zie gerelateerde node
514
514
  description: Sommige onderdelen zijn nodig om ons systeem toe te laten dit document te ontleden.
515
+ decision-node-not-found: Plaats de cursor in een besluit