@lblod/ember-rdfa-editor-lblod-plugins 23.1.0 → 24.0.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 (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/addon/components/snippet-plugin/nodes/snippet.gts +18 -8
  3. package/addon/components/snippet-plugin/snippet-insert-placeholder.gts +10 -2
  4. package/addon/components/snippet-plugin/snippet-insert-rdfa.gts +5 -0
  5. package/addon/components/snippet-plugin/snippet-insert.gts +2 -0
  6. package/addon/components/snippet-plugin/snippet-list/snippet-list-modal.hbs +23 -12
  7. package/addon/components/snippet-plugin/snippet-list/snippet-list-modal.ts +18 -3
  8. package/addon/components/snippet-plugin/snippet-list-select.gts +7 -1
  9. package/addon/components/variable-plugin/autofilled/edit.gts +224 -0
  10. package/addon/components/variable-plugin/autofilled/insert.gts +143 -0
  11. package/addon/components/variable-plugin/autofilled/nodeview.gts +100 -0
  12. package/addon/components/variable-plugin/utils/label-input.gts +28 -0
  13. package/addon/plugins/citation-plugin/utils/public-decisions.ts +7 -6
  14. package/addon/plugins/citation-plugin/utils/vlaamse-codex.ts +7 -6
  15. package/addon/plugins/snippet-plugin/commands/insert-snippet.ts +4 -1
  16. package/addon/plugins/snippet-plugin/commands/update-snippet-placeholder.ts +7 -0
  17. package/addon/plugins/snippet-plugin/nodes/snippet-placeholder.ts +10 -1
  18. package/addon/plugins/snippet-plugin/nodes/snippet.ts +7 -0
  19. package/addon/plugins/snippet-plugin/utils/fetch-data.ts +32 -29
  20. package/addon/plugins/variable-plugin/plugins/autofiller.ts +98 -0
  21. package/addon/plugins/variable-plugin/variables/autofilled.ts +170 -0
  22. package/addon/plugins/variable-plugin/variables/index.ts +1 -0
  23. package/addon/services/roadsign-registry.ts +2 -1
  24. package/app/components/variable-plugin/autofilled/edit.js +1 -0
  25. package/app/components/variable-plugin/autofilled/insert.js +1 -0
  26. package/app/components/variable-plugin/autofilled/nodeview.js +1 -0
  27. package/declarations/addon/components/snippet-plugin/nodes/snippet.d.ts +1 -0
  28. package/declarations/addon/components/snippet-plugin/snippet-insert-placeholder.d.ts +1 -1
  29. package/declarations/addon/components/snippet-plugin/snippet-insert-rdfa.d.ts +1 -0
  30. package/declarations/addon/components/snippet-plugin/snippet-insert.d.ts +1 -0
  31. package/declarations/addon/components/snippet-plugin/snippet-list/snippet-list-modal.d.ts +4 -1
  32. package/declarations/addon/components/snippet-plugin/snippet-list-select.d.ts +2 -1
  33. package/declarations/addon/components/variable-plugin/autofilled/edit.d.ts +28 -0
  34. package/declarations/addon/components/variable-plugin/autofilled/insert.d.ts +20 -0
  35. package/declarations/addon/components/variable-plugin/autofilled/nodeview.d.ts +22 -0
  36. package/declarations/addon/components/variable-plugin/utils/label-input.d.ts +11 -1
  37. package/declarations/addon/plugins/snippet-plugin/commands/insert-snippet.d.ts +2 -1
  38. package/declarations/addon/plugins/snippet-plugin/commands/update-snippet-placeholder.d.ts +2 -1
  39. package/declarations/addon/plugins/snippet-plugin/nodes/snippet-placeholder.d.ts +1 -1
  40. package/declarations/addon/plugins/snippet-plugin/nodes/snippet.d.ts +2 -1
  41. package/declarations/addon/plugins/variable-plugin/plugins/autofiller.d.ts +8 -0
  42. package/declarations/addon/plugins/variable-plugin/variables/autofilled.d.ts +2 -0
  43. package/declarations/addon/plugins/variable-plugin/variables/index.d.ts +1 -0
  44. package/package.json +1 -1
  45. package/translations/en-US.yaml +7 -0
  46. package/translations/nl-BE.yaml +7 -0
  47. package/addon/components/variable-plugin/utils/label-input.hbs +0 -11
@@ -0,0 +1,100 @@
1
+ import Component from '@glimmer/component';
2
+ import {
3
+ NodeSelection,
4
+ PNode,
5
+ ProsePlugin,
6
+ SayController,
7
+ SayView,
8
+ } from '@lblod/ember-rdfa-editor';
9
+ import { editableNodePlugin } from '@lblod/ember-rdfa-editor/plugins/editable-node';
10
+ import { tracked } from '@glimmer/tracking';
11
+ import { action } from '@ember/object';
12
+ import { PencilIcon } from '@appuniversum/ember-appuniversum/components/icons/pencil';
13
+
14
+ import {
15
+ EmberNodeArgs,
16
+ SayNodeViewConstructor,
17
+ } from '@lblod/ember-rdfa-editor/utils/ember-node';
18
+ import { getOutgoingTriple } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/namespace';
19
+ import { EXT } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
20
+ import AuPill from '@appuniversum/ember-appuniversum/components/au-pill';
21
+ import { on } from '@ember/modifier';
22
+ import EmbeddedEditor from '@lblod/ember-rdfa-editor/components/ember-node/embedded-editor';
23
+
24
+ type Args = EmberNodeArgs & {
25
+ getPos: () => number | undefined;
26
+ node: PNode;
27
+ updateAttribute: (attr: string, value: unknown) => void;
28
+ controller: SayController;
29
+ view: SayView;
30
+ selected: boolean;
31
+ selectNode: () => void;
32
+ nodeViews: Record<string, SayNodeViewConstructor>;
33
+ };
34
+
35
+ export default class AutoFilledVariableNodeViewComponent extends Component<Args> {
36
+ PencilIcon = PencilIcon;
37
+
38
+ @tracked innerView?: SayView;
39
+
40
+ get plugins(): ProsePlugin[] {
41
+ return [editableNodePlugin(this.args.getPos)];
42
+ }
43
+ @action
44
+ onClick() {
45
+ if (this.innerView) {
46
+ if (this.innerView.state.doc.firstChild?.type.name === 'placeholder') {
47
+ this.innerView.focus();
48
+ // Use request animation frame to only change the selection when the focus has been established
49
+ window.requestAnimationFrame(() => {
50
+ if (this.innerView) {
51
+ const tr = this.innerView.state.tr;
52
+ tr.setSelection(NodeSelection.create(this.innerView?.state.doc, 0));
53
+ this.innerView?.dispatch(tr);
54
+ }
55
+ });
56
+ } else {
57
+ this.innerView.focus();
58
+ }
59
+ }
60
+ }
61
+ @action
62
+ initEditor(view: SayView) {
63
+ this.innerView = view;
64
+ }
65
+
66
+ get label() {
67
+ if (this.innerView?.state.doc.firstChild?.type.name !== 'placeholder') {
68
+ return '';
69
+ }
70
+ return getOutgoingTriple(this.args.node.attrs, EXT('label'))?.object.value;
71
+ }
72
+ <template>
73
+ <AuPill
74
+ @icon={{this.PencilIcon}}
75
+ @iconAlignment='right'
76
+ class='variable'
77
+ {{on 'click' this.onClick}}
78
+ >
79
+ <EmbeddedEditor
80
+ @controller={{@controller}}
81
+ @view={{@view}}
82
+ @getPos={{@getPos}}
83
+ @node={{@node}}
84
+ @selected={{@selected}}
85
+ @initEditor={{this.initEditor}}
86
+ @nodeViews={{@nodeViews}}
87
+ @plugins={{this.plugins}}
88
+ @updateAttribute={{@updateAttribute}}
89
+ @selectNode={{@selectNode}}
90
+ @placeholder=''
91
+ @contentDecorations={{@contentDecorations}}
92
+ />
93
+ {{#if this.label}}
94
+ <span class='label'>
95
+ ({{this.label}})
96
+ </span>
97
+ {{/if}}
98
+ </AuPill>
99
+ </template>
100
+ }
@@ -0,0 +1,28 @@
1
+ import AuLabel from '@appuniversum/ember-appuniversum/components/au-label';
2
+ import AuNativeInput from '@lblod/ember-rdfa-editor-lblod-plugins/components/au-native-input';
3
+ import t from 'ember-intl/helpers/t';
4
+ import { on } from '@ember/modifier';
5
+ import { TemplateOnlyComponent } from '@ember/component/template-only';
6
+
7
+ interface Sig {
8
+ Args: {
9
+ label: string;
10
+ updateLabel: (event: InputEvent) => void;
11
+ };
12
+ }
13
+
14
+ const LabelInput: TemplateOnlyComponent<Sig> = <template>
15
+ <AuLabel for='label'>
16
+ {{t 'variable-plugin.label'}}
17
+ </AuLabel>
18
+ <AuNativeInput
19
+ id='label'
20
+ placeholder={{t 'variable-plugin.labelPlaceholder'}}
21
+ @type='text'
22
+ @width='block'
23
+ value={{@label}}
24
+ {{on 'input' @updateLabel}}
25
+ />
26
+ </template>;
27
+
28
+ export default LabelInput;
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  executeCountQuery,
3
3
  executeQuery,
4
+ sparqlEscapeString,
4
5
  } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/sparql-helpers';
5
6
  import {
6
7
  dateValue,
@@ -43,18 +44,18 @@ const getFilters = ({
43
44
  }
44
45
 
45
46
  const governmentNameFilter = filter.governmentName?.trim()
46
- ? `FILTER (CONTAINS(LCASE(?administrativeUnitName), "${replaceDiacriticsInWord(
47
- filter.governmentName?.trim(),
48
- ).toLowerCase()}"))`
47
+ ? `FILTER (CONTAINS(LCASE(?administrativeUnitName), ${sparqlEscapeString(
48
+ replaceDiacriticsInWord(filter.governmentName?.trim()).toLowerCase(),
49
+ )}))`
49
50
  : '';
50
51
 
51
52
  return `
52
53
  ${words
53
54
  .map(
54
55
  (word) =>
55
- `FILTER (CONTAINS(LCASE(?decisionTitle), "${replaceDiacriticsInWord(
56
- word,
57
- ).toLowerCase()}"))`,
56
+ `FILTER (CONTAINS(LCASE(?decisionTitle), ${sparqlEscapeString(
57
+ replaceDiacriticsInWord(word).toLowerCase(),
58
+ )}))`,
58
59
  )
59
60
  .join('\n')}
60
61
  ${documentDateFilter.join('\n')}
@@ -5,6 +5,7 @@ import {
5
5
  import {
6
6
  executeCountQuery,
7
7
  executeQuery,
8
+ sparqlEscapeString,
8
9
  } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/sparql-helpers';
9
10
  import {
10
11
  LegalDocument,
@@ -91,9 +92,9 @@ export async function fetchVlaamseCodexLegalDocuments({
91
92
  ${words
92
93
  .map(
93
94
  (word) =>
94
- `FILTER (CONTAINS(LCASE(?title), "${replaceDiacriticsInWord(
95
- word,
96
- ).toLowerCase()}"))`,
95
+ `FILTER (CONTAINS(LCASE(?title), ${sparqlEscapeString(
96
+ replaceDiacriticsInWord(word).toLowerCase(),
97
+ )}))`,
97
98
  )
98
99
  .join('\n')}
99
100
  ${excludeAdaptationFilters.join('\n')}
@@ -116,9 +117,9 @@ export async function fetchVlaamseCodexLegalDocuments({
116
117
  ${words
117
118
  .map(
118
119
  (word) =>
119
- `FILTER (CONTAINS(LCASE(?title), "${replaceDiacriticsInWord(
120
- word,
121
- ).toLowerCase()}"))`,
120
+ `FILTER (CONTAINS(LCASE(?title), ${sparqlEscapeString(
121
+ replaceDiacriticsInWord(word).toLowerCase(),
122
+ )}))`,
122
123
  )
123
124
  .join('\n')}
124
125
  OPTIONAL { ?expressionUri eli:date_publication ?publicationDate . }
@@ -15,6 +15,7 @@ export interface InsertSnippetCommandArgs {
15
15
  assignedSnippetListsIds: string[];
16
16
  importedResources?: ImportedResourceMap;
17
17
  range?: { start: number; end: number };
18
+ allowMultipleSnippets?: boolean;
18
19
  }
19
20
 
20
21
  const insertSnippet = ({
@@ -23,6 +24,7 @@ const insertSnippet = ({
23
24
  assignedSnippetListsIds,
24
25
  importedResources,
25
26
  range,
27
+ allowMultipleSnippets,
26
28
  }: InsertSnippetCommandArgs): Command => {
27
29
  return (state, dispatch) => {
28
30
  const domParser = new DOMParser();
@@ -44,6 +46,7 @@ const insertSnippet = ({
44
46
  title,
45
47
  snippetListIds: assignedSnippetListsIds,
46
48
  importedResources,
49
+ allowMultipleSnippets,
47
50
  });
48
51
 
49
52
  const addImportedResourceProperties = Object.values(
@@ -60,7 +63,6 @@ const insertSnippet = ({
60
63
  );
61
64
  })
62
65
  .flat();
63
-
64
66
  tr = transactionCombinator(
65
67
  state,
66
68
  tr.replaceRangeWith(insertRange.start, insertRange.end, snippet),
@@ -72,6 +74,7 @@ const insertSnippet = ({
72
74
  tr.replaceRange(insertRange.start, insertRange.end, slice),
73
75
  )([recalculateNumbers]).transaction;
74
76
  }
77
+ tr.setMeta('insertSnippet', true);
75
78
  if (dispatch) {
76
79
  dispatch(tr);
77
80
  }
@@ -17,12 +17,14 @@ export const updateSnippetPlaceholder = ({
17
17
  newSnippetLists,
18
18
  oldImportedResources,
19
19
  node,
20
+ allowMultipleSnippets = false,
20
21
  }: {
21
22
  resource: string;
22
23
  oldSnippetProperties: OutgoingTriple[];
23
24
  newSnippetLists: SnippetList[];
24
25
  oldImportedResources: ImportedResourceMap;
25
26
  node: ResolvedPNode;
27
+ allowMultipleSnippets?: boolean;
26
28
  }): Command => {
27
29
  return (state, dispatch) => {
28
30
  if (dispatch) {
@@ -68,6 +70,11 @@ export const updateSnippetPlaceholder = ({
68
70
  oldImportedResources,
69
71
  ),
70
72
  );
73
+ transaction = transaction.setNodeAttribute(
74
+ node.pos,
75
+ 'allowMultipleSnippets',
76
+ allowMultipleSnippets,
77
+ );
71
78
 
72
79
  dispatch(transaction);
73
80
  }
@@ -38,7 +38,11 @@ export function importedResourcesFromSnippetLists(
38
38
  );
39
39
  }
40
40
 
41
- export function createSnippetPlaceholder(lists: SnippetList[], schema: Schema) {
41
+ export function createSnippetPlaceholder(
42
+ lists: SnippetList[],
43
+ schema: Schema,
44
+ allowMultipleSnippets?: boolean,
45
+ ) {
42
46
  const mappingResource = `http://example.net/lblod-snippet-placeholder/${uuidv4()}`;
43
47
  return schema.nodes.snippet_placeholder.create({
44
48
  rdfaNodeType: 'resource',
@@ -55,6 +59,7 @@ export function createSnippetPlaceholder(lists: SnippetList[], schema: Schema) {
55
59
  })),
56
60
  ],
57
61
  importedResources: importedResourcesFromSnippetLists(lists),
62
+ allowMultipleSnippets,
58
63
  });
59
64
  }
60
65
 
@@ -70,6 +75,7 @@ const emberNodeConfig: EmberNodeConfig = {
70
75
  typeof: { default: EXT('SnippetPlaceholder') },
71
76
  listNames: { default: [] },
72
77
  importedResources: { default: {} },
78
+ allowMultipleSnippets: { default: false },
73
79
  },
74
80
  component: SnippetPlaceholderComponent,
75
81
  serialize(node, editorState) {
@@ -82,6 +88,7 @@ const emberNodeConfig: EmberNodeConfig = {
82
88
  class: 'say-snippet-placeholder-node',
83
89
  'data-list-names': (node.attrs.listNames as string[]).join(','),
84
90
  'data-imported-resources': JSON.stringify(node.attrs.importedResources),
91
+ 'data-allow-multiple-snippets': node.attrs.allowMultipleSnippets,
85
92
  },
86
93
  content: [
87
94
  'text',
@@ -111,6 +118,8 @@ const emberNodeConfig: EmberNodeConfig = {
111
118
  importedResources: jsonParse(
112
119
  node.getAttribute('data-imported-resources'),
113
120
  ),
121
+ allowMultipleSnippets:
122
+ node.dataset.allowMultipleSnippets === 'true',
114
123
  };
115
124
  }
116
125
  return false;
@@ -63,6 +63,7 @@ interface CreateSnippetArgs {
63
63
  title: string;
64
64
  snippetListIds: string[];
65
65
  importedResources?: ImportedResourceMap;
66
+ allowMultipleSnippets?: boolean;
66
67
  }
67
68
 
68
69
  /**
@@ -77,6 +78,7 @@ export function createSnippet({
77
78
  title,
78
79
  snippetListIds,
79
80
  importedResources,
81
+ allowMultipleSnippets,
80
82
  }: CreateSnippetArgs): [PNode, Map<string, OutgoingTriple[]>] {
81
83
  // Replace instances of linked to uris with the resources that exist in the outer document.
82
84
  let replacedContent = content;
@@ -99,6 +101,7 @@ export function createSnippet({
99
101
  title,
100
102
  subject: `http://data.lblod.info/snippets/${uuidv4()}`,
101
103
  importedResources,
104
+ allowMultipleSnippets,
102
105
  },
103
106
  contentAsNode.content,
104
107
  );
@@ -151,6 +154,7 @@ const emberNodeConfig = (options: SnippetPluginConfig): EmberNodeConfig => ({
151
154
  importedResources: { default: {} },
152
155
  title: { default: '' },
153
156
  config: { default: options },
157
+ allowMultipleSnippets: { default: false },
154
158
  },
155
159
  component: SnippetComponent,
156
160
  content: options.allowedContent || DEFAULT_CONTENT_STRING,
@@ -165,6 +169,7 @@ const emberNodeConfig = (options: SnippetPluginConfig): EmberNodeConfig => ({
165
169
  ).join(','),
166
170
  'data-imported-resources': JSON.stringify(node.attrs.importedResources),
167
171
  'data-snippet-title': node.attrs.title,
172
+ 'data-allow-multiple-snippets': node.attrs.allowMultipleSnippets,
168
173
  },
169
174
  content: 0,
170
175
  });
@@ -187,6 +192,8 @@ const emberNodeConfig = (options: SnippetPluginConfig): EmberNodeConfig => ({
187
192
  node.getAttribute('data-imported-resources'),
188
193
  ),
189
194
  title: node.getAttribute('data-snippet-title'),
195
+ allowMultipleSnippets:
196
+ node.dataset.allowMultipleSnippets === 'true',
190
197
  };
191
198
  }
192
199
  return false;
@@ -17,7 +17,7 @@ export type OrderBy =
17
17
  type Pagination = { pageNumber: number; pageSize: number };
18
18
 
19
19
  const buildSnippetCountQuery = ({ name, assignedSnippetListIds }: Filter) => {
20
- return `
20
+ return /* sparql */ `
21
21
  PREFIX schema: <http://schema.org/>
22
22
  PREFIX dct: <http://purl.org/dc/terms/>
23
23
  PREFIX pav: <http://purl.org/pav/>
@@ -25,26 +25,25 @@ const buildSnippetCountQuery = ({ name, assignedSnippetListIds }: Filter) => {
25
25
  PREFIX ext: <http://mu.semte.ch/vocabularies/ext/>
26
26
  PREFIX prov: <http://www.w3.org/ns/prov#>
27
27
  PREFIX mu: <http://mu.semte.ch/vocabularies/core/>
28
+ PREFIX say: <https://say.data.gift/ns/>
28
29
 
29
- SELECT (COUNT(?publishedSnippetVersion) AS ?count)
30
+ SELECT (COUNT(?snippet) AS ?count)
30
31
  WHERE {
31
- ?publishedSnippetContainer a ext:PublishedSnippetContainer ;
32
- pav:hasCurrentVersion ?publishedSnippetVersion ;
33
- ext:fromSnippetList ?fromSnippetList .
34
- ?fromSnippetList mu:uuid ?fromSnippetListId .
35
- ?publishedSnippetVersion dct:title ?title ;
36
- ext:editorDocumentContent ?content ;
37
- pav:createdOn ?createdOn .
38
- OPTIONAL { ?publishedSnippetVersion schema:validThrough ?validThrough. }
32
+ ?snippet a say:Snippet;
33
+ pav:hasCurrentVersion ?snippetVersion;
34
+ ^say:hasSnippet ?snippetList.
35
+ ?snippetList mu:uuid ?snippetListId.
36
+ ?snippetVersion dct:title ?title.
37
+ OPTIONAL { ?snippetVersion schema:validThrough ?validThrough. }
39
38
  FILTER(!BOUND(?validThrough) || xsd:dateTime(?validThrough) > now())
40
39
  ${
41
40
  name
42
- ? `FILTER (CONTAINS(LCASE(?title), "${name.toLowerCase()}"))`
41
+ ? `FILTER (CONTAINS(LCASE(?title), ${sparqlEscapeString(name.toLowerCase())}))`
43
42
  : ''
44
43
  }
45
44
  ${
46
45
  assignedSnippetListIds && assignedSnippetListIds.length
47
- ? `FILTER (?fromSnippetListId IN (${assignedSnippetListIds
46
+ ? `FILTER (?snippetListId IN (${assignedSnippetListIds
48
47
  .map((from) => sparqlEscapeString(from))
49
48
  .join(', ')}))`
50
49
  : ''
@@ -76,7 +75,7 @@ const buildSnippetFetchQuery = ({
76
75
  filter: Filter;
77
76
  pagination: Pagination;
78
77
  }) => {
79
- return `
78
+ return /* sparql */ `
80
79
  PREFIX schema: <http://schema.org/>
81
80
  PREFIX dct: <http://purl.org/dc/terms/>
82
81
  PREFIX pav: <http://purl.org/pav/>
@@ -84,32 +83,37 @@ const buildSnippetFetchQuery = ({
84
83
  PREFIX ext: <http://mu.semte.ch/vocabularies/ext/>
85
84
  PREFIX prov: <http://www.w3.org/ns/prov#>
86
85
  PREFIX mu: <http://mu.semte.ch/vocabularies/core/>
86
+ PREFIX say: <https://say.data.gift/ns/>
87
87
 
88
88
  SELECT DISTINCT ?title ?content ?createdOn
89
89
  WHERE {
90
- ?publishedSnippetContainer a ext:PublishedSnippetContainer ;
91
- pav:hasCurrentVersion ?publishedSnippetVersion ;
92
- ext:fromSnippetList ?fromSnippetList .
93
- ?fromSnippetList mu:uuid ?fromSnippetListId .
94
- ?publishedSnippetVersion dct:title ?title ;
95
- ext:editorDocumentContent ?content ;
96
- pav:createdOn ?createdOn .
90
+ ?snippet a say:Snippet;
91
+ pav:hasCurrentVersion ?snippetVersion;
92
+ pav:createdOn ?createdOn;
93
+ ^say:hasSnippet ?snippetList.
94
+ OPTIONAL {
95
+ ?snippet schema:position ?position.
96
+ }
97
+ ?snippetList mu:uuid ?snippetListId;
98
+ pav:createdOn ?snippetListCreatedOn.
99
+ ?snippetVersion dct:title ?title ;
100
+ ext:editorDocumentContent ?content.
101
+ OPTIONAL { ?snippetVersion schema:validThrough ?validThrough. }
102
+ FILTER(!BOUND(?validThrough) || xsd:dateTime(?validThrough) > now())
97
103
  ${
98
104
  name
99
- ? `FILTER (CONTAINS(LCASE(?title), "${name.toLowerCase()}"))`
105
+ ? `FILTER (CONTAINS(LCASE(?title), ${sparqlEscapeString(name.toLowerCase())}))`
100
106
  : ''
101
107
  }
102
108
  ${
103
109
  assignedSnippetListIds && assignedSnippetListIds.length
104
- ? `FILTER (?fromSnippetListId IN (${assignedSnippetListIds
110
+ ? `FILTER (?snippetListId IN (${assignedSnippetListIds
105
111
  .map((from) => sparqlEscapeString(from))
106
112
  .join(', ')}))`
107
113
  : ''
108
114
  }
109
- OPTIONAL { ?publishedSnippetVersion schema:validThrough ?validThrough. }
110
- FILTER(!BOUND(?validThrough) || xsd:dateTime(?validThrough) > now())
111
115
  }
112
- ORDER BY DESC(?createdOn) LIMIT ${pageSize} OFFSET ${
116
+ ORDER BY DESC(?snippetListCreatedOn) ASC(?position) DESC(?createdOn) LIMIT ${pageSize} OFFSET ${
113
117
  pageNumber * pageSize
114
118
  }
115
119
  `;
@@ -138,20 +142,19 @@ const buildSnippetListFetchQuery = ({
138
142
  filter: Filter;
139
143
  orderBy: OrderBy;
140
144
  }) => {
141
- return `
145
+ return /* sparql */ `
142
146
  PREFIX pav: <http://purl.org/pav/>
143
147
  PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
144
- PREFIX ext: <http://mu.semte.ch/vocabularies/ext/>
145
148
  PREFIX say: <https://say.data.gift/ns/>
146
149
 
147
150
  SELECT (?snippetLists as ?id) ?label ?createdOn ?importedResources WHERE {
148
- ?snippetLists a ext:SnippetList;
151
+ ?snippetLists a say:SnippetList;
149
152
  skos:prefLabel ?label;
150
153
  pav:createdOn ?createdOn.
151
154
  OPTIONAL { ?snippetLists say:snippetImportedResource ?importedResources . }
152
155
  ${
153
156
  name
154
- ? `FILTER (CONTAINS(LCASE(?label), "${name.toLowerCase()}"))`
157
+ ? `FILTER (CONTAINS(LCASE(?label), ${sparqlEscapeString(name.toLowerCase())}))`
155
158
  : ''
156
159
  }
157
160
  }
@@ -0,0 +1,98 @@
1
+ import {
2
+ EditorState,
3
+ PNode,
4
+ ProsePlugin,
5
+ Schema,
6
+ Transaction,
7
+ } from '@lblod/ember-rdfa-editor';
8
+ import { changedDescendants } from '@lblod/ember-rdfa-editor/utils/_private/changed-descendants';
9
+ import { undoDepth } from '@lblod/ember-rdfa-editor/plugins/history';
10
+
11
+ type AutofilledArgs = {
12
+ autofilledValues: {
13
+ [Key: string]: string;
14
+ };
15
+ };
16
+
17
+ export function variableAutofillerPlugin(config: AutofilledArgs): ProsePlugin {
18
+ return new ProsePlugin({
19
+ appendTransaction(
20
+ transactions: readonly Transaction[],
21
+ oldState: EditorState,
22
+ newState: EditorState,
23
+ ) {
24
+ const autofilledVariables: { node: PNode; pos: number }[] = [];
25
+ if (undoDepth(oldState)) {
26
+ let isInsertSnippet = false;
27
+ for (const transaction of transactions) {
28
+ if (transaction.getMeta('insertSnippet')) {
29
+ isInsertSnippet = true;
30
+ }
31
+ }
32
+ if (!isInsertSnippet) return;
33
+ changedDescendants(
34
+ oldState.doc,
35
+ newState.doc,
36
+ 0,
37
+ (node: PNode, pos: number) => {
38
+ if (
39
+ node.type.name === 'autofilled_variable' &&
40
+ !node.attrs.initialized
41
+ ) {
42
+ autofilledVariables.push({ node, pos });
43
+ return false;
44
+ }
45
+ return true;
46
+ },
47
+ );
48
+ } else {
49
+ newState.doc.descendants((node: PNode, pos: number) => {
50
+ if (
51
+ node.type.name === 'autofilled_variable' &&
52
+ !node.attrs.initialized
53
+ ) {
54
+ autofilledVariables.push({ node, pos });
55
+ return false;
56
+ }
57
+ return true;
58
+ });
59
+ }
60
+ if (autofilledVariables.length) {
61
+ const tr = newState.tr;
62
+ autofilledVariables.reverse();
63
+ for (const { node, pos } of autofilledVariables) {
64
+ autofillVariable(
65
+ node,
66
+ pos,
67
+ config.autofilledValues,
68
+ tr,
69
+ newState.schema,
70
+ );
71
+ }
72
+ return tr;
73
+ }
74
+ return newState.tr;
75
+ },
76
+ });
77
+ }
78
+
79
+ function autofillVariable(
80
+ node: PNode,
81
+ pos: number,
82
+ values: { [Key: string]: string },
83
+ tr: Transaction,
84
+ schema: Schema,
85
+ ) {
86
+ const autofillKey = node.attrs.autofillKey as string;
87
+ const value = values[autofillKey];
88
+ if (value) {
89
+ const nodeSize = node.nodeSize;
90
+ const valueNode = schema.text(value);
91
+ if (node.attrs.convertToString === true) {
92
+ tr.replaceRangeWith(pos, pos + nodeSize, valueNode);
93
+ } else {
94
+ tr.replaceRangeWith(pos + 1, pos + nodeSize - 1, valueNode);
95
+ tr.setNodeAttribute(pos, 'initialized', true);
96
+ }
97
+ }
98
+ }