@byline/admin 4.1.0 → 4.3.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/dist/fields/array/array-field.d.ts +12 -2
- package/dist/fields/array/array-field.js +46 -20
- package/dist/fields/array/array-field.module.js +5 -1
- package/dist/fields/array/array-field_module.css +23 -0
- package/dist/fields/blocks/blocks-field.js +34 -12
- package/dist/fields/blocks/blocks-field.module.js +2 -0
- package/dist/fields/blocks/blocks-field_module.css +37 -0
- package/dist/fields/code/code-editor.d.ts +20 -0
- package/dist/fields/code/code-editor.js +239 -0
- package/dist/fields/code/code-field.d.ts +21 -0
- package/dist/fields/code/code-field.js +124 -0
- package/dist/fields/code/code-field.module.js +7 -0
- package/dist/fields/code/code-field_module.css +58 -0
- package/dist/fields/draggable-context-menu.js +2 -1
- package/dist/fields/field-admin.d.ts +15 -0
- package/dist/fields/field-admin.js +11 -0
- package/dist/fields/field-admin.test.node.d.ts +8 -0
- package/dist/fields/field-helpers.js +1 -0
- package/dist/fields/field-renderer.d.ts +11 -2
- package/dist/fields/field-renderer.js +21 -4
- package/dist/fields/group/group-field.d.ts +23 -2
- package/dist/fields/group/group-field.js +7 -3
- package/dist/fields/relation/relation-picker.js +8 -2
- package/dist/fields/select/select-field.js +4 -2
- package/dist/fields/select/select-field.module.js +1 -0
- package/dist/fields/select/select-field_module.css +4 -0
- package/dist/fields/sortable-item.d.ts +6 -0
- package/dist/fields/sortable-item.js +44 -25
- package/dist/fields/text/text-field_module.css +1 -0
- package/dist/fields/text-area/text-area-field_module.css +1 -0
- package/dist/forms/form-context.js +1 -1
- package/dist/forms/form-renderer.d.ts +1 -1
- package/dist/forms/form-renderer.js +3 -1
- package/dist/forms/tree-placement-widget.d.ts +1 -1
- package/package.json +23 -10
- package/src/fields/array/array-field.module.css +30 -0
- package/src/fields/array/array-field.tsx +74 -20
- package/src/fields/blocks/blocks-field.module.css +53 -0
- package/src/fields/blocks/blocks-field.tsx +38 -1
- package/src/fields/code/code-editor.tsx +246 -0
- package/src/fields/code/code-field.module.css +84 -0
- package/src/fields/code/code-field.tsx +191 -0
- package/src/fields/draggable-context-menu.tsx +9 -1
- package/src/fields/field-admin.test.node.ts +49 -0
- package/src/fields/field-admin.ts +39 -0
- package/src/fields/field-helpers.ts +1 -0
- package/src/fields/field-renderer.tsx +33 -2
- package/src/fields/field-services-types.ts +1 -1
- package/src/fields/group/group-field.tsx +29 -2
- package/src/fields/relation/relation-picker.tsx +11 -0
- package/src/fields/select/select-field.module.css +5 -0
- package/src/fields/select/select-field.tsx +9 -2
- package/src/fields/sortable-item.tsx +115 -34
- package/src/fields/text/text-field.module.css +1 -0
- package/src/fields/text-area/text-area-field.module.css +1 -0
- package/src/forms/form-context.tsx +9 -1
- package/src/forms/form-renderer.tsx +3 -1
- package/src/forms/tree-placement-widget.tsx +1 -1
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import type { ArrayField as ArrayFieldType } from '@byline/core';
|
|
9
|
-
export declare const ArrayField: ({ field, defaultValue, path, disableSorting, collectionPath, contentLocale, }: {
|
|
8
|
+
import type { ArrayField as ArrayFieldType, FieldAdminConfig } from '@byline/core';
|
|
9
|
+
export declare const ArrayField: ({ field, defaultValue, path, disableSorting, collectionPath, contentLocale, fieldAdmin, }: {
|
|
10
10
|
field: ArrayFieldType;
|
|
11
11
|
defaultValue: any;
|
|
12
12
|
path: string;
|
|
@@ -24,4 +24,14 @@ export declare const ArrayField: ({ field, defaultValue, path, disableSorting, c
|
|
|
24
24
|
* can render their locale badge.
|
|
25
25
|
*/
|
|
26
26
|
contentLocale?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Admin overrides for the array's child fields, keyed by dotted,
|
|
29
|
+
* index-free schema paths relative to this array ('answer',
|
|
30
|
+
* 'filesGroup.publicationFile'). Schema paths address declarations, so
|
|
31
|
+
* one entry applies to that field in every item. Arrives pre-sliced from
|
|
32
|
+
* the enclosing widget (`FieldRenderer` / `GroupField`); exact-name
|
|
33
|
+
* entries apply to the child, deeper entries are re-sliced and threaded
|
|
34
|
+
* on (see `sliceFieldAdmin`).
|
|
35
|
+
*/
|
|
36
|
+
fieldAdmin?: Record<string, FieldAdminConfig>;
|
|
27
37
|
}) => import("react").JSX.Element;
|
|
@@ -3,12 +3,13 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { useTranslation } from "@byline/i18n/react";
|
|
4
4
|
import { DraggableSortable, IconButton, PlusIcon, moveItem } from "@byline/ui/react";
|
|
5
5
|
import classnames from "classnames";
|
|
6
|
+
import { sliceFieldAdmin } from "../field-admin.js";
|
|
6
7
|
import { defaultScalarForField } from "../field-helpers.js";
|
|
7
8
|
import { FieldRenderer } from "../field-renderer.js";
|
|
8
|
-
import { SortableItem } from "../sortable-item.js";
|
|
9
|
+
import { SortableItem, StaticItem } from "../sortable-item.js";
|
|
9
10
|
import { useFormContext } from "../../forms/form-context.js";
|
|
10
11
|
import array_field_module from "./array-field.module.js";
|
|
11
|
-
const ArrayField = ({ field, defaultValue, path, disableSorting = false, collectionPath, contentLocale })=>{
|
|
12
|
+
const ArrayField = ({ field, defaultValue, path, disableSorting = false, collectionPath, contentLocale, fieldAdmin })=>{
|
|
12
13
|
const { appendPatch, getFieldValue, getFieldValues, setFieldStore } = useFormContext();
|
|
13
14
|
const { t } = useTranslation('byline-admin');
|
|
14
15
|
const [items, setItems] = useState([]);
|
|
@@ -113,6 +114,7 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
113
114
|
const initial = item[childField.name];
|
|
114
115
|
if ('group' === childField.type && childField.fields && childField.fields.length > 0) {
|
|
115
116
|
const groupData = initial && 'object' == typeof initial ? initial : {};
|
|
117
|
+
const groupAdmin = sliceFieldAdmin(fieldAdmin, childField.name);
|
|
116
118
|
return /*#__PURE__*/ jsxs("div", {
|
|
117
119
|
className: classnames('byline-field-array-group-fields', array_field_module["group-fields"]),
|
|
118
120
|
children: [
|
|
@@ -126,7 +128,10 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
126
128
|
basePath: `${arrayElementPath}.${childField.name}`,
|
|
127
129
|
disableSorting: true,
|
|
128
130
|
collectionPath: collectionPath,
|
|
129
|
-
contentLocale: contentLocale
|
|
131
|
+
contentLocale: contentLocale,
|
|
132
|
+
components: groupAdmin?.[innerField.name]?.components,
|
|
133
|
+
editor: groupAdmin?.[innerField.name]?.editor,
|
|
134
|
+
fieldAdmin: sliceFieldAdmin(groupAdmin, innerField.name)
|
|
130
135
|
}, innerField.name))
|
|
131
136
|
]
|
|
132
137
|
}, childField.name);
|
|
@@ -137,12 +142,17 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
137
142
|
basePath: arrayElementPath,
|
|
138
143
|
disableSorting: true,
|
|
139
144
|
collectionPath: collectionPath,
|
|
140
|
-
contentLocale: contentLocale
|
|
145
|
+
contentLocale: contentLocale,
|
|
146
|
+
components: fieldAdmin?.[childField.name]?.components,
|
|
147
|
+
editor: fieldAdmin?.[childField.name]?.editor,
|
|
148
|
+
fieldAdmin: sliceFieldAdmin(fieldAdmin, childField.name)
|
|
141
149
|
}, childField.name);
|
|
142
150
|
});
|
|
143
|
-
const
|
|
144
|
-
if (disableSorting) return /*#__PURE__*/ jsx(
|
|
145
|
-
|
|
151
|
+
const itemLabel = `${field.label ?? field.name} ${index + 1}`;
|
|
152
|
+
if (disableSorting) return /*#__PURE__*/ jsx(StaticItem, {
|
|
153
|
+
label: itemLabel,
|
|
154
|
+
onAddBelow: ()=>handleInsertBelow(index),
|
|
155
|
+
onRemove: ()=>handleRemoveItem(index),
|
|
146
156
|
children: /*#__PURE__*/ jsx("div", {
|
|
147
157
|
className: classnames('byline-field-array-group-fields', array_field_module["group-fields"]),
|
|
148
158
|
children: innerBody
|
|
@@ -150,7 +160,7 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
150
160
|
}, itemWrapper.id);
|
|
151
161
|
return /*#__PURE__*/ jsx(SortableItem, {
|
|
152
162
|
id: itemWrapper.id,
|
|
153
|
-
label:
|
|
163
|
+
label: itemLabel,
|
|
154
164
|
onAddBelow: ()=>handleInsertBelow(index),
|
|
155
165
|
onRemove: ()=>handleRemoveItem(index),
|
|
156
166
|
children: /*#__PURE__*/ jsx("div", {
|
|
@@ -159,31 +169,47 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
159
169
|
})
|
|
160
170
|
}, itemWrapper.id);
|
|
161
171
|
};
|
|
172
|
+
const addRow = /*#__PURE__*/ jsxs("div", {
|
|
173
|
+
className: classnames('byline-field-array-add-row', array_field_module["add-row"]),
|
|
174
|
+
children: [
|
|
175
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
176
|
+
onClick: ()=>{
|
|
177
|
+
handleAddItem();
|
|
178
|
+
},
|
|
179
|
+
"aria-label": t('fields.array.addItemAriaLabel'),
|
|
180
|
+
children: /*#__PURE__*/ jsx(PlusIcon, {})
|
|
181
|
+
}),
|
|
182
|
+
/*#__PURE__*/ jsx("button", {
|
|
183
|
+
type: "button",
|
|
184
|
+
tabIndex: -1,
|
|
185
|
+
onClick: ()=>{
|
|
186
|
+
handleAddItem();
|
|
187
|
+
},
|
|
188
|
+
className: classnames('byline-field-array-add-label', array_field_module["add-label"]),
|
|
189
|
+
children: t('fields.array.addItem')
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
});
|
|
162
193
|
return /*#__PURE__*/ jsxs("div", {
|
|
163
194
|
className: `byline-field-array ${field.name}`,
|
|
164
195
|
children: [
|
|
165
|
-
|
|
196
|
+
field.label && /*#__PURE__*/ jsx("h3", {
|
|
166
197
|
className: classnames('byline-field-array-title', array_field_module.title),
|
|
167
198
|
children: field.label
|
|
168
199
|
}),
|
|
169
|
-
disableSorting ? /*#__PURE__*/
|
|
200
|
+
disableSorting ? /*#__PURE__*/ jsxs("div", {
|
|
170
201
|
className: classnames('byline-field-array-stack', array_field_module.stack),
|
|
171
|
-
children:
|
|
202
|
+
children: [
|
|
203
|
+
items.map((item, index)=>renderItem(item, index)),
|
|
204
|
+
addRow
|
|
205
|
+
]
|
|
172
206
|
}) : /*#__PURE__*/ jsxs(DraggableSortable, {
|
|
173
207
|
ids: items.map((i)=>i.id),
|
|
174
208
|
onDragEnd: handleDragEnd,
|
|
175
209
|
className: classnames('byline-field-array-stack', array_field_module.stack),
|
|
176
210
|
children: [
|
|
177
211
|
items.map((item, index)=>renderItem(item, index)),
|
|
178
|
-
|
|
179
|
-
children: /*#__PURE__*/ jsx(IconButton, {
|
|
180
|
-
onClick: ()=>{
|
|
181
|
-
handleAddItem();
|
|
182
|
-
},
|
|
183
|
-
"aria-label": t('fields.array.addItemAriaLabel'),
|
|
184
|
-
children: /*#__PURE__*/ jsx(PlusIcon, {})
|
|
185
|
-
})
|
|
186
|
-
})
|
|
212
|
+
addRow
|
|
187
213
|
]
|
|
188
214
|
})
|
|
189
215
|
]
|
|
@@ -6,6 +6,10 @@ const array_field_module = {
|
|
|
6
6
|
"group-header": "group-header-KJxpIV",
|
|
7
7
|
groupHeader: "group-header-KJxpIV",
|
|
8
8
|
"group-fields": "group-fields-Bmi_hA",
|
|
9
|
-
groupFields: "group-fields-Bmi_hA"
|
|
9
|
+
groupFields: "group-fields-Bmi_hA",
|
|
10
|
+
"add-row": "add-row-bCHtVe",
|
|
11
|
+
addRow: "add-row-bCHtVe",
|
|
12
|
+
"add-label": "add-label-KdsV2D",
|
|
13
|
+
addLabel: "add-label-KdsV2D"
|
|
10
14
|
};
|
|
11
15
|
export default array_field_module;
|
|
@@ -30,3 +30,26 @@
|
|
|
30
30
|
display: flex;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
:is(.add-row-bCHtVe, .byline-field-array-add-row) {
|
|
34
|
+
color: var(--muted);
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: var(--spacing-8);
|
|
37
|
+
font-size: .9rem;
|
|
38
|
+
display: flex;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:is(.add-label-KdsV2D, .byline-field-array-add-label) {
|
|
42
|
+
appearance: none;
|
|
43
|
+
font: inherit;
|
|
44
|
+
color: inherit;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
background: none;
|
|
47
|
+
border: none;
|
|
48
|
+
padding: 0;
|
|
49
|
+
transition: color .12s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:is(.add-row-bCHtVe:hover .add-label-KdsV2D, .byline-field-array-add-row:hover .byline-field-array-add-label) {
|
|
53
|
+
color: var(--text);
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { getClientConfig } from "@byline/core";
|
|
3
4
|
import { useTranslation } from "@byline/i18n/react";
|
|
4
5
|
import { Card, CloseIcon, DraggableSortable, IconButton, Modal, PlusIcon, moveItem } from "@byline/ui/react";
|
|
5
6
|
import classnames from "classnames";
|
|
@@ -17,6 +18,11 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
|
|
|
17
18
|
const availableBlocks = useMemo(()=>field.blocks ?? [], [
|
|
18
19
|
field.blocks
|
|
19
20
|
]);
|
|
21
|
+
const blockAdminByType = useMemo(()=>{
|
|
22
|
+
const map = new Map();
|
|
23
|
+
for (const entry of getClientConfig().blockAdmin ?? [])map.set(entry.blockType, entry);
|
|
24
|
+
return map;
|
|
25
|
+
}, []);
|
|
20
26
|
const [selectedBlockName, setSelectedBlockName] = useState(()=>availableBlocks[0]?.blockType ?? '');
|
|
21
27
|
useEffect(()=>{
|
|
22
28
|
if (null == selectedBlockName || !availableBlocks.some((b)=>b.blockType === selectedBlockName)) setSelectedBlockName(availableBlocks[0]?.blockType ?? '');
|
|
@@ -130,7 +136,9 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
|
|
|
130
136
|
},
|
|
131
137
|
defaultValue: fieldData,
|
|
132
138
|
path: arrayElementPath,
|
|
133
|
-
|
|
139
|
+
disableSorting: false,
|
|
140
|
+
contentLocale: contentLocale,
|
|
141
|
+
fieldAdmin: blockAdminByType.get(item._type)?.fields
|
|
134
142
|
}, subField.blockType);
|
|
135
143
|
return /*#__PURE__*/ jsx(SortableItem, {
|
|
136
144
|
id: itemWrapper.id,
|
|
@@ -153,17 +161,31 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
|
|
|
153
161
|
className: classnames('byline-field-blocks-stack', blocks_field_module.stack),
|
|
154
162
|
children: [
|
|
155
163
|
items.map((item, index)=>renderItem(item, index)),
|
|
156
|
-
/*#__PURE__*/
|
|
164
|
+
/*#__PURE__*/ jsxs("div", {
|
|
157
165
|
className: classnames('byline-field-blocks-add-row', blocks_field_module["add-row"]),
|
|
158
|
-
children:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
children: [
|
|
167
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
168
|
+
onClick: ()=>{
|
|
169
|
+
setPendingInsertIndex(null);
|
|
170
|
+
setShowAddBlockModal(true);
|
|
171
|
+
},
|
|
172
|
+
disabled: !selectedBlockName,
|
|
173
|
+
"aria-label": t('fields.blocks.addBlockAriaLabel'),
|
|
174
|
+
variant: "outlined",
|
|
175
|
+
children: /*#__PURE__*/ jsx(PlusIcon, {})
|
|
176
|
+
}),
|
|
177
|
+
/*#__PURE__*/ jsx("button", {
|
|
178
|
+
type: "button",
|
|
179
|
+
tabIndex: -1,
|
|
180
|
+
disabled: !selectedBlockName,
|
|
181
|
+
onClick: ()=>{
|
|
182
|
+
setPendingInsertIndex(null);
|
|
183
|
+
setShowAddBlockModal(true);
|
|
184
|
+
},
|
|
185
|
+
className: classnames('byline-field-blocks-add-label', blocks_field_module["add-label"]),
|
|
186
|
+
children: t('fields.blocks.addBlock')
|
|
187
|
+
})
|
|
188
|
+
]
|
|
167
189
|
})
|
|
168
190
|
]
|
|
169
191
|
}),
|
|
@@ -176,7 +198,7 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
|
|
|
176
198
|
},
|
|
177
199
|
children: /*#__PURE__*/ jsxs(Modal.Container, {
|
|
178
200
|
style: {
|
|
179
|
-
maxWidth: '
|
|
201
|
+
maxWidth: '700px'
|
|
180
202
|
},
|
|
181
203
|
children: [
|
|
182
204
|
/*#__PURE__*/ jsxs(Modal.Header, {
|
|
@@ -4,6 +4,8 @@ const blocks_field_module = {
|
|
|
4
4
|
stack: "stack-Xxa89J",
|
|
5
5
|
"add-row": "add-row-gEqjM_",
|
|
6
6
|
addRow: "add-row-gEqjM_",
|
|
7
|
+
"add-label": "add-label-jiOURu",
|
|
8
|
+
addLabel: "add-label-jiOURu",
|
|
7
9
|
"modal-head": "modal-head-BTxR9C",
|
|
8
10
|
modalHead: "modal-head-BTxR9C",
|
|
9
11
|
"modal-title": "modal-title-jwOfU_",
|
|
@@ -11,11 +11,32 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
:is(.add-row-gEqjM_, .byline-field-blocks-add-row) {
|
|
14
|
+
color: var(--muted);
|
|
14
15
|
align-items: center;
|
|
15
16
|
gap: var(--spacing-8);
|
|
17
|
+
font-size: .9rem;
|
|
16
18
|
display: flex;
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
:is(.add-label-jiOURu, .byline-field-blocks-add-label) {
|
|
22
|
+
appearance: none;
|
|
23
|
+
font: inherit;
|
|
24
|
+
color: inherit;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
background: none;
|
|
27
|
+
border: none;
|
|
28
|
+
padding: 0;
|
|
29
|
+
transition: color .12s;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
:is(.add-label-jiOURu:disabled, .byline-field-blocks-add-label:disabled) {
|
|
33
|
+
cursor: default;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
:is(.add-row-gEqjM_:hover .add-label-jiOURu:not(:disabled), .byline-field-blocks-add-row:hover .byline-field-blocks-add-label:not(:disabled)) {
|
|
37
|
+
color: var(--text);
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
:is(.modal-head-BTxR9C, .byline-field-blocks-modal-head) {
|
|
20
41
|
margin-bottom: var(--spacing-8);
|
|
21
42
|
padding-top: 1rem;
|
|
@@ -28,6 +49,22 @@
|
|
|
28
49
|
|
|
29
50
|
:is(.modal-content-S12b_T, .byline-field-blocks-card-cursor) {
|
|
30
51
|
cursor: pointer;
|
|
52
|
+
overscroll-behavior: contain;
|
|
53
|
+
min-height: 0;
|
|
54
|
+
max-height: 80dvh;
|
|
55
|
+
overflow-y: auto;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@media (min-width: 40rem) {
|
|
59
|
+
:is(.modal-content-S12b_T, .byline-field-blocks-card-cursor) {
|
|
60
|
+
max-height: 70dvh;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@media (min-width: 64rem) {
|
|
65
|
+
:is(.modal-content-S12b_T, .byline-field-blocks-card-cursor) {
|
|
66
|
+
max-height: 60dvh;
|
|
67
|
+
}
|
|
31
68
|
}
|
|
32
69
|
|
|
33
70
|
:is(.grid-gALL6j, .byline-field-blocks-grid) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
export interface CodeEditorProps {
|
|
9
|
+
id?: string;
|
|
10
|
+
/** Initial document. External updates sync only while the editor is unfocused. */
|
|
11
|
+
value: string;
|
|
12
|
+
/** Highlight language (canonical name or alias). Unknown → plain text. */
|
|
13
|
+
language?: string;
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
|
+
ariaInvalid?: boolean;
|
|
17
|
+
ariaDescribedBy?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const CodeEditor: ({ id, value, language, readOnly, onChange, ariaInvalid, ariaDescribedBy, }: CodeEditorProps) => import("react").JSX.Element;
|
|
20
|
+
export default CodeEditor;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { defaultKeymap, history as commands_history, historyKeymap, indentWithTab } from "@codemirror/commands";
|
|
4
|
+
import { HighlightStyle, bracketMatching, syntaxHighlighting } from "@codemirror/language";
|
|
5
|
+
import { Compartment, EditorState } from "@codemirror/state";
|
|
6
|
+
import { EditorView, keymap, lineNumbers } from "@codemirror/view";
|
|
7
|
+
import { tags } from "@lezer/highlight";
|
|
8
|
+
const LANGUAGE_LOADERS = {
|
|
9
|
+
javascript: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript()),
|
|
10
|
+
jsx: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript({
|
|
11
|
+
jsx: true
|
|
12
|
+
})),
|
|
13
|
+
typescript: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript({
|
|
14
|
+
typescript: true
|
|
15
|
+
})),
|
|
16
|
+
tsx: ()=>import("@codemirror/lang-javascript").then((m)=>m.javascript({
|
|
17
|
+
jsx: true,
|
|
18
|
+
typescript: true
|
|
19
|
+
})),
|
|
20
|
+
json: ()=>import("@codemirror/lang-json").then((m)=>m.json()),
|
|
21
|
+
html: ()=>import("@codemirror/lang-html").then((m)=>m.html()),
|
|
22
|
+
css: ()=>import("@codemirror/lang-css").then((m)=>m.css()),
|
|
23
|
+
markdown: ()=>import("@codemirror/lang-markdown").then((m)=>m.markdown()),
|
|
24
|
+
python: ()=>import("@codemirror/lang-python").then((m)=>m.python()),
|
|
25
|
+
sql: ()=>import("@codemirror/lang-sql").then((m)=>m.sql()),
|
|
26
|
+
yaml: ()=>import("@codemirror/lang-yaml").then((m)=>m.yaml())
|
|
27
|
+
};
|
|
28
|
+
const LANGUAGE_ALIASES = {
|
|
29
|
+
js: "javascript",
|
|
30
|
+
ts: "typescript",
|
|
31
|
+
md: 'markdown',
|
|
32
|
+
py: 'python',
|
|
33
|
+
yml: 'yaml'
|
|
34
|
+
};
|
|
35
|
+
const resolveLanguageLoader = (language)=>{
|
|
36
|
+
if (!language) return null;
|
|
37
|
+
const key = LANGUAGE_ALIASES[language] ?? language;
|
|
38
|
+
return LANGUAGE_LOADERS[key] ?? null;
|
|
39
|
+
};
|
|
40
|
+
const bylineCodeTheme = EditorView.theme({
|
|
41
|
+
'&': {
|
|
42
|
+
backgroundColor: 'var(--byline-code-bg)',
|
|
43
|
+
color: 'var(--byline-code-fg)',
|
|
44
|
+
fontSize: 'var(--byline-code-font-size, 13px)',
|
|
45
|
+
border: '1px solid var(--byline-code-border)',
|
|
46
|
+
borderRadius: 'var(--byline-code-radius, 4px)'
|
|
47
|
+
},
|
|
48
|
+
'&.cm-focused': {
|
|
49
|
+
outline: '2px solid var(--byline-code-focus-ring)',
|
|
50
|
+
outlineOffset: '-1px'
|
|
51
|
+
},
|
|
52
|
+
'.cm-content': {
|
|
53
|
+
fontFamily: 'var(--byline-code-font, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)',
|
|
54
|
+
caretColor: 'var(--byline-code-caret)',
|
|
55
|
+
minHeight: 'var(--byline-code-min-height, 6rem)'
|
|
56
|
+
},
|
|
57
|
+
'.cm-scroller': {
|
|
58
|
+
fontFamily: 'inherit',
|
|
59
|
+
maxHeight: 'var(--byline-code-max-height, 32rem)'
|
|
60
|
+
},
|
|
61
|
+
'.cm-gutters': {
|
|
62
|
+
backgroundColor: 'var(--byline-code-gutter-bg)',
|
|
63
|
+
color: 'var(--byline-code-gutter-fg)',
|
|
64
|
+
border: 'none',
|
|
65
|
+
borderRight: '1px solid var(--byline-code-border)'
|
|
66
|
+
},
|
|
67
|
+
'.cm-activeLine': {
|
|
68
|
+
backgroundColor: 'var(--byline-code-active-line)'
|
|
69
|
+
},
|
|
70
|
+
'.cm-activeLineGutter': {
|
|
71
|
+
backgroundColor: 'var(--byline-code-active-line)'
|
|
72
|
+
},
|
|
73
|
+
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection': {
|
|
74
|
+
backgroundColor: 'var(--byline-code-selection) !important'
|
|
75
|
+
},
|
|
76
|
+
'.cm-cursor': {
|
|
77
|
+
borderLeftColor: 'var(--byline-code-caret)'
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const bylineHighlightStyle = HighlightStyle.define([
|
|
81
|
+
{
|
|
82
|
+
tag: tags.keyword,
|
|
83
|
+
color: 'var(--byline-code-keyword)'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
tag: [
|
|
87
|
+
tags.string,
|
|
88
|
+
tags.special(tags.string)
|
|
89
|
+
],
|
|
90
|
+
color: 'var(--byline-code-string)'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
tag: tags.comment,
|
|
94
|
+
color: 'var(--byline-code-comment)',
|
|
95
|
+
fontStyle: 'italic'
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
tag: [
|
|
99
|
+
tags.number,
|
|
100
|
+
tags.bool,
|
|
101
|
+
tags["null"]
|
|
102
|
+
],
|
|
103
|
+
color: 'var(--byline-code-number)'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
tag: [
|
|
107
|
+
tags["function"](tags.variableName),
|
|
108
|
+
tags["function"](tags.propertyName)
|
|
109
|
+
],
|
|
110
|
+
color: 'var(--byline-code-function)'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
tag: [
|
|
114
|
+
tags.typeName,
|
|
115
|
+
tags.className,
|
|
116
|
+
tags.tagName
|
|
117
|
+
],
|
|
118
|
+
color: 'var(--byline-code-type)'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
tag: [
|
|
122
|
+
tags.propertyName,
|
|
123
|
+
tags.attributeName
|
|
124
|
+
],
|
|
125
|
+
color: 'var(--byline-code-property)'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
tag: [
|
|
129
|
+
tags.operator,
|
|
130
|
+
tags.definitionKeyword
|
|
131
|
+
],
|
|
132
|
+
color: 'var(--byline-code-operator)'
|
|
133
|
+
}
|
|
134
|
+
]);
|
|
135
|
+
const CodeEditor = ({ id, value, language, readOnly, onChange, ariaInvalid, ariaDescribedBy })=>{
|
|
136
|
+
const containerRef = useRef(null);
|
|
137
|
+
const viewRef = useRef(null);
|
|
138
|
+
const onChangeRef = useRef(onChange);
|
|
139
|
+
onChangeRef.current = onChange;
|
|
140
|
+
const languageCompartment = useRef(new Compartment());
|
|
141
|
+
const readOnlyCompartment = useRef(new Compartment());
|
|
142
|
+
const ariaCompartment = useRef(new Compartment());
|
|
143
|
+
const initialValueRef = useRef(value);
|
|
144
|
+
useEffect(()=>{
|
|
145
|
+
if (null == containerRef.current) return;
|
|
146
|
+
const state = EditorState.create({
|
|
147
|
+
doc: initialValueRef.current,
|
|
148
|
+
extensions: [
|
|
149
|
+
lineNumbers(),
|
|
150
|
+
commands_history(),
|
|
151
|
+
bracketMatching(),
|
|
152
|
+
keymap.of([
|
|
153
|
+
...defaultKeymap,
|
|
154
|
+
...historyKeymap,
|
|
155
|
+
indentWithTab
|
|
156
|
+
]),
|
|
157
|
+
EditorView.lineWrapping,
|
|
158
|
+
bylineCodeTheme,
|
|
159
|
+
syntaxHighlighting(bylineHighlightStyle),
|
|
160
|
+
languageCompartment.current.of([]),
|
|
161
|
+
readOnlyCompartment.current.of([]),
|
|
162
|
+
ariaCompartment.current.of([]),
|
|
163
|
+
EditorView.updateListener.of((update)=>{
|
|
164
|
+
if (update.docChanged) onChangeRef.current?.(update.state.doc.toString());
|
|
165
|
+
})
|
|
166
|
+
]
|
|
167
|
+
});
|
|
168
|
+
const view = new EditorView({
|
|
169
|
+
state,
|
|
170
|
+
parent: containerRef.current
|
|
171
|
+
});
|
|
172
|
+
viewRef.current = view;
|
|
173
|
+
return ()=>{
|
|
174
|
+
view.destroy();
|
|
175
|
+
viewRef.current = null;
|
|
176
|
+
};
|
|
177
|
+
}, []);
|
|
178
|
+
useEffect(()=>{
|
|
179
|
+
const view = viewRef.current;
|
|
180
|
+
if (null == view || view.hasFocus) return;
|
|
181
|
+
const current = view.state.doc.toString();
|
|
182
|
+
if (current !== value) view.dispatch({
|
|
183
|
+
changes: {
|
|
184
|
+
from: 0,
|
|
185
|
+
to: current.length,
|
|
186
|
+
insert: value
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}, [
|
|
190
|
+
value
|
|
191
|
+
]);
|
|
192
|
+
useEffect(()=>{
|
|
193
|
+
let cancelled = false;
|
|
194
|
+
const view = viewRef.current;
|
|
195
|
+
if (null == view) return;
|
|
196
|
+
const loader = resolveLanguageLoader(language);
|
|
197
|
+
if (null == loader) return void view.dispatch({
|
|
198
|
+
effects: languageCompartment.current.reconfigure([])
|
|
199
|
+
});
|
|
200
|
+
loader().then((extension)=>{
|
|
201
|
+
if (!cancelled && null != viewRef.current) viewRef.current.dispatch({
|
|
202
|
+
effects: languageCompartment.current.reconfigure(extension)
|
|
203
|
+
});
|
|
204
|
+
}).catch(()=>{});
|
|
205
|
+
return ()=>{
|
|
206
|
+
cancelled = true;
|
|
207
|
+
};
|
|
208
|
+
}, [
|
|
209
|
+
language
|
|
210
|
+
]);
|
|
211
|
+
useEffect(()=>{
|
|
212
|
+
viewRef.current?.dispatch({
|
|
213
|
+
effects: readOnlyCompartment.current.reconfigure([
|
|
214
|
+
EditorState.readOnly.of(true === readOnly),
|
|
215
|
+
EditorView.editable.of(true !== readOnly)
|
|
216
|
+
])
|
|
217
|
+
});
|
|
218
|
+
}, [
|
|
219
|
+
readOnly
|
|
220
|
+
]);
|
|
221
|
+
useEffect(()=>{
|
|
222
|
+
const attributes = {};
|
|
223
|
+
if (ariaInvalid) attributes['aria-invalid'] = 'true';
|
|
224
|
+
if (ariaDescribedBy) attributes['aria-describedby'] = ariaDescribedBy;
|
|
225
|
+
viewRef.current?.dispatch({
|
|
226
|
+
effects: ariaCompartment.current.reconfigure(EditorView.contentAttributes.of(attributes))
|
|
227
|
+
});
|
|
228
|
+
}, [
|
|
229
|
+
ariaInvalid,
|
|
230
|
+
ariaDescribedBy
|
|
231
|
+
]);
|
|
232
|
+
return /*#__PURE__*/ jsx("div", {
|
|
233
|
+
ref: containerRef,
|
|
234
|
+
id: id,
|
|
235
|
+
className: "byline-code-editor"
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
const code_editor = CodeEditor;
|
|
239
|
+
export default code_editor;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import type { FieldComponentSlots, CodeField as FieldType } from '@byline/core';
|
|
10
|
+
export declare const CodeField: ({ field, value, defaultValue, onChange, id, path, locale, components, }: {
|
|
11
|
+
field: FieldType;
|
|
12
|
+
value?: string;
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
onChange?: (value: string) => void;
|
|
15
|
+
id?: string;
|
|
16
|
+
path?: string;
|
|
17
|
+
/** When provided, renders a LocaleBadge next to the field label. */
|
|
18
|
+
locale?: string;
|
|
19
|
+
/** Optional UI component slot overrides from the admin config. */
|
|
20
|
+
components?: FieldComponentSlots;
|
|
21
|
+
}) => React.JSX.Element;
|