@lblod/ember-rdfa-editor-lblod-plugins 32.0.0 → 32.1.0-dev.2e9fd650499765c0030b9262f90aa975fdfd7a2f

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 (43) hide show
  1. package/.changeset/empty-donkeys-change.md +5 -0
  2. package/.changeset/tidy-dancers-grow.md +5 -0
  3. package/.changeset/wicked-tools-serve.md +5 -0
  4. package/CHANGELOG.md +11 -0
  5. package/addon/components/citation-plugin/citations/search-modal.hbs +2 -2
  6. package/addon/components/document-validation-plugin/card.gts +131 -0
  7. package/addon/components/roadsign-regulation-plugin/roadsigns-table.gts +5 -2
  8. package/addon/components/snippet-plugin/nodes/snippet.gts +1 -1
  9. package/addon/components/structure-plugin/_private/structure.gts +1 -3
  10. package/addon/components/template-comments-plugin/template-comment.hbs +1 -3
  11. package/addon/components/worship-plugin/administrative-unit-picker.hbs +3 -3
  12. package/addon/components/worship-plugin/search-modal.hbs +4 -4
  13. package/addon/plugins/document-validation-plugin/index.ts +150 -0
  14. package/addon/plugins/roadsign-regulation-plugin/actions/insert-measure.ts +22 -2
  15. package/addon/plugins/roadsign-regulation-plugin/constants.ts +22 -6
  16. package/addon/plugins/roadsign-regulation-plugin/queries/road-sign-category.ts +6 -1
  17. package/addon/plugins/roadsign-regulation-plugin/queries/sign-concept.ts +19 -13
  18. package/addon/plugins/roadsign-regulation-plugin/schemas/sign-concept.ts +22 -8
  19. package/addon/plugins/snippet-plugin/nodes/snippet.ts +1 -0
  20. package/addon/plugins/structure-plugin/node.ts +1 -0
  21. package/addon/plugins/template-comments-plugin/node.ts +1 -0
  22. package/addon/plugins/variable-plugin/variables/date.ts +1 -0
  23. package/addon/utils/remove-quotes.ts +7 -0
  24. package/app/components/document-validation-plugin/card.js +1 -0
  25. package/app/styles/document-validation.scss +24 -0
  26. package/app/styles/snippet-plugin.scss +7 -1
  27. package/app/styles/structure-plugin.scss +2 -1
  28. package/app/styles/template-comments-plugin.scss +1 -1
  29. package/declarations/addon/components/document-validation-plugin/card.d.ts +18 -0
  30. package/declarations/addon/components/roadsign-regulation-plugin/roadsigns-modal.d.ts +20 -8
  31. package/declarations/addon/plugins/document-validation-plugin/index.d.ts +14 -0
  32. package/declarations/addon/plugins/roadsign-regulation-plugin/constants.d.ts +11 -5
  33. package/declarations/addon/plugins/roadsign-regulation-plugin/queries/mobility-measure-concept.d.ts +10 -4
  34. package/declarations/addon/plugins/roadsign-regulation-plugin/queries/road-sign-category.d.ts +1 -0
  35. package/declarations/addon/plugins/roadsign-regulation-plugin/queries/sign-concept.d.ts +14 -4
  36. package/declarations/addon/plugins/roadsign-regulation-plugin/schemas/mobility-measure-concept.d.ts +52 -21
  37. package/declarations/addon/plugins/roadsign-regulation-plugin/schemas/sign-concept.d.ts +32 -13
  38. package/declarations/addon/utils/remove-quotes.d.ts +1 -0
  39. package/package.json +8 -4
  40. package/pnpm-lock.yaml +1530 -1166
  41. package/translations/en-US.yaml +15 -7
  42. package/translations/nl-BE.yaml +17 -9
  43. package/types/global.d.ts +4 -0
@@ -0,0 +1,5 @@
1
+ ---
2
+ '@lblod/ember-rdfa-editor-lblod-plugins': minor
3
+ ---
4
+
5
+ Add new plugin for document validation with SHACL
@@ -0,0 +1,5 @@
1
+ ---
2
+ '@lblod/ember-rdfa-editor-lblod-plugins': patch
3
+ ---
4
+
5
+ Fix missing translations in citation-plugin
@@ -0,0 +1,5 @@
1
+ ---
2
+ '@lblod/ember-rdfa-editor-lblod-plugins': patch
3
+ ---
4
+
5
+ Improve translations of worship-plugin (capitalization, add missing translations etc.)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @lblod/ember-rdfa-editor-lblod-plugins
2
2
 
3
+ ## 32.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#573](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/pull/573) [`be9e2f4`](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/commit/be9e2f476266037926f43f2729aae7e969c55652) Thanks [@elpoelma](https://github.com/elpoelma)! - Roadsign-regulation-plugin: differentiate between 'Verkeersbord' and 'Onderbord' signs
8
+
9
+ ### Patch Changes
10
+
11
+ - [#576](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/pull/576) [`c68bef3`](https://github.com/lblod/ember-rdfa-editor-lblod-plugins/commit/c68bef3a3e215e0d721894b72f8e0754b9988ca9) Thanks [@elpoelma](https://github.com/elpoelma)! - Slightly readjust styling of `snippet` and `structure` nodes, to ensure better cursor behaviour in firefox.
12
+ Note: for the better cursor behaviour in firefox to work as expected, `@lblod/ember-rdfa-editor` version [12.8.0](https://github.com/lblod/ember-rdfa-editor/releases/tag/%40lblod%2Fember-rdfa-editor%4012.8.0) or higher needs to be installed.
13
+
3
14
  ## 32.0.0
4
15
 
5
16
  ### Major Changes
@@ -38,8 +38,8 @@
38
38
  @allowClear={{false}}
39
39
  @disabled={{false}}
40
40
  @searchEnabled={{true}}
41
- @loadingMessage={{t 'citaten-plugin.alert.loading'}}
42
- @noMatchesMessage={{t 'citaten-plugin.alert.no-results'}}
41
+ @loadingMessage={{t 'common.loading'}}
42
+ @noMatchesMessage={{t 'common.search.no-results'}}
43
43
  @searchMessage={{t 'citaten-plugin.search.placeholder'}}
44
44
  @options={{this.legislationTypes}}
45
45
  @selected={{this.legislationSelected}}
@@ -0,0 +1,131 @@
1
+ import Component from '@glimmer/component';
2
+ import { SayController } from '@lblod/ember-rdfa-editor';
3
+ import AuCard from '@appuniversum/ember-appuniversum/components/au-card';
4
+ import { documentValidationPluginKey } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/document-validation-plugin';
5
+ import AuIcon from '@appuniversum/ember-appuniversum/components/au-icon';
6
+ import { CloseFilledIcon } from '@appuniversum/ember-appuniversum/components/icons/close-filled';
7
+ import { CheckFilledIcon } from '@appuniversum/ember-appuniversum/components/icons/check-filled';
8
+ import { selectNodeBySubject } from '@lblod/ember-rdfa-editor/commands/_private/rdfa-commands/select-node-by-subject';
9
+ import { on } from '@ember/modifier';
10
+ import { fn } from '@ember/helper';
11
+ import AuButton from '@appuniversum/ember-appuniversum/components/au-button';
12
+ import { ExternalLinkIcon } from '@appuniversum/ember-appuniversum/components/icons/external-link';
13
+ import removeQuotes from '@lblod/ember-rdfa-editor-lblod-plugins/utils/remove-quotes';
14
+ import t from 'ember-intl/helpers/t';
15
+ import type { ShaclValidationReport } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/document-validation-plugin';
16
+ import { SayDataFactory } from '@lblod/ember-rdfa-editor/core/say-data-factory';
17
+
18
+ interface Sig {
19
+ Args: {
20
+ controller: SayController;
21
+ };
22
+ }
23
+
24
+ export default class DocumentValidationPluginCard extends Component<Sig> {
25
+ get documentValidationErrors() {
26
+ const { report } = documentValidationPluginKey.getState(
27
+ this.controller.mainEditorView.state,
28
+ );
29
+ if (!report) return undefined;
30
+ return shaclReportToErrorArray(report);
31
+ }
32
+ get propertiesWithoutErrors() {
33
+ const { propertiesWithoutErrors } = documentValidationPluginKey.getState(
34
+ this.controller.mainEditorView.state,
35
+ );
36
+ return propertiesWithoutErrors;
37
+ }
38
+ get controller() {
39
+ return this.args.controller;
40
+ }
41
+ goToSubject = (subject: string) => {
42
+ this.controller.doCommand(selectNodeBySubject({ subject }), {
43
+ view: this.controller.mainEditorView,
44
+ });
45
+ this.controller.focus();
46
+ };
47
+ get isValidDocument() {
48
+ return this.documentValidationErrors?.length === 0;
49
+ }
50
+ <template>
51
+ {{#if this.documentValidationErrors}}
52
+ <AuCard
53
+ @flex={{true}}
54
+ @divided={{true}}
55
+ @isOpenInitially={{true}}
56
+ @expandable={{true}}
57
+ @shadow={{true}}
58
+ @size='small'
59
+ class={{if
60
+ this.isValidDocument
61
+ 'say-document-validation__card-valid'
62
+ 'say-document-validation__card-invalid'
63
+ }}
64
+ as |c|
65
+ >
66
+ <c.header>
67
+ <p class='au-u-medium au-u-h6'>
68
+ {{#if this.isValidDocument}}
69
+ {{t 'document-validation-plugin.valid-document-title'}}
70
+ {{else}}
71
+ {{t 'document-validation-plugin.invalid-document-title'}}
72
+ {{/if}}
73
+ </p>
74
+ </c.header>
75
+ <c.content>
76
+ <p class='au-u-medium au-u-para-small'>{{t
77
+ 'document-validation-plugin.description'
78
+ }}</p>
79
+ {{#each this.propertiesWithoutErrors as |property|}}
80
+ <div class='say-document-validation__error-container'>
81
+ <AuIcon
82
+ @icon={{CheckFilledIcon}}
83
+ @size='large'
84
+ @ariaHidden={{true}}
85
+ class='say-document-validation__icon-success au-u-margin-right-small'
86
+ />
87
+ {{property.message}}
88
+ </div>
89
+ {{/each}}
90
+ {{#each this.documentValidationErrors as |error|}}
91
+ <div class='say-document-validation__error-container'>
92
+ <AuIcon
93
+ @icon={{CloseFilledIcon}}
94
+ @size='large'
95
+ @ariaHidden={{true}}
96
+ class='say-document-validation__icon-error au-u-margin-right-small'
97
+ />
98
+ {{error.message}}
99
+ <AuButton
100
+ class='au-u-padding-left-none au-u-padding-right-none'
101
+ @icon={{ExternalLinkIcon}}
102
+ @skin='link'
103
+ title={{error.subject}}
104
+ {{on 'click' (fn this.goToSubject error.subject)}}
105
+ >{{t 'document-validation-plugin.see-related-node'}}</AuButton>
106
+ </div>
107
+ {{/each}}
108
+ </c.content>
109
+
110
+ </AuCard>
111
+ {{/if}}
112
+ </template>
113
+ }
114
+
115
+ function shaclReportToErrorArray(report: ShaclValidationReport) {
116
+ let errorArray = [];
117
+ const factory = new SayDataFactory();
118
+ for (const r of report.results) {
119
+ const match = [
120
+ ...r.dataset.match(
121
+ r.sourceShape,
122
+ factory.namedNode('http://www.w3.org/ns/shacl#resultMessage'),
123
+ ),
124
+ ][0];
125
+ errorArray.push({
126
+ message: removeQuotes(match.object.value),
127
+ subject: r.focusNode?.value,
128
+ });
129
+ }
130
+ return errorArray;
131
+ }
@@ -19,6 +19,7 @@ import { eq } from 'ember-truth-helpers';
19
19
  import t from 'ember-intl/helpers/t';
20
20
  import { on } from '@ember/modifier';
21
21
  import { fn } from '@ember/helper';
22
+ import { SIGN_CONCEPT_TYPES } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/roadsign-regulation-plugin/constants';
22
23
 
23
24
  type Signature = {
24
25
  Args: {
@@ -44,8 +45,10 @@ export default class RoadSignsTable extends Component<Signature> {
44
45
  categories = (measureConcept: MobilityMeasureConcept) => {
45
46
  const categorySet: Set<string> = new Set();
46
47
  for (const signConcept of measureConcept.signConcepts) {
47
- const categories = signConcept.classifications;
48
- addAll(categorySet, ...categories);
48
+ if (signConcept.type === SIGN_CONCEPT_TYPES.ROAD_SIGN) {
49
+ const categoryLabels = signConcept.categories.map((cat) => cat.label);
50
+ addAll(categorySet, ...categoryLabels);
51
+ }
49
52
  }
50
53
  return [...categorySet].sort();
51
54
  };
@@ -226,7 +226,7 @@ export default class SnippetNode extends Component<Signature> {
226
226
  </span>
227
227
  {{this.node.attrs.title}}
228
228
  </div>
229
- <div class='say-snippet-content'>
229
+ <div class='say-snippet-body'>
230
230
  {{yield}}
231
231
  <div class='say-snippet-icons' contenteditable='false'>
232
232
  <SnippetButton
@@ -226,9 +226,7 @@ export default class Structure extends Component<Sig> {
226
226
  </Tag>
227
227
  {{/let}}
228
228
  </div>
229
- <div class='say-structure__content'>
230
- {{yield}}
231
- </div>
229
+ {{yield}}
232
230
  </div>
233
231
  </template>
234
232
  }
@@ -8,7 +8,5 @@
8
8
  {{if this.selectionInside "ProseMirror-selectednode"}}
9
9
  say-default-cursor'
10
10
  >
11
- <div class='say-text-cursor'>
12
- {{yield}}
13
- </div>
11
+ {{yield}}
14
12
  </AuAlert>
@@ -1,9 +1,9 @@
1
1
  {{! @glint-nocheck: not typesafe yet }}
2
2
  <PowerSelect
3
3
  @loadingMessage={{t 'common.search.loading'}}
4
- @searchMessage={{t 'worship-plugin.modal.search.search'}}
5
- @noMatchesMessage={{t 'worship-plugin.modal.search.no-results'}}
6
- @placeholderMessage={{t 'worship-plugin.modal.fields.admin-unit'}}
4
+ @searchMessage={{t 'worship-plugin.modal.fields.admin-unit.search'}}
5
+ @noMatchesMessage={{t 'worship-plugin.modal.fields.admin-unit.no-results'}}
6
+ @placeholder={{t 'worship-plugin.modal.fields.admin-unit.placeholder'}}
7
7
  @allowClear={{true}}
8
8
  @renderInPlace={{true}}
9
9
  @searchEnabled={{true}}
@@ -18,21 +18,21 @@
18
18
  <div class='au-c-sidebar'>
19
19
  <div class='au-c-sidebar__content au-u-padding'>
20
20
  <AuHeading @level='3' @skin='4' class='au-u-padding-bottom-small'>
21
- {{t 'worship-plugin.modal.search.title'}}
21
+ {{t 'worship-plugin.modal.filter-on'}}
22
22
  </AuHeading>
23
23
  <AuLabel class='au-margin-bottom-small' for='searchTerm'>
24
- {{t 'worship-plugin.modal.fields.name'}}
24
+ {{t 'worship-plugin.modal.fields.name.label'}}
25
25
  </AuLabel>
26
26
  <AuNativeInput
27
27
  @type='text'
28
28
  @width='block'
29
29
  id='searchTerm'
30
30
  value={{this.searchText}}
31
- placeholder={{t 'worship-plugin.modal.search.placeholder'}}
31
+ placeholder={{t 'worship-plugin.modal.fields.name.placeholder'}}
32
32
  {{on 'input' this.setInputSearchText}}
33
33
  />
34
34
  <AuLabel class='au-margin-bottom-small' for='admin-unit-select'>
35
- {{t 'worship-plugin.modal.fields.admin-unit'}}
35
+ {{t 'worship-plugin.modal.fields.admin-unit.label'}}
36
36
  </AuLabel>
37
37
  <WorshipPlugin::AdministrativeUnitPicker
38
38
  id='admin-unit-select'
@@ -0,0 +1,150 @@
1
+ import factory from '@rdfjs/dataset';
2
+ import SHACLValidator from 'rdf-validate-shacl';
3
+ import { Parser as ParserN3 } from 'n3';
4
+ import { RdfaParser } from 'rdfa-streaming-parser';
5
+ import { ProsePlugin, PluginKey, EditorView } from '@lblod/ember-rdfa-editor';
6
+ import removeQuotes from '@lblod/ember-rdfa-editor-lblod-plugins/utils/remove-quotes';
7
+ import {
8
+ DataFactory,
9
+ DatasetCore,
10
+ DatasetCoreFactory,
11
+ Quad,
12
+ } from '@rdfjs/types';
13
+ import ValidationReport from 'rdf-validate-shacl/src/validation-report';
14
+ import { SayDataFactory } from '@lblod/ember-rdfa-editor/core/say-data-factory';
15
+
16
+ export const documentValidationPluginKey = new PluginKey('DOCUMENT_VALIDATION');
17
+
18
+ interface DocumentValidationPluginArgs {
19
+ documentShape: string;
20
+ }
21
+
22
+ export type ShaclValidationReport = ValidationReport.ValidationReport<
23
+ DataFactory<Quad, Quad> &
24
+ DatasetCoreFactory<Quad, Quad, DatasetCore<Quad, Quad>>
25
+ >;
26
+
27
+ interface documentValidationTransactionMeta {
28
+ type: string;
29
+ report: ValidationReport;
30
+ propertiesWithoutErrors: string[];
31
+ }
32
+
33
+ export const documentValidationPlugin = (
34
+ options: DocumentValidationPluginArgs,
35
+ ) =>
36
+ new ProsePlugin({
37
+ key: documentValidationPluginKey,
38
+ state: {
39
+ init() {
40
+ return {
41
+ validationCallback: validationCallback,
42
+ documentShape: options.documentShape,
43
+ };
44
+ },
45
+ apply(tr, state) {
46
+ const pluginTransaction = tr.getMeta(documentValidationPluginKey) as
47
+ | documentValidationTransactionMeta
48
+ | undefined;
49
+ if (pluginTransaction) {
50
+ if (pluginTransaction.type === 'setNewReport') {
51
+ return {
52
+ ...state,
53
+ report: pluginTransaction.report,
54
+ propertiesWithoutErrors:
55
+ pluginTransaction.propertiesWithoutErrors,
56
+ };
57
+ }
58
+ }
59
+
60
+ return state;
61
+ },
62
+ },
63
+ });
64
+
65
+ async function validationCallback(view: EditorView, documentHtml: string) {
66
+ const { documentShape } = documentValidationPluginKey.getState(view.state);
67
+ const rdf = await htmlToRdf(documentHtml);
68
+ const shacl = await parse(documentShape);
69
+
70
+ const validator = new SHACLValidator(shacl, {
71
+ // @ts-expect-error ts doesn't recognize the configuration parameter not sure why
72
+ allowNamedNodeInList: true,
73
+ });
74
+ const report = validator.validate(rdf);
75
+ const sayFactory = new SayDataFactory();
76
+ const propertyPred = sayFactory.namedNode(
77
+ 'http://www.w3.org/ns/shacl#property',
78
+ );
79
+ const propertyNodes = [
80
+ ...shacl.match(undefined, propertyPred, undefined),
81
+ ].map((quad: Quad) => quad.object);
82
+
83
+ const propertiesWithErrors: string[] = [];
84
+ for (const r of report.results) {
85
+ const shapeId = r.sourceShape?.value;
86
+ if (shapeId) propertiesWithErrors.push(shapeId);
87
+ }
88
+ const propertiesWithoutErrorsArray = propertyNodes.filter(
89
+ (term) => !propertiesWithErrors.includes(term.value),
90
+ );
91
+
92
+ const successMessagePred = sayFactory.namedNode(
93
+ 'http://mu.semte.ch/vocabularies/ext/successMessage',
94
+ );
95
+ const propertiesWithoutErrors = propertiesWithoutErrorsArray
96
+ .map((propertyNode) => {
97
+ const match = shacl.match(propertyNode, successMessagePred, undefined);
98
+ const message = [...match][0]?.object.value;
99
+ return message ? { message: removeQuotes(message) } : undefined;
100
+ })
101
+ .filter((message) => message);
102
+ const transaction = view.state.tr;
103
+ transaction.setMeta(documentValidationPluginKey, {
104
+ type: 'setNewReport',
105
+ report,
106
+ propertiesWithoutErrors,
107
+ });
108
+ transaction.setMeta('addToHistory', false);
109
+ view.dispatch(transaction);
110
+ }
111
+
112
+ interface N3Parser {
113
+ parse: (
114
+ triples: string,
115
+ callback: (error: string, quad: Quad) => void,
116
+ ) => void;
117
+ }
118
+
119
+ async function parse(triples: string): Promise<DatasetCore<Quad>> {
120
+ return new Promise((resolve, reject) => {
121
+ // @ts-expect-error we have to use a custom type to make the quads compatible or else ts will complain
122
+ const parser: N3Parser = new ParserN3();
123
+ const dataset = factory.dataset();
124
+ parser.parse(triples, (error, quad) => {
125
+ if (error) {
126
+ console.warn(error);
127
+ reject(error);
128
+ } else if (quad) {
129
+ dataset.add(quad);
130
+ } else {
131
+ resolve(dataset);
132
+ }
133
+ });
134
+ });
135
+ }
136
+
137
+ function htmlToRdf(html: string): Promise<DatasetCore<Quad>> {
138
+ return new Promise((res, rej) => {
139
+ const myParser = new RdfaParser({ contentType: 'text/html' });
140
+ const dataset = factory.dataset();
141
+ myParser
142
+ .on('data', (data) => {
143
+ dataset.add(data);
144
+ })
145
+ .on('error', rej)
146
+ .on('end', () => res(dataset));
147
+ myParser.write(html);
148
+ myParser.end();
149
+ });
150
+ }
@@ -25,7 +25,8 @@ import { buildArticleStructure } from '../../decision-plugin/utils/build-article
25
25
  import { insertArticle } from '../../decision-plugin/actions/insert-article';
26
26
  import { SignConcept } from '../schemas/sign-concept';
27
27
  import {
28
- SIGN_CONCEPT_TYPE_LABELS,
28
+ ROAD_SIGN_CATEGORIES,
29
+ SIGN_CONCEPT_TYPES,
29
30
  SIGN_TYPE_MAPPING,
30
31
  SIGN_TYPES,
31
32
  ZONALITY_OPTIONS,
@@ -190,9 +191,28 @@ function constructMeasureBody(
190
191
  return schema.nodes.paragraph.create({}, nodes);
191
192
  }
192
193
 
194
+ function determineSignLabel(signConcept: SignConcept) {
195
+ switch (signConcept.type) {
196
+ case SIGN_CONCEPT_TYPES.TRAFFIC_LIGHT:
197
+ return 'Verkeerslicht';
198
+ case SIGN_CONCEPT_TYPES.ROAD_MARKING:
199
+ return 'Wegmarkering';
200
+ case SIGN_CONCEPT_TYPES.ROAD_SIGN:
201
+ if (
202
+ signConcept.categories
203
+ .map((cat) => cat.uri)
204
+ .includes(ROAD_SIGN_CATEGORIES.ONDERBORD)
205
+ ) {
206
+ return 'Onderbord';
207
+ } else {
208
+ return 'Verkeersbord';
209
+ }
210
+ }
211
+ }
212
+
193
213
  function constructSignNode(signConcept: SignConcept, schema: Schema) {
194
214
  const signUri = `http://data.lblod.info/verkeerstekens/${uuid()}`;
195
- const prefix = SIGN_CONCEPT_TYPE_LABELS[signConcept.type];
215
+ const prefix = determineSignLabel(signConcept);
196
216
  const node = schema.nodes.inline_rdfa.create(
197
217
  {
198
218
  rdfaNodeType: 'resource',
@@ -29,9 +29,25 @@ export const SIGN_TYPE_MAPPING = {
29
29
  [SIGN_CONCEPT_TYPES.ROAD_MARKING]: SIGN_TYPES.ROAD_MARKING,
30
30
  } as const;
31
31
 
32
- export const SIGN_CONCEPT_TYPE_LABELS = {
33
- [SIGN_CONCEPT_TYPES.TRAFFIC_SIGN]: 'Verkeersteken',
34
- [SIGN_CONCEPT_TYPES.ROAD_SIGN]: 'Verkeersbord',
35
- [SIGN_CONCEPT_TYPES.TRAFFIC_LIGHT]: 'Verkeerslicht',
36
- [SIGN_CONCEPT_TYPES.ROAD_MARKING]: 'Wegmarkering',
37
- } as const;
32
+ export const ROAD_SIGN_CATEGORIES = {
33
+ XXBORD:
34
+ 'https://data.vlaanderen.be/id/concept/Verkeersbordcategorie/ae1b7231-1f31-492d-947a-25fc5d114492',
35
+ 'XX-AWVBORD':
36
+ 'https://data.vlaanderen.be/id/concept/Verkeersbordcategorie/8e302648-0eca-478b-8b48-67c3b0e39c0a',
37
+ GEVAARSBORD:
38
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/2982567006d9e19f04063df73123f56f40e3a28941031a7ba6e6667f64740fa9',
39
+ STILSTAANPARKEERBORD:
40
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/29ea3335e357e414d07229242607b352941c0c21e78760600cc0f5270f18c38b',
41
+ VOORRANGSBORD:
42
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/737da5751bc7f311398a834f34df310dd95255a0b62afa2db2882c72d54b47d2',
43
+ ZONEBORD:
44
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/86a67f3cba6512ae10c4b9b09ba35d8c80109189b44d37e848858af9efb37019',
45
+ VERBODSBORD:
46
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/955a9adc73d076a2a424754cd540b73da8d15fb002ab6c9f115d080edddb57e8',
47
+ ONDERBORD:
48
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/991b04b477b77bc7cf1414fb5d255cc4435dd9c1681e8de66f770710c1c83ad0',
49
+ GEBODSBORD:
50
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/9d84069e70f192b7a474d02f07687bc3343ee324207ad9e093c0b2f5def647f8',
51
+ AANWIJSBORD:
52
+ 'http://data.vlaanderen.be/id/concept/Verkeersbordcategorie/9ea8f8b421343370d20a8bd45d6226aadc48125bda8ddbbeeb53d99f181ee05a',
53
+ };
@@ -2,6 +2,7 @@ import {
2
2
  BindingObject,
3
3
  executeQuery,
4
4
  objectify,
5
+ sparqlEscapeUri,
5
6
  } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/sparql-helpers';
6
7
  import {
7
8
  RoadSignCategory,
@@ -10,16 +11,18 @@ import {
10
11
 
11
12
  type QueryOptions = {
12
13
  abortSignal?: AbortSignal;
14
+ roadSignConceptUri?: string;
13
15
  };
14
16
 
15
17
  export default async function queryRoadSignCategories(
16
18
  endpoint: string,
17
19
  options: QueryOptions = {},
18
20
  ) {
19
- const { abortSignal } = options;
21
+ const { abortSignal, roadSignConceptUri } = options;
20
22
  const query = /* sparql */ `
21
23
  PREFIX mobiliteit: <https://data.vlaanderen.be/ns/mobiliteit#>
22
24
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
25
+ PREFIX dct: <http://purl.org/dc/terms/>
23
26
 
24
27
  SELECT DISTINCT
25
28
  ?uri
@@ -27,6 +30,8 @@ export default async function queryRoadSignCategories(
27
30
  WHERE {
28
31
  ?uri a mobiliteit:Verkeersbordcategorie;
29
32
  skos:prefLabel ?label.
33
+
34
+ ${roadSignConceptUri ? `${sparqlEscapeUri(roadSignConceptUri)} dct:type ?uri` : ''}
30
35
  }
31
36
  `;
32
37
  const queryResult = await executeQuery<BindingObject<RoadSignCategory>>({
@@ -6,6 +6,8 @@ import {
6
6
  sparqlEscapeUri,
7
7
  } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/sparql-helpers';
8
8
  import { SignConcept, SignConceptSchema } from '../schemas/sign-concept';
9
+ import queryRoadSignCategories from './road-sign-category';
10
+ import { SIGN_CONCEPT_TYPES } from '../constants';
9
11
 
10
12
  type QueryOptions = {
11
13
  imageBaseUrl?: string;
@@ -31,7 +33,6 @@ export async function querySignConcepts(
31
33
  ?code
32
34
  ?zonality
33
35
  (CONCAT(${sparqlEscapeString(imageBaseUrl ?? '')}, "/files/", ?imageId, "/download") AS ?image)
34
- (GROUP_CONCAT(?classification; SEPARATOR="|") AS ?classifications)
35
36
  WHERE {
36
37
  ?uri
37
38
  a mobiliteit:Verkeerstekenconcept;
@@ -44,9 +45,6 @@ export async function querySignConcepts(
44
45
  OPTIONAL {
45
46
  ?uri ext:zonality ?zonality.
46
47
  }
47
- OPTIONAL {
48
- ?uri dct:type/skos:prefLabel ?classification.
49
- }
50
48
 
51
49
  VALUES ?type {
52
50
  <https://data.vlaanderen.be/ns/mobiliteit#Verkeersbordconcept>
@@ -63,13 +61,21 @@ export async function querySignConcepts(
63
61
  abortSignal,
64
62
  });
65
63
  const bindings = queryResult.results.bindings;
66
- const processed = bindings.map(objectify).map((binding) => {
67
- return {
68
- ...binding,
69
- classifications: binding.classifications
70
- ? binding.classifications.split('|')
71
- : [],
72
- };
73
- });
74
- return SignConceptSchema.array().parse(processed);
64
+ const concepts = SignConceptSchema.array().parse(bindings.map(objectify));
65
+ const conceptsWithCategories = await Promise.all(
66
+ concepts.map(async (concept) => {
67
+ if (concept.type === SIGN_CONCEPT_TYPES.ROAD_SIGN) {
68
+ const categories = await queryRoadSignCategories(endpoint, {
69
+ roadSignConceptUri: concept.uri,
70
+ });
71
+ return {
72
+ ...concept,
73
+ categories,
74
+ };
75
+ } else {
76
+ return concept;
77
+ }
78
+ }),
79
+ );
80
+ return conceptsWithCategories;
75
81
  }
@@ -1,13 +1,27 @@
1
1
  import { z } from 'zod';
2
2
  import { SIGN_CONCEPT_TYPES, ZONALITY_OPTIONS } from '../constants';
3
+ import { RoadSignCategorySchema } from './road-sign-category';
3
4
 
4
- export const SignConceptSchema = z.object({
5
- uri: z.string(),
6
- code: z.string(),
7
- type: z.nativeEnum(SIGN_CONCEPT_TYPES),
8
- image: z.string(),
9
- classifications: z.array(z.string()).default([]),
10
- zonality: z.nativeEnum(ZONALITY_OPTIONS).optional(),
11
- });
5
+ export const SignConceptSchema = z
6
+ .object({
7
+ uri: z.string(),
8
+ code: z.string(),
9
+ image: z.string(),
10
+ zonality: z.nativeEnum(ZONALITY_OPTIONS).optional(),
11
+ })
12
+ .and(
13
+ z.discriminatedUnion('type', [
14
+ z.object({
15
+ type: z.literal(SIGN_CONCEPT_TYPES.ROAD_SIGN),
16
+ categories: z.array(RoadSignCategorySchema).default([]),
17
+ }),
18
+ z.object({
19
+ type: z.enum([
20
+ SIGN_CONCEPT_TYPES.ROAD_MARKING,
21
+ SIGN_CONCEPT_TYPES.TRAFFIC_LIGHT,
22
+ ]),
23
+ }),
24
+ ]),
25
+ );
12
26
 
13
27
  export type SignConcept = z.infer<typeof SignConceptSchema>;
@@ -175,6 +175,7 @@ const emberNodeConfig = (options: SnippetPluginConfig): EmberNodeConfig => ({
175
175
  allowMultipleSnippets: { default: false },
176
176
  },
177
177
  component: SnippetComponent,
178
+ contentDomClassNames: ['say-snippet-content'],
178
179
  content: options.allowedContent || DEFAULT_CONTENT_STRING,
179
180
  serialize(node) {
180
181
  const listNames = node.attrs.snippetListNames as string[];
@@ -118,6 +118,7 @@ export const emberNodeConfig: (
118
118
  return {
119
119
  name: 'structure',
120
120
  component: Structure as unknown as ComponentLike,
121
+ contentDomClassNames: ['say-structure__content'],
121
122
  inline: false,
122
123
  group: 'block structure',
123
124
  content: 'block+',
@@ -22,6 +22,7 @@ export const emberNodeConfig: () => EmberNodeConfig = () => {
22
22
  return {
23
23
  name: 'template-comment',
24
24
  component: TemplateCommentsComponent as unknown as ComponentLike,
25
+ contentDomClassNames: ['say-template-comment-content'],
25
26
  inline: false,
26
27
  group: 'block',
27
28
  content: 'block+',
@@ -282,6 +282,7 @@ const emberNodeConfig = (options: DateOptions): EmberNodeConfig => ({
282
282
  draggable: false,
283
283
  atom: true,
284
284
  recreateUriFunction: recreateVariableUris,
285
+ needsFFKludge: true,
285
286
  defining: false,
286
287
  options,
287
288
  attrs: {