@lblod/ember-rdfa-editor-lblod-plugins 36.0.0 → 37.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.
- package/CHANGELOG.md +22 -0
- package/addon/components/citation-plugin/citation-card.hbs +1 -0
- package/addon/components/decision-plugin/decision-plugin-card.gts +4 -0
- package/addon/components/document-validation-plugin/card.gts +2 -0
- package/addon/components/location-plugin/insert.gts +27 -19
- package/addon/components/locked-placeholder-plugin/insert.gts +161 -0
- package/addon/components/locked-placeholder-plugin/nodeviews/block.gts +23 -0
- package/addon/components/locked-placeholder-plugin/nodeviews/inline.gts +23 -0
- package/addon/components/mandatee-table-plugin/configure.gts +1 -0
- package/addon/components/placeholder-utils-plugin/edit.gts +1 -0
- package/addon/components/roadsign-regulation-plugin/roadsigns-table.gts +1 -1
- package/addon/components/structure-plugin/control-card.gts +1 -0
- package/addon/components/template-comments-plugin/edit-card.hbs +1 -0
- package/addon/components/variable-plugin/address/edit.hbs +1 -0
- package/addon/components/variable-plugin/autofilled/edit.gts +1 -0
- package/addon/components/variable-plugin/codelist/edit.gts +1 -0
- package/addon/components/variable-plugin/date/edit.hbs +1 -0
- package/addon/components/variable-plugin/insert-variable-card.hbs +1 -0
- package/addon/components/variable-plugin/location/edit.hbs +1 -0
- package/addon/components/variable-plugin/number/nodeview.hbs +1 -0
- package/addon/components/variable-plugin/person/edit.hbs +1 -0
- package/addon/plugins/decision-plugin/commands/insert-article-container.ts +3 -0
- package/addon/plugins/decision-plugin/commands/insert-description.ts +4 -1
- package/addon/plugins/decision-plugin/commands/insert-motivation.ts +3 -1
- package/addon/plugins/decision-plugin/commands/insert-title.ts +3 -1
- package/addon/plugins/document-validation-plugin/common-fixes.ts +4 -0
- package/addon/plugins/location-plugin/contextual-actions/index.ts +84 -0
- package/addon/plugins/location-plugin/index.ts +88 -0
- package/addon/plugins/locked-placeholder-plugin/nodes/block-locked-placeholder.ts +68 -0
- package/addon/plugins/locked-placeholder-plugin/nodes/inline-locked-placeholder.ts +68 -0
- package/addon/plugins/locked-placeholder-plugin/utils/replace-content-function.ts +89 -0
- package/addon/plugins/structure-plugin/monads/regenerate-rdfa-links.ts +113 -40
- package/addon/plugins/variable-plugin/contextual-actions/index.ts +1 -1
- package/addon/utils/set-utils.ts +18 -0
- package/app/components/locked-placeholder-plugin/insert.js +1 -0
- package/app/styles/locked-placeholder-plugin.scss +12 -0
- package/declarations/addon/components/location-plugin/insert.d.ts +2 -2
- package/declarations/addon/components/locked-placeholder-plugin/insert.d.ts +24 -0
- package/declarations/addon/components/locked-placeholder-plugin/nodeviews/block.d.ts +14 -0
- package/declarations/addon/components/locked-placeholder-plugin/nodeviews/inline.d.ts +14 -0
- package/declarations/addon/plugins/decision-plugin/commands/insert-article-container.d.ts +2 -1
- package/declarations/addon/plugins/decision-plugin/commands/insert-description.d.ts +2 -1
- package/declarations/addon/plugins/decision-plugin/commands/insert-motivation.d.ts +2 -1
- package/declarations/addon/plugins/decision-plugin/commands/insert-title.d.ts +2 -1
- package/declarations/addon/plugins/location-plugin/contextual-actions/index.d.ts +14 -0
- package/declarations/addon/plugins/location-plugin/index.d.ts +13 -0
- package/declarations/addon/plugins/locked-placeholder-plugin/nodes/block-locked-placeholder.d.ts +2 -0
- package/declarations/addon/plugins/locked-placeholder-plugin/nodes/inline-locked-placeholder.d.ts +2 -0
- package/declarations/addon/plugins/locked-placeholder-plugin/utils/replace-content-function.d.ts +7 -0
- package/declarations/addon/plugins/structure-plugin/monads/regenerate-rdfa-links.d.ts +6 -0
- package/declarations/addon/plugins/variable-plugin/contextual-actions/index.d.ts +1 -1
- package/declarations/addon/utils/set-utils.d.ts +8 -0
- package/package.json +4 -4
- package/translations/en-US.yaml +16 -0
- 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,88 @@
|
|
|
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
|
+
const tr = state.tr;
|
|
50
|
+
tr.setMeta(locationModalsPluginKey, {
|
|
51
|
+
action: 'open_location_modal',
|
|
52
|
+
locationType,
|
|
53
|
+
});
|
|
54
|
+
dispatch(tr);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function locationModalsPlugin() {
|
|
59
|
+
return new ProsePlugin<PluginState>({
|
|
60
|
+
key: locationModalsPluginKey,
|
|
61
|
+
state: {
|
|
62
|
+
init() {
|
|
63
|
+
return { modalOpen: false, locationType: null };
|
|
64
|
+
},
|
|
65
|
+
apply(tr, pluginState) {
|
|
66
|
+
const meta = tr.getMeta(locationModalsPluginKey);
|
|
67
|
+
if (!isLocationMeta(meta)) return pluginState;
|
|
68
|
+
|
|
69
|
+
if (meta?.action === 'open_location_modal') {
|
|
70
|
+
return {
|
|
71
|
+
modalOpen: true,
|
|
72
|
+
locationType: meta.locationType ?? null,
|
|
73
|
+
};
|
|
74
|
+
} else if (meta?.action === 'close_location_modal') {
|
|
75
|
+
return { modalOpen: false, locationType: null };
|
|
76
|
+
} else {
|
|
77
|
+
return pluginState;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getLocationModalsPluginState(
|
|
85
|
+
state: EditorState,
|
|
86
|
+
): PluginState | undefined {
|
|
87
|
+
return locationModalsPluginKey.getState(state);
|
|
88
|
+
}
|
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
} from '@lblod/ember-rdfa-editor/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 {
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {};
|