@manuscripts/body-editor 2.8.40 → 2.8.46

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.
@@ -56,14 +56,25 @@ const formik_1 = require("formik");
56
56
  const react_1 = __importStar(require("react"));
57
57
  const styled_components_1 = __importDefault(require("styled-components"));
58
58
  const ChangeHandlingForm_1 = require("../ChangeHandlingForm");
59
+ const use_debounce_1 = require("../hooks/use-debounce");
59
60
  const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
60
61
  const [funders, setFunders] = (0, react_1.useState)([]);
62
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
63
+ const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
61
64
  const formRef = (0, react_1.useRef)(null);
62
65
  const primaryButtonText = values.source ? 'Update funder' : 'Add funder';
66
+ const debouncedSearchQuery = (0, use_debounce_1.useDebounce)(searchQuery, 300);
63
67
  (0, react_1.useEffect)(() => {
64
- const fetchFunders = () => __awaiter(void 0, void 0, void 0, function* () {
68
+ const searchFunders = () => __awaiter(void 0, void 0, void 0, function* () {
69
+ const query = debouncedSearchQuery;
70
+ if (!query) {
71
+ setFunders([]);
72
+ return;
73
+ }
74
+ setIsLoading(true);
65
75
  try {
66
- const response = yield fetch('https://api.crossref.org/funders');
76
+ const formattedQuery = query.replace(/\s+/g, '+');
77
+ const response = yield fetch(`https://api.crossref.org/funders?query=${encodeURIComponent(formattedQuery)}`);
67
78
  if (!response.ok) {
68
79
  throw new Error(`HTTP error! status: ${response.status}`);
69
80
  }
@@ -73,21 +84,21 @@ const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
73
84
  label: funder.name,
74
85
  }));
75
86
  funderOptions.sort((a, b) => a.label.localeCompare(b.label));
76
- sessionStorage.setItem('funders', JSON.stringify(funderOptions));
77
87
  setFunders(funderOptions);
78
88
  }
79
89
  catch (error) {
80
90
  console.error('Error fetching funders:', error);
91
+ setFunders([]);
92
+ }
93
+ finally {
94
+ setIsLoading(false);
81
95
  }
82
96
  });
83
- const storedFunders = sessionStorage.getItem('funders');
84
- if (storedFunders) {
85
- setFunders(JSON.parse(storedFunders));
86
- }
87
- else {
88
- fetchFunders();
89
- }
90
- }, []);
97
+ searchFunders();
98
+ }, [debouncedSearchQuery]);
99
+ const handleFunderSearch = (event) => {
100
+ setSearchQuery(event.target.value);
101
+ };
91
102
  const handleCancel = () => {
92
103
  var _a;
93
104
  (_a = formRef.current) === null || _a === void 0 ? void 0 : _a.resetForm();
@@ -100,9 +111,7 @@ const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
100
111
  }
101
112
  return errors;
102
113
  };
103
- return (react_1.default.createElement(formik_1.Formik, { initialValues: Object.assign(Object.assign({}, values), { source: funders.some((funder) => funder.value === values.source)
104
- ? values.source
105
- : '' }), onSubmit: (values, { setSubmitting }) => {
114
+ return (react_1.default.createElement(formik_1.Formik, { initialValues: values, onSubmit: (values, { setSubmitting }) => {
106
115
  onSave(values);
107
116
  setSubmitting(false);
108
117
  }, enableReinitialize: true, validate: validate, validateOnChange: false, innerRef: formRef }, (formik) => {
@@ -110,8 +119,19 @@ const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
110
119
  react_1.default.createElement(formik_1.Field, { type: "hidden", name: "id" }),
111
120
  react_1.default.createElement(LabelContainer, null,
112
121
  react_1.default.createElement(Label, { htmlFor: 'source' }, "Funder name")),
113
- react_1.default.createElement(formik_1.Field, { name: "source", component: style_guide_1.SelectField, options: funders, value: funders.find((funder) => funder.value === formik.values.source) || '' }),
114
- formik.errors.source && formik.touched.source && (react_1.default.createElement("div", { style: { color: 'red' } }, formik.errors.source)),
122
+ react_1.default.createElement(SearchContainer, null,
123
+ react_1.default.createElement(SearchIconContainer, null,
124
+ react_1.default.createElement(style_guide_1.SearchIcon, null)),
125
+ react_1.default.createElement(formik_1.Field, { name: "source" }, (props) => (react_1.default.createElement(StyledTextField, { id: "source", placeholder: "Search for funder...", onChange: (e) => {
126
+ props.field.onChange(e);
127
+ handleFunderSearch(e);
128
+ }, value: props.field.value || '', autoFocus: true }))),
129
+ isLoading && react_1.default.createElement(LoadingText, null, "Loading..."),
130
+ funders.length > 0 && (react_1.default.createElement(SearchResults, null, funders.map((funder) => (react_1.default.createElement(SearchResultItem, { key: funder.value, onClick: () => {
131
+ formik.setFieldValue('source', funder.value);
132
+ setFunders([]);
133
+ } }, funder.label)))))),
134
+ formik.errors.source && formik.touched.source && (react_1.default.createElement(ErrorText, null, formik.errors.source)),
115
135
  react_1.default.createElement(LabelContainer, null,
116
136
  react_1.default.createElement(Label, { htmlFor: 'code' }, "Grant number")),
117
137
  react_1.default.createElement(style_guide_1.MultiValueInput, { id: "code", inputType: "text", placeholder: "Enter grant number and press enter", initialValues: values.code ? values.code.split(';') : [], onChange: (newValues) => {
@@ -119,7 +139,7 @@ const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
119
139
  } }),
120
140
  react_1.default.createElement(LabelContainer, null,
121
141
  react_1.default.createElement(Label, { htmlFor: 'recipient' }, "Recipient name")),
122
- react_1.default.createElement(formik_1.Field, { name: "recipient" }, (props) => (react_1.default.createElement(style_guide_1.TextField, Object.assign({ id: "recipient", placeholder: "Enter full name " }, props.field)))),
142
+ react_1.default.createElement(formik_1.Field, { name: "recipient" }, (props) => (react_1.default.createElement(style_guide_1.TextField, Object.assign({ id: "recipient", placeholder: "Enter full name" }, props.field)))),
123
143
  react_1.default.createElement(ButtonContainer, null,
124
144
  react_1.default.createElement(style_guide_1.SecondaryButton, { onClick: handleCancel }, "Cancel"),
125
145
  react_1.default.createElement(style_guide_1.PrimaryButton, { type: "submit", disabled: !formik.dirty || formik.isSubmitting }, primaryButtonText))));
@@ -145,3 +165,66 @@ const ButtonContainer = styled_components_1.default.div `
145
165
  margin-top: 16px;
146
166
  gap: 8px;
147
167
  `;
168
+ const SearchContainer = styled_components_1.default.div `
169
+ position: relative;
170
+ width: 100%;
171
+ `;
172
+ const SearchIconContainer = styled_components_1.default.span `
173
+ display: flex;
174
+ left: ${(props) => props.theme.grid.unit * 4}px;
175
+ position: absolute;
176
+ top: 50%;
177
+ transform: translateY(-50%);
178
+ z-index: 2;
179
+
180
+ path {
181
+ stroke: ${(props) => props.theme.colors.text.primary};
182
+ }
183
+
184
+ ${SearchContainer}:hover &,
185
+ ${SearchContainer}:focus-within & {
186
+ path {
187
+ stroke: ${(props) => props.theme.colors.brand.medium};
188
+ }
189
+ }
190
+ `;
191
+ const StyledTextField = (0, styled_components_1.default)(style_guide_1.TextField) `
192
+ padding-left: ${(props) => props.theme.grid.unit * 11}px;
193
+ &:hover,
194
+ &:focus {
195
+ background-color: ${(props) => props.theme.colors.background.fifth};
196
+ }
197
+ `;
198
+ const SearchResults = styled_components_1.default.div `
199
+ position: absolute;
200
+ top: 100%;
201
+ left: 0;
202
+ right: 0;
203
+ max-height: 200px;
204
+ overflow-y: auto;
205
+ background: white;
206
+ border: 1px solid ${(props) => props.theme.colors.border.secondary};
207
+ border-radius: 4px;
208
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
209
+ z-index: 1000;
210
+ `;
211
+ const SearchResultItem = styled_components_1.default.div `
212
+ padding: 8px 12px;
213
+ cursor: pointer;
214
+ &:hover {
215
+ background-color: ${(props) => props.theme.colors.background.fifth};
216
+ }
217
+ `;
218
+ const LoadingText = styled_components_1.default.div `
219
+ position: absolute;
220
+ right: 12px;
221
+ top: 50%;
222
+ transform: translateY(-50%);
223
+ color: ${(props) => props.theme.colors.text.secondary};
224
+ font-size: ${(props) => props.theme.font.size.small};
225
+ `;
226
+ const ErrorText = styled_components_1.default.div `
227
+ color: ${(props) => props.theme.colors.text.error};
228
+ font-size: ${(props) => props.theme.font.size.small};
229
+ margin-top: 4px;
230
+ `;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MATHJAX_VERSION = exports.VERSION = void 0;
4
- exports.VERSION = '2.8.40';
4
+ exports.VERSION = '2.8.46';
5
5
  exports.MATHJAX_VERSION = '3.2.2';
@@ -22,19 +22,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
22
22
  step((generator = generator.apply(thisArg, _arguments || [])).next());
23
23
  });
24
24
  };
25
- import { MultiValueInput, PrimaryButton, SecondaryButton, SelectField, TextField, } from '@manuscripts/style-guide';
25
+ import { MultiValueInput, PrimaryButton, SearchIcon, SecondaryButton, TextField, } from '@manuscripts/style-guide';
26
26
  import { Field, Formik } from 'formik';
27
27
  import React, { useEffect, useRef, useState } from 'react';
28
28
  import styled from 'styled-components';
29
29
  import { ChangeHandlingForm } from '../ChangeHandlingForm';
30
+ import { useDebounce } from '../hooks/use-debounce';
30
31
  export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
31
32
  const [funders, setFunders] = useState([]);
33
+ const [isLoading, setIsLoading] = useState(false);
34
+ const [searchQuery, setSearchQuery] = useState('');
32
35
  const formRef = useRef(null);
33
36
  const primaryButtonText = values.source ? 'Update funder' : 'Add funder';
37
+ const debouncedSearchQuery = useDebounce(searchQuery, 300);
34
38
  useEffect(() => {
35
- const fetchFunders = () => __awaiter(void 0, void 0, void 0, function* () {
39
+ const searchFunders = () => __awaiter(void 0, void 0, void 0, function* () {
40
+ const query = debouncedSearchQuery;
41
+ if (!query) {
42
+ setFunders([]);
43
+ return;
44
+ }
45
+ setIsLoading(true);
36
46
  try {
37
- const response = yield fetch('https://api.crossref.org/funders');
47
+ const formattedQuery = query.replace(/\s+/g, '+');
48
+ const response = yield fetch(`https://api.crossref.org/funders?query=${encodeURIComponent(formattedQuery)}`);
38
49
  if (!response.ok) {
39
50
  throw new Error(`HTTP error! status: ${response.status}`);
40
51
  }
@@ -44,21 +55,21 @@ export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
44
55
  label: funder.name,
45
56
  }));
46
57
  funderOptions.sort((a, b) => a.label.localeCompare(b.label));
47
- sessionStorage.setItem('funders', JSON.stringify(funderOptions));
48
58
  setFunders(funderOptions);
49
59
  }
50
60
  catch (error) {
51
61
  console.error('Error fetching funders:', error);
62
+ setFunders([]);
63
+ }
64
+ finally {
65
+ setIsLoading(false);
52
66
  }
53
67
  });
54
- const storedFunders = sessionStorage.getItem('funders');
55
- if (storedFunders) {
56
- setFunders(JSON.parse(storedFunders));
57
- }
58
- else {
59
- fetchFunders();
60
- }
61
- }, []);
68
+ searchFunders();
69
+ }, [debouncedSearchQuery]);
70
+ const handleFunderSearch = (event) => {
71
+ setSearchQuery(event.target.value);
72
+ };
62
73
  const handleCancel = () => {
63
74
  var _a;
64
75
  (_a = formRef.current) === null || _a === void 0 ? void 0 : _a.resetForm();
@@ -71,9 +82,7 @@ export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
71
82
  }
72
83
  return errors;
73
84
  };
74
- return (React.createElement(Formik, { initialValues: Object.assign(Object.assign({}, values), { source: funders.some((funder) => funder.value === values.source)
75
- ? values.source
76
- : '' }), onSubmit: (values, { setSubmitting }) => {
85
+ return (React.createElement(Formik, { initialValues: values, onSubmit: (values, { setSubmitting }) => {
77
86
  onSave(values);
78
87
  setSubmitting(false);
79
88
  }, enableReinitialize: true, validate: validate, validateOnChange: false, innerRef: formRef }, (formik) => {
@@ -81,8 +90,19 @@ export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
81
90
  React.createElement(Field, { type: "hidden", name: "id" }),
82
91
  React.createElement(LabelContainer, null,
83
92
  React.createElement(Label, { htmlFor: 'source' }, "Funder name")),
84
- React.createElement(Field, { name: "source", component: SelectField, options: funders, value: funders.find((funder) => funder.value === formik.values.source) || '' }),
85
- formik.errors.source && formik.touched.source && (React.createElement("div", { style: { color: 'red' } }, formik.errors.source)),
93
+ React.createElement(SearchContainer, null,
94
+ React.createElement(SearchIconContainer, null,
95
+ React.createElement(SearchIcon, null)),
96
+ React.createElement(Field, { name: "source" }, (props) => (React.createElement(StyledTextField, { id: "source", placeholder: "Search for funder...", onChange: (e) => {
97
+ props.field.onChange(e);
98
+ handleFunderSearch(e);
99
+ }, value: props.field.value || '', autoFocus: true }))),
100
+ isLoading && React.createElement(LoadingText, null, "Loading..."),
101
+ funders.length > 0 && (React.createElement(SearchResults, null, funders.map((funder) => (React.createElement(SearchResultItem, { key: funder.value, onClick: () => {
102
+ formik.setFieldValue('source', funder.value);
103
+ setFunders([]);
104
+ } }, funder.label)))))),
105
+ formik.errors.source && formik.touched.source && (React.createElement(ErrorText, null, formik.errors.source)),
86
106
  React.createElement(LabelContainer, null,
87
107
  React.createElement(Label, { htmlFor: 'code' }, "Grant number")),
88
108
  React.createElement(MultiValueInput, { id: "code", inputType: "text", placeholder: "Enter grant number and press enter", initialValues: values.code ? values.code.split(';') : [], onChange: (newValues) => {
@@ -90,7 +110,7 @@ export const AwardForm = ({ values, onSave, onCancel, onChange, }) => {
90
110
  } }),
91
111
  React.createElement(LabelContainer, null,
92
112
  React.createElement(Label, { htmlFor: 'recipient' }, "Recipient name")),
93
- React.createElement(Field, { name: "recipient" }, (props) => (React.createElement(TextField, Object.assign({ id: "recipient", placeholder: "Enter full name " }, props.field)))),
113
+ React.createElement(Field, { name: "recipient" }, (props) => (React.createElement(TextField, Object.assign({ id: "recipient", placeholder: "Enter full name" }, props.field)))),
94
114
  React.createElement(ButtonContainer, null,
95
115
  React.createElement(SecondaryButton, { onClick: handleCancel }, "Cancel"),
96
116
  React.createElement(PrimaryButton, { type: "submit", disabled: !formik.dirty || formik.isSubmitting }, primaryButtonText))));
@@ -115,3 +135,66 @@ const ButtonContainer = styled.div `
115
135
  margin-top: 16px;
116
136
  gap: 8px;
117
137
  `;
138
+ const SearchContainer = styled.div `
139
+ position: relative;
140
+ width: 100%;
141
+ `;
142
+ const SearchIconContainer = styled.span `
143
+ display: flex;
144
+ left: ${(props) => props.theme.grid.unit * 4}px;
145
+ position: absolute;
146
+ top: 50%;
147
+ transform: translateY(-50%);
148
+ z-index: 2;
149
+
150
+ path {
151
+ stroke: ${(props) => props.theme.colors.text.primary};
152
+ }
153
+
154
+ ${SearchContainer}:hover &,
155
+ ${SearchContainer}:focus-within & {
156
+ path {
157
+ stroke: ${(props) => props.theme.colors.brand.medium};
158
+ }
159
+ }
160
+ `;
161
+ const StyledTextField = styled(TextField) `
162
+ padding-left: ${(props) => props.theme.grid.unit * 11}px;
163
+ &:hover,
164
+ &:focus {
165
+ background-color: ${(props) => props.theme.colors.background.fifth};
166
+ }
167
+ `;
168
+ const SearchResults = styled.div `
169
+ position: absolute;
170
+ top: 100%;
171
+ left: 0;
172
+ right: 0;
173
+ max-height: 200px;
174
+ overflow-y: auto;
175
+ background: white;
176
+ border: 1px solid ${(props) => props.theme.colors.border.secondary};
177
+ border-radius: 4px;
178
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
179
+ z-index: 1000;
180
+ `;
181
+ const SearchResultItem = styled.div `
182
+ padding: 8px 12px;
183
+ cursor: pointer;
184
+ &:hover {
185
+ background-color: ${(props) => props.theme.colors.background.fifth};
186
+ }
187
+ `;
188
+ const LoadingText = styled.div `
189
+ position: absolute;
190
+ right: 12px;
191
+ top: 50%;
192
+ transform: translateY(-50%);
193
+ color: ${(props) => props.theme.colors.text.secondary};
194
+ font-size: ${(props) => props.theme.font.size.small};
195
+ `;
196
+ const ErrorText = styled.div `
197
+ color: ${(props) => props.theme.colors.text.error};
198
+ font-size: ${(props) => props.theme.font.size.small};
199
+ margin-top: 4px;
200
+ `;
@@ -1,2 +1,2 @@
1
- export const VERSION = '2.8.40';
1
+ export const VERSION = '2.8.46';
2
2
  export const MATHJAX_VERSION = '3.2.2';
@@ -6,6 +6,7 @@ export declare const StyledSelect: import("styled-components").StyledComponent<(
6
6
  options?: import("react-select").OptionsOrGroups<Option, import("react-select").GroupBase<Option>> | undefined;
7
7
  tabIndex?: number | undefined;
8
8
  'aria-live'?: "off" | "assertive" | "polite" | undefined;
9
+ isLoading?: boolean | undefined;
9
10
  backspaceRemovesValue?: boolean | undefined;
10
11
  blurInputOnSelect?: boolean | undefined;
11
12
  captureMenuScroll?: boolean | undefined;
@@ -20,7 +21,6 @@ export declare const StyledSelect: import("styled-components").StyledComponent<(
20
21
  getOptionLabel?: import("react-select").GetOptionLabel<Option> | undefined;
21
22
  getOptionValue?: import("react-select").GetOptionValue<Option> | undefined;
22
23
  isDisabled?: boolean | undefined;
23
- isLoading?: boolean | undefined;
24
24
  isOptionDisabled?: ((option: Option, selectValue: import("react-select").Options<Option>) => boolean) | undefined;
25
25
  isMulti?: false | undefined;
26
26
  isRtl?: boolean | undefined;
@@ -52,6 +52,7 @@ export declare const StyledSelect: import("styled-components").StyledComponent<(
52
52
  options?: import("react-select").OptionsOrGroups<Option, import("react-select").GroupBase<Option>> | undefined;
53
53
  tabIndex?: number | undefined;
54
54
  'aria-live'?: "off" | "assertive" | "polite" | undefined;
55
+ isLoading?: boolean | undefined;
55
56
  backspaceRemovesValue?: boolean | undefined;
56
57
  blurInputOnSelect?: boolean | undefined;
57
58
  captureMenuScroll?: boolean | undefined;
@@ -66,7 +67,6 @@ export declare const StyledSelect: import("styled-components").StyledComponent<(
66
67
  getOptionLabel?: import("react-select").GetOptionLabel<Option> | undefined;
67
68
  getOptionValue?: import("react-select").GetOptionValue<Option> | undefined;
68
69
  isDisabled?: boolean | undefined;
69
- isLoading?: boolean | undefined;
70
70
  isOptionDisabled?: ((option: Option, selectValue: import("react-select").Options<Option>) => boolean) | undefined;
71
71
  isMulti?: false | undefined;
72
72
  isRtl?: boolean | undefined;
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "2.8.40";
1
+ export declare const VERSION = "2.8.46";
2
2
  export declare const MATHJAX_VERSION = "3.2.2";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@manuscripts/body-editor",
3
3
  "description": "Prosemirror components for editing and viewing manuscripts",
4
- "version": "2.8.40",
4
+ "version": "2.8.46",
5
5
  "repository": "github:Atypon-OpenSource/manuscripts-body-editor",
6
6
  "license": "Apache-2.0",
7
7
  "main": "dist/cjs",
@@ -32,7 +32,7 @@
32
32
  "@iarna/word-count": "^1.1.2",
33
33
  "@manuscripts/json-schema": "2.2.11",
34
34
  "@manuscripts/library": "1.3.13",
35
- "@manuscripts/style-guide": "2.1.7",
35
+ "@manuscripts/style-guide": "2.1.9",
36
36
  "@manuscripts/track-changes-plugin": "1.10.4",
37
37
  "@manuscripts/transform": "3.0.55",
38
38
  "@popperjs/core": "^2.11.8",