@lblod/ember-rdfa-editor-lblod-plugins 36.0.0 → 37.0.1

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 (55) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/addon/components/citation-plugin/citation-card.hbs +1 -0
  3. package/addon/components/decision-plugin/decision-plugin-card.gts +4 -0
  4. package/addon/components/document-validation-plugin/card.gts +2 -0
  5. package/addon/components/location-plugin/insert.gts +27 -19
  6. package/addon/components/locked-placeholder-plugin/insert.gts +161 -0
  7. package/addon/components/locked-placeholder-plugin/nodeviews/block.gts +23 -0
  8. package/addon/components/locked-placeholder-plugin/nodeviews/inline.gts +23 -0
  9. package/addon/components/mandatee-table-plugin/configure.gts +1 -0
  10. package/addon/components/placeholder-utils-plugin/edit.gts +1 -0
  11. package/addon/components/roadsign-regulation-plugin/roadsigns-table.gts +1 -1
  12. package/addon/components/structure-plugin/control-card.gts +1 -0
  13. package/addon/components/template-comments-plugin/edit-card.hbs +1 -0
  14. package/addon/components/variable-plugin/address/edit.hbs +1 -0
  15. package/addon/components/variable-plugin/autofilled/edit.gts +1 -0
  16. package/addon/components/variable-plugin/codelist/edit.gts +1 -0
  17. package/addon/components/variable-plugin/date/edit.hbs +1 -0
  18. package/addon/components/variable-plugin/insert-variable-card.hbs +1 -0
  19. package/addon/components/variable-plugin/location/edit.hbs +1 -0
  20. package/addon/components/variable-plugin/number/nodeview.hbs +1 -0
  21. package/addon/components/variable-plugin/person/edit.hbs +1 -0
  22. package/addon/plugins/decision-plugin/commands/insert-article-container.ts +3 -0
  23. package/addon/plugins/decision-plugin/commands/insert-description.ts +4 -1
  24. package/addon/plugins/decision-plugin/commands/insert-motivation.ts +3 -1
  25. package/addon/plugins/decision-plugin/commands/insert-title.ts +3 -1
  26. package/addon/plugins/document-validation-plugin/common-fixes.ts +4 -0
  27. package/addon/plugins/location-plugin/contextual-actions/index.ts +84 -0
  28. package/addon/plugins/location-plugin/index.ts +90 -0
  29. package/addon/plugins/locked-placeholder-plugin/nodes/block-locked-placeholder.ts +68 -0
  30. package/addon/plugins/locked-placeholder-plugin/nodes/inline-locked-placeholder.ts +68 -0
  31. package/addon/plugins/locked-placeholder-plugin/utils/replace-content-function.ts +89 -0
  32. package/addon/plugins/structure-plugin/monads/regenerate-rdfa-links.ts +113 -40
  33. package/addon/plugins/variable-plugin/contextual-actions/index.ts +1 -1
  34. package/addon/utils/set-utils.ts +18 -0
  35. package/app/components/locked-placeholder-plugin/insert.js +1 -0
  36. package/app/styles/locked-placeholder-plugin.scss +12 -0
  37. package/declarations/addon/components/location-plugin/insert.d.ts +2 -2
  38. package/declarations/addon/components/locked-placeholder-plugin/insert.d.ts +24 -0
  39. package/declarations/addon/components/locked-placeholder-plugin/nodeviews/block.d.ts +14 -0
  40. package/declarations/addon/components/locked-placeholder-plugin/nodeviews/inline.d.ts +14 -0
  41. package/declarations/addon/plugins/decision-plugin/commands/insert-article-container.d.ts +2 -1
  42. package/declarations/addon/plugins/decision-plugin/commands/insert-description.d.ts +2 -1
  43. package/declarations/addon/plugins/decision-plugin/commands/insert-motivation.d.ts +2 -1
  44. package/declarations/addon/plugins/decision-plugin/commands/insert-title.d.ts +2 -1
  45. package/declarations/addon/plugins/location-plugin/contextual-actions/index.d.ts +14 -0
  46. package/declarations/addon/plugins/location-plugin/index.d.ts +13 -0
  47. package/declarations/addon/plugins/locked-placeholder-plugin/nodes/block-locked-placeholder.d.ts +2 -0
  48. package/declarations/addon/plugins/locked-placeholder-plugin/nodes/inline-locked-placeholder.d.ts +2 -0
  49. package/declarations/addon/plugins/locked-placeholder-plugin/utils/replace-content-function.d.ts +7 -0
  50. package/declarations/addon/plugins/structure-plugin/monads/regenerate-rdfa-links.d.ts +6 -0
  51. package/declarations/addon/plugins/variable-plugin/contextual-actions/index.d.ts +1 -1
  52. package/declarations/addon/utils/set-utils.d.ts +8 -0
  53. package/package.json +4 -4
  54. package/translations/en-US.yaml +16 -0
  55. package/translations/nl-BE.yaml +16 -0
@@ -0,0 +1,84 @@
1
+ import { EditorState, NodeSelection } from '@lblod/ember-rdfa-editor';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { getTranslationFunction } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/translation';
4
+ import { openLocationModalCommand } from '..';
5
+ import { LocationType } from '@lblod/ember-rdfa-editor-lblod-plugins/components/location-plugin/map';
6
+
7
+ const otherElementsGroupId =
8
+ 'other-elements-e01f46a0-b323-4add-8035-d81dc2e8578d';
9
+
10
+ export function getContextualActions() {
11
+ return function (state: EditorState, searchQuery?: string) {
12
+ const t = getTranslationFunction(state);
13
+
14
+ const options: {
15
+ label: string;
16
+ locationType: LocationType;
17
+ icon: string;
18
+ }[] = [
19
+ {
20
+ label: t(
21
+ 'location-plugin.context-actions.insert-address',
22
+ 'Adres invoegen',
23
+ ),
24
+ locationType: 'address',
25
+ icon: 'location',
26
+ },
27
+ {
28
+ label: t(
29
+ 'location-plugin.context-actions.insert-point-on-map',
30
+ 'Punt op de kaart invoegen',
31
+ ),
32
+ locationType: 'place',
33
+ icon: 'location-gps',
34
+ },
35
+ {
36
+ label: t(
37
+ 'location-plugin.context-actions.insert-area',
38
+ 'Gebied invoegen',
39
+ ),
40
+ locationType: 'area',
41
+ icon: 'area',
42
+ },
43
+ ];
44
+
45
+ return options
46
+ .filter(
47
+ (option) =>
48
+ !searchQuery ||
49
+ option.label.toLocaleLowerCase().includes(searchQuery.toLowerCase()),
50
+ )
51
+ .map((option) => {
52
+ return {
53
+ ...option,
54
+ id: uuidv4(),
55
+ group: otherElementsGroupId,
56
+ command: openLocationModalCommand(option.locationType),
57
+ };
58
+ });
59
+ };
60
+ }
61
+
62
+ function contextualGroupIsVisible(state: EditorState) {
63
+ const { selection } = state;
64
+ return (
65
+ selection instanceof NodeSelection &&
66
+ selection.node.type === selection.node.type.schema.nodes['oslo_location']
67
+ );
68
+ }
69
+
70
+ export function getContextualActionGroups() {
71
+ return function (state: EditorState) {
72
+ return contextualGroupIsVisible(state)
73
+ ? [
74
+ {
75
+ id: otherElementsGroupId,
76
+ label: getTranslationFunction(state)(
77
+ 'location-plugin.context-actions.other-elements',
78
+ 'Andere elementen',
79
+ ),
80
+ },
81
+ ]
82
+ : [];
83
+ };
84
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ ProsePlugin,
3
+ PluginKey,
4
+ EditorState,
5
+ EditorView,
6
+ Transaction,
7
+ } from '@lblod/ember-rdfa-editor';
8
+ import { LocationType } from '@lblod/ember-rdfa-editor-lblod-plugins/components/location-plugin/map';
9
+
10
+ type PluginState = {
11
+ modalOpen: boolean;
12
+ locationType: LocationType | null;
13
+ };
14
+
15
+ type LocationModalMeta =
16
+ | { action: 'open_location_modal'; locationType?: LocationType }
17
+ | { action: 'close_location_modal' }
18
+ | undefined;
19
+
20
+ function isLocationMeta(meta: unknown): meta is LocationModalMeta {
21
+ if (!meta || typeof meta !== 'object') return false;
22
+ return 'action' in meta;
23
+ }
24
+
25
+ export const locationModalsPluginKey = new PluginKey<PluginState>(
26
+ 'LOCATION_MODALS_PLUGIN',
27
+ );
28
+
29
+ export function closeLocationModal(view: EditorView) {
30
+ const tr = view.state.tr;
31
+ tr.setMeta(locationModalsPluginKey, { action: 'close_location_modal' });
32
+ view.dispatch(tr);
33
+ }
34
+
35
+ export function openLocationModal(
36
+ view: EditorView,
37
+ locationType?: LocationType,
38
+ ) {
39
+ const tr = view.state.tr;
40
+ tr.setMeta(locationModalsPluginKey, {
41
+ action: 'open_location_modal',
42
+ locationType: locationType ?? null,
43
+ });
44
+ view.dispatch(tr);
45
+ }
46
+
47
+ export function openLocationModalCommand(locationType: LocationType) {
48
+ return function (state: EditorState, dispatch?: (tr: Transaction) => void) {
49
+ if (dispatch) {
50
+ const tr = state.tr;
51
+ tr.setMeta(locationModalsPluginKey, {
52
+ action: 'open_location_modal',
53
+ locationType,
54
+ });
55
+ dispatch(tr);
56
+ }
57
+ };
58
+ }
59
+
60
+ export function locationModalsPlugin() {
61
+ return new ProsePlugin<PluginState>({
62
+ key: locationModalsPluginKey,
63
+ state: {
64
+ init() {
65
+ return { modalOpen: false, locationType: null };
66
+ },
67
+ apply(tr, pluginState) {
68
+ const meta = tr.getMeta(locationModalsPluginKey);
69
+ if (!isLocationMeta(meta)) return pluginState;
70
+
71
+ if (meta?.action === 'open_location_modal') {
72
+ return {
73
+ modalOpen: true,
74
+ locationType: meta.locationType ?? null,
75
+ };
76
+ } else if (meta?.action === 'close_location_modal') {
77
+ return { modalOpen: false, locationType: null };
78
+ } else {
79
+ return pluginState;
80
+ }
81
+ },
82
+ },
83
+ });
84
+ }
85
+
86
+ export function getLocationModalsPluginState(
87
+ state: EditorState,
88
+ ): PluginState | undefined {
89
+ return locationModalsPluginKey.getState(state);
90
+ }
@@ -0,0 +1,68 @@
1
+ import {
2
+ createEmberNodeSpec,
3
+ createEmberNodeView,
4
+ EmberNodeConfig,
5
+ } from '@lblod/ember-rdfa-editor/utils/ember-node';
6
+ import { DOMOutputSpec, PNode } from '@lblod/ember-rdfa-editor';
7
+ import BlockLockedPlaceholderNodeView from '@lblod/ember-rdfa-editor-lblod-plugins/components/locked-placeholder-plugin/nodeviews/block';
8
+ import type { ComponentLike } from '@glint/template';
9
+
10
+ const parseDOM = [
11
+ {
12
+ tag: 'div',
13
+ getAttrs: (node: HTMLElement) => {
14
+ if (
15
+ node.dataset.lockedPlaceholder &&
16
+ node.dataset.lockedPlaceholderType === 'block'
17
+ ) {
18
+ const label = node.dataset.label;
19
+ const key = node.dataset.key;
20
+ return {
21
+ label,
22
+ key,
23
+ };
24
+ }
25
+ return false;
26
+ },
27
+ },
28
+ ];
29
+
30
+ const toDOM = (node: PNode): DOMOutputSpec => {
31
+ return [
32
+ 'div',
33
+ {
34
+ 'data-locked-placeholder': 'true',
35
+ 'data-locked-placeholder-type': 'block',
36
+ 'data-label': node.attrs['label'],
37
+ 'data-key': node.attrs['key'],
38
+ },
39
+ 0,
40
+ ];
41
+ };
42
+
43
+ const emberNodeConfig: EmberNodeConfig = {
44
+ name: 'block-locked-placeholder',
45
+ component: BlockLockedPlaceholderNodeView as unknown as ComponentLike,
46
+ inline: false,
47
+ group: 'block',
48
+ content: 'inline*',
49
+ atom: true,
50
+ draggable: false,
51
+ needsFFKludge: true,
52
+ editable: true,
53
+ selectable: true,
54
+ attrs: {
55
+ label: {
56
+ default: '',
57
+ },
58
+ key: {
59
+ default: undefined,
60
+ },
61
+ },
62
+ classNames: ['say-block-locked-placeholder'],
63
+ toDOM,
64
+ parseDOM: parseDOM,
65
+ };
66
+
67
+ export const blockLockedPlaceholder = createEmberNodeSpec(emberNodeConfig);
68
+ export const blockLockedPlaceholderView = createEmberNodeView(emberNodeConfig);
@@ -0,0 +1,68 @@
1
+ import {
2
+ createEmberNodeSpec,
3
+ createEmberNodeView,
4
+ EmberNodeConfig,
5
+ } from '@lblod/ember-rdfa-editor/utils/ember-node';
6
+ import { DOMOutputSpec, PNode } from '@lblod/ember-rdfa-editor';
7
+ import InlineLockedPlaceholderNodeView from '@lblod/ember-rdfa-editor-lblod-plugins/components/locked-placeholder-plugin/nodeviews/inline';
8
+ import type { ComponentLike } from '@glint/template';
9
+
10
+ const parseDOM = [
11
+ {
12
+ tag: 'span',
13
+ getAttrs: (node: HTMLElement) => {
14
+ if (
15
+ node.dataset.lockedPlaceholder &&
16
+ node.dataset.lockedPlaceholderType === 'inline'
17
+ ) {
18
+ const label = node.dataset.label;
19
+ const key = node.dataset.key;
20
+ return {
21
+ label,
22
+ key,
23
+ };
24
+ }
25
+ return false;
26
+ },
27
+ },
28
+ ];
29
+
30
+ const toDOM = (node: PNode): DOMOutputSpec => {
31
+ return [
32
+ 'span',
33
+ {
34
+ 'data-locked-placeholder': 'true',
35
+ 'data-locked-placeholder-type': 'inline',
36
+ 'data-label': node.attrs['label'],
37
+ 'data-key': node.attrs['key'],
38
+ },
39
+ 0,
40
+ ];
41
+ };
42
+
43
+ const emberNodeConfig: EmberNodeConfig = {
44
+ name: 'inline-locked-placeholder',
45
+ component: InlineLockedPlaceholderNodeView as unknown as ComponentLike,
46
+ inline: true,
47
+ group: 'inline',
48
+ content: 'inline*',
49
+ atom: true,
50
+ draggable: false,
51
+ needsFFKludge: true,
52
+ editable: true,
53
+ selectable: true,
54
+ attrs: {
55
+ label: {
56
+ default: '',
57
+ },
58
+ key: {
59
+ default: undefined,
60
+ },
61
+ },
62
+ classNames: ['say-inline-locked-placeholder'],
63
+ toDOM,
64
+ parseDOM: parseDOM,
65
+ };
66
+
67
+ export const inlineLockedPlaceholder = createEmberNodeSpec(emberNodeConfig);
68
+ export const inlineLockedPlaceholderView = createEmberNodeView(emberNodeConfig);
@@ -0,0 +1,89 @@
1
+ import { Node, EditorState } from '@lblod/ember-rdfa-editor';
2
+ import { DOMParser as ProseParser } from '@lblod/ember-rdfa-editor';
3
+ import {
4
+ transactionCombinator,
5
+ type TransactionCombinatorResult,
6
+ } from '@lblod/ember-rdfa-editor/utils/transaction-utils';
7
+
8
+ type PlaceholderWithPos = {
9
+ placeholder: Node;
10
+ pos: number;
11
+ };
12
+
13
+ type ReplacementValues = {
14
+ [key: string]: string;
15
+ };
16
+
17
+ export default function replaceLockedPlaceholderContent(
18
+ initialState: EditorState,
19
+ values: ReplacementValues | ((state: EditorState) => ReplacementValues),
20
+ ): TransactionCombinatorResult<boolean> {
21
+ const doc = initialState.doc;
22
+ const placeholdersWithPos: PlaceholderWithPos[] = [];
23
+ doc.descendants((node, pos) => {
24
+ if (
25
+ node.type.name === 'block_locked_placeholder' ||
26
+ node.type.name === 'inline_locked_placeholder'
27
+ ) {
28
+ placeholdersWithPos.push({
29
+ placeholder: node,
30
+ pos: pos,
31
+ });
32
+ return false;
33
+ }
34
+ return true;
35
+ });
36
+ placeholdersWithPos.reverse();
37
+ const monads = [];
38
+ const valuesResolved =
39
+ typeof values === 'function' ? values(initialState) : values;
40
+ for (const { placeholder, pos } of placeholdersWithPos) {
41
+ const key = placeholder.attrs.key as string;
42
+ const valueToReplace = valuesResolved[key];
43
+ if (!valueToReplace) continue;
44
+ if (typeof valueToReplace === 'string') {
45
+ const monad = replacePlaceholderWithHtml(
46
+ placeholder,
47
+ pos,
48
+ valueToReplace,
49
+ );
50
+ monads.push(monad);
51
+ } else {
52
+ const monad = replacePlaceholderWithProsemirrorNode(
53
+ placeholder,
54
+ pos,
55
+ valueToReplace,
56
+ );
57
+ monads.push(monad);
58
+ }
59
+ }
60
+ return transactionCombinator<boolean>(initialState)(monads);
61
+ }
62
+
63
+ function replacePlaceholderWithHtml(
64
+ placeholder: Node,
65
+ pos: number,
66
+ value: string,
67
+ ) {
68
+ return (state: EditorState) => {
69
+ const domParser = new DOMParser();
70
+ const contentFragment = ProseParser.fromSchema(state.schema).parse(
71
+ domParser.parseFromString(value, 'text/html'),
72
+ ).content;
73
+ const tr = state.tr;
74
+ tr.replaceWith(pos, pos + placeholder.nodeSize, contentFragment);
75
+ return { initialState: state, transaction: tr, result: true };
76
+ };
77
+ }
78
+
79
+ function replacePlaceholderWithProsemirrorNode(
80
+ placeholder: Node,
81
+ pos: number,
82
+ value: Node,
83
+ ) {
84
+ return (state: EditorState) => {
85
+ const tr = state.tr;
86
+ tr.replaceWith(pos, pos + placeholder.nodeSize, value);
87
+ return { initialState: state, transaction: tr, result: true };
88
+ };
89
+ }
@@ -1,20 +1,37 @@
1
- import { type PNode } from '@lblod/ember-rdfa-editor';
2
- import {
3
- composeMonads,
4
- type TransactionMonad,
5
- } from '@lblod/ember-rdfa-editor/utils/transaction-utils';
1
+ import { Attrs, type PNode } from '@lblod/ember-rdfa-editor';
2
+ import { SAY } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
3
+ import { getOutgoingTripleList } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/namespace';
4
+ import { isSome } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/option';
5
+ import { difference } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/set-utils';
6
+ import { SayDataFactory } from '@lblod/ember-rdfa-editor/core/say-data-factory';
6
7
  import {
7
8
  addPropertyToNode,
8
9
  getProperties,
9
10
  getSubject,
10
11
  removePropertyFromNode,
11
12
  } from '@lblod/ember-rdfa-editor/utils/rdfa-utils';
12
- import { SayDataFactory } from '@lblod/ember-rdfa-editor/core/say-data-factory';
13
- import { SAY } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/constants';
13
+ import {
14
+ composeMonads,
15
+ type TransactionMonad,
16
+ } from '@lblod/ember-rdfa-editor/utils/transaction-utils';
14
17
  import { isHierarchyNode } from '../structure-types';
15
18
 
19
+ const factory = new SayDataFactory();
20
+
21
+ /**
22
+ * Generate monads to recursively sync the RDFa links with the actual document structure
23
+ * of the given structure node and its descendants.
24
+ *
25
+ * Takes the document hierarchy as the source of truth, and updates the rdfa links to match.
26
+ *
27
+ * Currently this is being called every transaction that modifies any structure node,
28
+ * which includes typing inside one. As such, the implementation should take care to avoid
29
+ * generating unnecessary updates.
30
+ *
31
+ * @param parent A structure node
32
+ * @returns a list of monads which will update the structures as described
33
+ */
16
34
  function linkAllChildrenToParent(parent: PNode): TransactionMonad<boolean>[] {
17
- const factory = new SayDataFactory();
18
35
  const parentResource = getSubject(parent);
19
36
  if (!parentResource) {
20
37
  console.warn(
@@ -24,48 +41,104 @@ function linkAllChildrenToParent(parent: PNode): TransactionMonad<boolean>[] {
24
41
  }
25
42
  const monads: TransactionMonad<boolean>[] = [];
26
43
 
27
- // Remove existing links
28
- const existingLinks = getProperties(parent)?.filter((rel) =>
29
- SAY('hasPart').matches(rel.predicate),
30
- );
31
- existingLinks?.forEach((link) => {
32
- monads.push(
33
- removePropertyFromNode({ resource: parentResource, property: link }),
34
- );
35
- });
44
+ const childStructures: PNode[] = [];
36
45
 
46
+ // collect all direct structure-node descendants, skipping non-structure nodes
47
+ // which is why the rest of the function calls them "children" even if they might
48
+ // not be direct children in the strict sense
37
49
  parent.descendants((node) => {
38
50
  if (isHierarchyNode(node)) {
39
- const resource = getSubject(node);
40
- if (!resource) {
41
- console.warn(
42
- 'Trying to update RDFa links for a structure node with no subject',
43
- );
44
- return false;
45
- }
46
- const monad = addPropertyToNode({
47
- resource: parentResource,
48
- property: {
49
- predicate: SAY('hasPart').full,
50
- object: factory.resourceNode(resource),
51
- },
52
- });
53
- monads.push(monad);
54
-
55
- // Recurse for children
56
- monads.push(...linkAllChildrenToParent(node));
57
-
58
- // Avoid `descendants()` from handling children, we do that with recursion
51
+ childStructures.push(node);
59
52
  return false;
60
- } else {
61
- // Look for hierarchy nodes within
62
- return true;
63
53
  }
54
+ return true;
64
55
  });
56
+
57
+ const documentChildSubjects = new Set(
58
+ // As far as this function is concerned,
59
+ // it will handle child structures without a subject just fine, so we can filter them out
60
+ // Any dangling reference to them in the parent will be removed.
61
+ // However, it is definitely unexpected and likely will break other things
62
+ childStructures.map((node) => getSubject(node)).filter(isSome),
63
+ );
64
+
65
+ const parentProps = getProperties(parent) ?? [];
66
+ const rdfaChildSubjects = new Set(
67
+ parentProps
68
+ .filter((p) => SAY('hasPart').matches(p.predicate))
69
+ .map((p) => p.object.value),
70
+ );
71
+
72
+ // calculating this is highly worth doing, since there will usually be no differences
73
+ // and avoiding the extra transactions is very worth it
74
+ const toBeDeleted = difference(rdfaChildSubjects, documentChildSubjects);
75
+ const toBeAdded = difference(documentChildSubjects, rdfaChildSubjects);
76
+
77
+ // generate the monads to update the props
78
+ for (const subject of toBeDeleted) {
79
+ monads.push(...removeChildLinks(parent.attrs, parentResource, subject));
80
+ }
81
+ for (const subject of toBeAdded) {
82
+ monads.push(addChildLink(parentResource, subject));
83
+ }
84
+
85
+ // recurse down for each child
86
+ for (const child of childStructures) {
87
+ monads.push(...linkAllChildrenToParent(child));
88
+ }
89
+
65
90
  return monads;
66
91
  }
67
92
 
93
+ /**
94
+ * Generate monad to remove child from the parent's node attrs
95
+ *
96
+ * @param parentAttrs the prosemirror attrs of the parent
97
+ * @param parentSubject
98
+ * @param childSubject
99
+ * @returns list of monads to remove each property
100
+ *
101
+ * @private
102
+ */
103
+ function removeChildLinks(
104
+ parentAttrs: Attrs,
105
+ parentSubject: string,
106
+ childSubject: string,
107
+ ) {
108
+ const toRemove = getOutgoingTripleList(parentAttrs, SAY('hasPart')).filter(
109
+ (t) => t.object.value === childSubject,
110
+ );
111
+ return toRemove.map((prop) =>
112
+ removePropertyFromNode({ resource: parentSubject, property: prop }),
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Generate monad to add child to the parent's rdfa props
118
+ *
119
+ * @param parentSubject
120
+ * @param childSubject
121
+ * @returns monad to add the child to the parent with a 'hasPart' predicate
122
+ *
123
+ * @private
124
+ */
125
+ function addChildLink(parentSubject: string, childSubject: string) {
126
+ return addPropertyToNode({
127
+ resource: parentSubject,
128
+ property: {
129
+ predicate: SAY('hasPart').full,
130
+ object: factory.resourceNode(childSubject),
131
+ },
132
+ });
133
+ }
134
+
68
135
  // TODO add support for decision structure nodes to this to remove their current manual linking
136
+ /**
137
+ * Generate monads to update the rdfa-hierarchy of all structure nodes to match their document hierarchy
138
+ * @see {@link linkAllChildrenToParent} for more details
139
+ *
140
+ * @returns list of monads to perform the update on the whole doc
141
+ */
69
142
  export const regenerateRdfaLinks = composeMonads((state) => {
70
143
  const monads: TransactionMonad<boolean>[] = [];
71
144
  // First find all the nodes that are in the hierarchy, but do not have a parent that is part of
@@ -92,7 +92,7 @@ function humanReadableLabel(label: string) {
92
92
  }
93
93
 
94
94
  function createInsertPlaceDescriptionCommand(label: string) {
95
- return (state: EditorState, dispatch: (tr: Transaction) => void) => {
95
+ return (state: EditorState, dispatch?: (tr: Transaction) => void) => {
96
96
  if (dispatch) {
97
97
  let htmlToInsert = label;
98
98
  htmlToInsert = wrapVariableInHighlight(htmlToInsert);
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Calculate the set difference a - b between two sets
3
+ *
4
+ * @param a set a, or the left hand side of the operation
5
+ * @param b set b, or the right hand sidde of the operation
6
+ * @returns a-b
7
+ */
8
+ export function difference<T>(a: Set<T>, b: Set<T>): Set<T> {
9
+ const diff = new Set<T>();
10
+
11
+ for (const item of a) {
12
+ if (!b.has(item)) {
13
+ diff.add(item);
14
+ }
15
+ }
16
+
17
+ return diff;
18
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@lblod/ember-rdfa-editor-lblod-plugins/components/locked-placeholder-plugin/insert';
@@ -0,0 +1,12 @@
1
+ .say-inline-locked-placeholder {
2
+ background-color: var(--au-gray-200);
3
+ color: var(--au-gray-700);
4
+ border-color: var(--au-gray-300);
5
+ }
6
+
7
+ .say-block-locked-placeholder {
8
+ background-color: var(--au-gray-200);
9
+ color: var(--au-gray-700);
10
+ border-color: var(--au-gray-300);
11
+ padding: 1.2rem;
12
+ }
@@ -21,7 +21,6 @@ interface Signature {
21
21
  }
22
22
  export default class LocationPluginInsertComponent extends Component<Signature> {
23
23
  intl: IntlService;
24
- modalOpen: boolean;
25
24
  chosenPoint: GeoPos | undefined;
26
25
  isLoading: boolean;
27
26
  constructor(owner: unknown, args: Signature['Args']);
@@ -30,7 +29,6 @@ export default class LocationPluginInsertComponent extends Component<Signature>
30
29
  savedLocation: GeoPos | undefined;
31
30
  placeName: string;
32
31
  savedArea: GeoPos[] | undefined;
33
- locationType: LocationType;
34
32
  selectedLocationNode: ResolvedPNode | null;
35
33
  get controller(): SayController;
36
34
  get nodeContentsUtils(): NodeContentsUtils;
@@ -38,7 +36,9 @@ export default class LocationPluginInsertComponent extends Component<Signature>
38
36
  get modalTitle(): string;
39
37
  get canInsert(): boolean;
40
38
  get disableConfirm(): boolean;
39
+ get modalOpen(): boolean | undefined;
41
40
  setLocationType(type: LocationType): void;
41
+ get locationType(): LocationType;
42
42
  setChosenPoint(point: GlobalCoordinates): void;
43
43
  setPlaceName(name: string): void;
44
44
  closeModal(): void;
@@ -0,0 +1,24 @@
1
+ import Component from '@glimmer/component';
2
+ import { SayController } from '@lblod/ember-rdfa-editor';
3
+ import { type LocationPluginConfig } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/location-plugin/node';
4
+ interface Signature {
5
+ Args: {
6
+ controller: SayController;
7
+ config: LocationPluginConfig;
8
+ templateMode?: boolean;
9
+ };
10
+ Element: HTMLLIElement;
11
+ }
12
+ export default class LockedPlaceholderPluginInsert extends Component<Signature> {
13
+ key: string | null;
14
+ label: string | null;
15
+ type: string;
16
+ get selectedPlaceholderNode(): import("prosemirror-model").Node | undefined;
17
+ get controller(): SayController;
18
+ get canInsert(): boolean;
19
+ insertPlaceholder(): void;
20
+ updateKey(event: InputEvent): void;
21
+ updateLabel(event: InputEvent): void;
22
+ updateType(type: string): void;
23
+ }
24
+ export {};
@@ -0,0 +1,14 @@
1
+ import Component from '@glimmer/component';
2
+ import { PNode } from '@lblod/ember-rdfa-editor';
3
+ interface Sig {
4
+ Args: {
5
+ node: PNode;
6
+ };
7
+ Blocks: {
8
+ default: [];
9
+ };
10
+ }
11
+ export default class BlockLockedPlaceholder extends Component<Sig> {
12
+ get label(): any;
13
+ }
14
+ export {};