@lblod/ember-rdfa-editor-lblod-plugins 33.0.0 → 33.1.0-dev.367f7322beb7deee231dd44356f445bd1d6515fd

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 (39) hide show
  1. package/.changeset/eleven-jeans-yell.md +5 -0
  2. package/CHANGELOG.md +10 -0
  3. package/addon/components/besluit-topic-plugin/besluit-topic-toolbar-dropdown.ts +1 -1
  4. package/addon/components/besluit-type-plugin/toolbar-dropdown.gts +1 -1
  5. package/addon/components/decision-plugin/decision-plugin-card.gts +3 -2
  6. package/addon/components/decision-plugin/insert-article.gts +1 -1
  7. package/addon/components/document-validation-plugin/card.gts +130 -33
  8. package/addon/components/lpdc-plugin/lpdc-insert.ts +1 -1
  9. package/addon/components/roadsign-regulation-plugin/roadsign-regulation-card.gts +1 -1
  10. package/addon/components/roadsign-regulation-plugin/roadsigns-modal.gts +1 -1
  11. package/addon/plugins/besluit-type-plugin/utils/besluit-type-instances.ts +1 -1
  12. package/addon/plugins/besluit-type-plugin/utils/set-besluit-type.ts +1 -1
  13. package/addon/plugins/decision-plugin/commands/insert-article-container.ts +14 -1
  14. package/addon/plugins/decision-plugin/commands/insert-description.ts +9 -1
  15. package/addon/plugins/decision-plugin/commands/insert-motivation.ts +9 -1
  16. package/addon/plugins/decision-plugin/commands/insert-title.ts +9 -1
  17. package/addon/plugins/document-validation-plugin/common-fixes.ts +125 -0
  18. package/addon/plugins/document-validation-plugin/index.ts +61 -32
  19. package/addon/plugins/roadsign-regulation-plugin/actions/insert-measure.ts +69 -18
  20. package/addon/plugins/roadsign-regulation-plugin/queries/mobility-measure-concept.ts +9 -1
  21. package/addon/plugins/roadsign-regulation-plugin/schemas/mobility-measure-preview.ts +22 -0
  22. package/addon/plugins/roadsign-regulation-plugin/schemas/traffic-signal.ts +18 -0
  23. package/addon/plugins/roadsign-regulation-plugin/schemas/variable-instance.ts +55 -0
  24. package/addon/plugins/roadsign-regulation-plugin/schemas/variable.ts +6 -6
  25. package/addon/{plugins/besluit-topic-plugin/utils/helpers.ts → utils/decision-utils.ts} +33 -9
  26. package/declarations/addon/components/document-validation-plugin/card.d.ts +51 -1
  27. package/declarations/addon/plugins/decision-plugin/commands/insert-article-container.d.ts +3 -1
  28. package/declarations/addon/plugins/document-validation-plugin/common-fixes.d.ts +6 -0
  29. package/declarations/addon/plugins/document-validation-plugin/index.d.ts +30 -6
  30. package/declarations/addon/plugins/roadsign-regulation-plugin/actions/insert-measure.d.ts +10 -5
  31. package/declarations/addon/plugins/roadsign-regulation-plugin/schemas/mobility-measure-preview.d.ts +279 -0
  32. package/declarations/addon/plugins/roadsign-regulation-plugin/schemas/traffic-signal.d.ts +90 -0
  33. package/declarations/addon/plugins/roadsign-regulation-plugin/schemas/variable-instance.d.ts +246 -0
  34. package/declarations/addon/plugins/roadsign-regulation-plugin/schemas/variable.d.ts +120 -0
  35. package/declarations/addon/{plugins/besluit-topic-plugin/utils/helpers.d.ts → utils/decision-utils.d.ts} +5 -1
  36. package/package.json +2 -2
  37. package/pnpm-lock.yaml +5 -5
  38. package/translations/en-US.yaml +1 -0
  39. package/translations/nl-BE.yaml +1 -0
@@ -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);
@@ -38,30 +38,50 @@ import { createNumberVariable } from '../../variable-plugin/actions/create-numbe
38
38
  import { createDateVariable } from '../../variable-plugin/actions/create-date-variable';
39
39
  import { createCodelistVariable } from '../../variable-plugin/actions/create-codelist-variable';
40
40
  import { createClassicLocationVariable } from '../../variable-plugin/actions/create-classic-location-variable';
41
+ import { isTrafficSignal, TrafficSignal } from '../schemas/traffic-signal';
42
+ import { MobilityMeasurePreview } from '../schemas/mobility-measure-preview';
43
+ import {
44
+ isVariableInstance,
45
+ VariableInstance,
46
+ } from '../schemas/variable-instance';
41
47
 
42
- interface InsertMeasureArgs {
43
- measureConcept: MobilityMeasureConcept;
48
+ type InsertMeasureArgs = {
44
49
  zonality: ZonalOrNot;
45
50
  temporal: boolean;
46
- variables: Record<string, Exclude<Variable, { type: 'instruction' }>>;
51
+ variables: Record<
52
+ string,
53
+ Exclude<Variable, { type: 'instruction' }> | VariableInstance
54
+ >;
47
55
  templateString: string;
48
56
  decisionUri: string;
49
57
  articleUriGenerator?: () => string;
50
- }
58
+ } & (
59
+ | {
60
+ measureConcept: MobilityMeasureConcept;
61
+ }
62
+ | {
63
+ measurePreview: MobilityMeasurePreview;
64
+ }
65
+ );
51
66
 
52
67
  export default function insertMeasure({
53
- measureConcept,
54
68
  zonality,
55
69
  temporal,
56
70
  variables,
57
71
  templateString,
58
72
  articleUriGenerator,
59
73
  decisionUri,
74
+ ...args
60
75
  }: InsertMeasureArgs): TransactionMonad<boolean> {
61
76
  return function (state: EditorState) {
77
+ const measureConcept =
78
+ 'measureConcept' in args
79
+ ? args.measureConcept
80
+ : args.measurePreview.measureConcept;
81
+ const measurePreview = 'measurePreview' in args && args.measurePreview;
62
82
  const { schema } = state;
63
83
  const signNodes = measureConcept.trafficSignalConcepts.map((signConcept) =>
64
- constructSignNode(signConcept, schema, zonality),
84
+ constructSignalNode(signConcept, schema, zonality),
65
85
  );
66
86
  let signSection: PNode[] = [];
67
87
  if (signNodes.length) {
@@ -117,6 +137,14 @@ export default function insertMeasure({
117
137
  predicate: DCT('description').full,
118
138
  object: sayDataFactory.contentLiteral('nl-BE'),
119
139
  },
140
+ ...(measurePreview
141
+ ? [
142
+ {
143
+ predicate: MOBILITEIT('isGebaseerdOpMaatregelOntwerp'),
144
+ object: sayDataFactory.namedNode(measurePreview.uri),
145
+ },
146
+ ]
147
+ : []),
120
148
  // TODO there are some properties that are missing from the measure that we should define if we can:
121
149
  // locn:address, mobiliteit:contactorganisatie, mobiliteit:doelgroep, adms:identifier,
122
150
  // mobiliteit:periode, mobiliteit:plaatsbepaling, schema:eventSchedule, mobiliteit:type,
@@ -172,7 +200,10 @@ export default function insertMeasure({
172
200
 
173
201
  function constructMeasureBody(
174
202
  templateString: string,
175
- variables: Record<string, Exclude<Variable, { type: 'instruction' }>>,
203
+ variables: Record<
204
+ string,
205
+ Exclude<Variable, { type: 'instruction' }> | VariableInstance
206
+ >,
176
207
  schema: Schema,
177
208
  ) {
178
209
  const parts = templateString.split(/(\$\{[^{}$]+\})/);
@@ -216,13 +247,18 @@ function determineSignLabel(signConcept: TrafficSignalConcept) {
216
247
  }
217
248
  }
218
249
 
219
- function constructSignNode(
220
- signConcept: TrafficSignalConcept,
250
+ function constructSignalNode(
251
+ signalOrSignalConcept: TrafficSignal | TrafficSignalConcept,
221
252
  schema: Schema,
222
253
  zonality?: ZonalOrNot,
223
254
  ) {
224
- const signUri = `http://data.lblod.info/verkeerstekens/${uuid()}`;
225
- const prefix = determineSignLabel(signConcept);
255
+ const signalConcept = isTrafficSignal(signalOrSignalConcept)
256
+ ? signalOrSignalConcept.trafficSignalConcept
257
+ : signalOrSignalConcept;
258
+ const signalUri = isTrafficSignal(signalOrSignalConcept)
259
+ ? signalOrSignalConcept.uri
260
+ : `http://data.lblod.info/verkeerstekens/${uuid()}`;
261
+ const prefix = determineSignLabel(signalConcept);
226
262
  const zonalityText =
227
263
  !zonality || zonality !== ZONALITY_OPTIONS.ZONAL
228
264
  ? ''
@@ -232,7 +268,7 @@ function constructSignNode(
232
268
  const node = schema.nodes.inline_rdfa.create(
233
269
  {
234
270
  rdfaNodeType: 'resource',
235
- subject: signUri,
271
+ subject: signalUri,
236
272
  __rdfaId: uuid(),
237
273
  properties: [
238
274
  {
@@ -242,33 +278,48 @@ function constructSignNode(
242
278
  {
243
279
  predicate: RDF('type').full,
244
280
  object: sayDataFactory.namedNode(
245
- TRAFFIC_SIGNAL_TYPE_MAPPING[signConcept.type],
281
+ TRAFFIC_SIGNAL_TYPE_MAPPING[signalConcept.type],
246
282
  ),
247
283
  },
248
284
  {
249
285
  predicate: PROV('wasDerivedFrom').full,
250
- object: sayDataFactory.namedNode(signConcept.uri),
286
+ object: sayDataFactory.namedNode(signalOrSignalConcept.uri),
251
287
  },
252
288
  // TODO should include extra Verkeersteken properties? mobiliteit:heeftOnderbord,
253
289
  // mobiliteit:isBeginZone, mobiliteit:isEindeZone?
254
290
  ],
255
291
  },
256
292
  schema.text(
257
- `${prefix} ${signConcept.regulatoryNotation || signConcept.code}${zonalityText}`,
293
+ `${prefix} ${signalConcept.regulatoryNotation || signalConcept.code}${zonalityText}`,
258
294
  ),
259
295
  );
260
296
  return node;
261
297
  }
262
298
 
263
299
  function constructVariableNode(
264
- variable: Exclude<Variable, { type: 'instruction' }>,
300
+ variableOrVariableInstance:
301
+ | Exclude<Variable, { type: 'instruction' }>
302
+ | VariableInstance,
265
303
  schema: Schema,
266
304
  ) {
267
- const variableInstance = generateVariableInstanceUri();
305
+ const variable = isVariableInstance(variableOrVariableInstance)
306
+ ? variableOrVariableInstance.variable
307
+ : variableOrVariableInstance;
308
+ const variableInstance = isVariableInstance(variableOrVariableInstance)
309
+ ? variableOrVariableInstance
310
+ : {
311
+ uri: generateVariableInstanceUri(),
312
+ value: undefined,
313
+ };
314
+ const valueStr =
315
+ variableInstance.value instanceof Date
316
+ ? variableInstance.value.toISOString()
317
+ : variableInstance.value?.toString();
268
318
  const args = {
269
319
  schema,
270
320
  variable: variable.uri,
271
- variableInstance,
321
+ variableInstance: variableInstance.uri,
322
+ value: valueStr,
272
323
  label: variable.label,
273
324
  };
274
325
  switch (variable.type) {
@@ -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}
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ import { TrafficSignalSchema } from './traffic-signal';
3
+ import {
4
+ type MobilityMeasureConcept,
5
+ MobilityMeasureConceptSchema,
6
+ } from './mobility-measure-concept';
7
+
8
+ export const MobilityMeasurePreviewSchema = z.object({
9
+ uri: z.string(),
10
+ trafficSignals: z.array(TrafficSignalSchema),
11
+ measureConcept: MobilityMeasureConceptSchema,
12
+ });
13
+
14
+ export type MobilityMeasurePreview = z.infer<
15
+ typeof MobilityMeasurePreviewSchema
16
+ >;
17
+
18
+ export function isMobilityMeasurePreview(
19
+ conceptOrPreview: MobilityMeasureConcept | MobilityMeasurePreview,
20
+ ): conceptOrPreview is MobilityMeasurePreview {
21
+ return 'measureConcept' in conceptOrPreview;
22
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ TrafficSignalConcept,
4
+ TrafficSignalConceptSchema,
5
+ } from './traffic-signal-concept';
6
+
7
+ export const TrafficSignalSchema = z.object({
8
+ uri: z.string(),
9
+ trafficSignalConcept: TrafficSignalConceptSchema,
10
+ });
11
+
12
+ export type TrafficSignal = z.infer<typeof TrafficSignalSchema>;
13
+
14
+ export function isTrafficSignal(
15
+ signalOrSignalConcept: TrafficSignal | TrafficSignalConcept,
16
+ ): signalOrSignalConcept is TrafficSignal {
17
+ return 'trafficSignalConcept' in signalOrSignalConcept;
18
+ }
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ CodelistVariableSchema,
4
+ DateVariableSchema,
5
+ LocationVariableSchema,
6
+ NumberVariableSchema,
7
+ TextVariableSchema,
8
+ Variable,
9
+ } from './variable';
10
+
11
+ const BaseVariableInstanceSchema = z.object({
12
+ uri: z.string(),
13
+ id: z.string(),
14
+ });
15
+
16
+ const TextVariableInstanceSchema = BaseVariableInstanceSchema.extend({
17
+ value: z.string().optional(),
18
+ variable: TextVariableSchema,
19
+ });
20
+
21
+ const NumberVariableInstanceSchema = BaseVariableInstanceSchema.extend({
22
+ value: z.number({ coerce: true }).optional(),
23
+ variable: NumberVariableSchema,
24
+ });
25
+
26
+ const DateVariableInstanceSchema = BaseVariableInstanceSchema.extend({
27
+ value: z.date({ coerce: true }).optional(),
28
+ variable: DateVariableSchema,
29
+ });
30
+
31
+ const LocationVariableInstanceSchema = BaseVariableInstanceSchema.extend({
32
+ value: z.string().optional(),
33
+ variable: LocationVariableSchema,
34
+ });
35
+
36
+ const CodelistVariableInstanceSchema = BaseVariableInstanceSchema.extend({
37
+ value: z.string().optional(),
38
+ variable: CodelistVariableSchema,
39
+ });
40
+
41
+ const VariableInstanceSchema = z.union([
42
+ TextVariableInstanceSchema,
43
+ NumberVariableInstanceSchema,
44
+ DateVariableInstanceSchema,
45
+ LocationVariableInstanceSchema,
46
+ CodelistVariableInstanceSchema,
47
+ ]);
48
+
49
+ export type VariableInstance = z.infer<typeof VariableInstanceSchema>;
50
+
51
+ export function isVariableInstance(
52
+ variableOrVariableInstance: Variable | VariableInstance,
53
+ ): variableOrVariableInstance is VariableInstance {
54
+ return 'variable' in variableOrVariableInstance;
55
+ }
@@ -5,33 +5,33 @@ const BaseVariableSchema = z.object({
5
5
  label: z.string(),
6
6
  source: z.string().optional(),
7
7
  });
8
- const TextVariableSchema = BaseVariableSchema.extend({
8
+ export const TextVariableSchema = BaseVariableSchema.extend({
9
9
  type: z.literal('text'),
10
10
  defaultValue: z.string().optional(),
11
11
  });
12
12
 
13
- const NumberVariableSchema = BaseVariableSchema.extend({
13
+ export const NumberVariableSchema = BaseVariableSchema.extend({
14
14
  type: z.literal('number'),
15
15
  defaultValue: z.number({ coerce: true }).optional(),
16
16
  });
17
17
 
18
- const DateVariableSchema = BaseVariableSchema.extend({
18
+ export const DateVariableSchema = BaseVariableSchema.extend({
19
19
  type: z.literal('date'),
20
20
  defaultValue: z.date({ coerce: true }).optional(),
21
21
  });
22
22
 
23
- const CodelistVariableSchema = BaseVariableSchema.extend({
23
+ export const CodelistVariableSchema = BaseVariableSchema.extend({
24
24
  type: z.literal('codelist'),
25
25
  defaultValue: z.string().optional(),
26
26
  codelistUri: z.string(),
27
27
  });
28
28
 
29
- const LocationVariableSchema = BaseVariableSchema.extend({
29
+ export const LocationVariableSchema = BaseVariableSchema.extend({
30
30
  type: z.literal('location'),
31
31
  defaultValue: z.string().optional(),
32
32
  });
33
33
 
34
- const InstructionVariableSchema = BaseVariableSchema.extend({
34
+ export const InstructionVariableSchema = BaseVariableSchema.extend({
35
35
  type: z.literal('instruction'),
36
36
  });
37
37
 
@@ -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;