@drodil/backstage-plugin-qeta 1.0.0 → 1.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.
- package/dist/esm/{index-567d8d04.esm.js → index-0a5a1a59.esm.js} +239 -243
- package/dist/esm/index-0a5a1a59.esm.js.map +1 -0
- package/dist/esm/{index-c475e52e.esm.js → index-d42bcb05.esm.js} +463 -27
- package/dist/esm/index-d42bcb05.esm.js.map +1 -0
- package/dist/index.d.ts +132 -2
- package/dist/index.esm.js +8 -3
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/dist/esm/index-567d8d04.esm.js.map +0 -1
- package/dist/esm/index-c475e52e.esm.js.map +0 -1
|
@@ -1,209 +1,33 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { useApi } from '@backstage/core-plugin-api';
|
|
6
|
-
import { Autocomplete, Skeleton } from '@material-ui/lab';
|
|
7
|
-
import { useForm, Controller } from 'react-hook-form';
|
|
2
|
+
import { Button, Grid, useTheme, Tooltip, IconButton, Typography, Box, Modal, Backdrop, Card, CardContent, Chip, Link, Divider, TextField, Avatar, List, ListSubheader, ListItem, ListItemText } from '@material-ui/core';
|
|
3
|
+
import { Content, ContentHeader, InfoCard, MarkdownContent, WarningPanel, Page, Header } from '@backstage/core-components';
|
|
4
|
+
import { useParams, useNavigate, Routes, Route } from 'react-router-dom';
|
|
8
5
|
import 'react-mde/lib/styles/css/react-mde-all.css';
|
|
9
|
-
import {
|
|
10
|
-
import ReactMde from 'react-mde';
|
|
11
|
-
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
|
12
|
-
import { stringifyEntityRef } from '@backstage/catalog-model';
|
|
6
|
+
import { A as AskForm, q as qetaApiRef, u as useStyles, g as getEntityTitle, a as getEntityUrl, f as formatUsername, M as MarkdownEditor, b as useQetaApi, Q as QuestionsContainer } from './index-d42bcb05.esm.js';
|
|
13
7
|
import HomeOutlined from '@material-ui/icons/HomeOutlined';
|
|
14
8
|
import ArrowDownward from '@material-ui/icons/ArrowDownward';
|
|
15
9
|
import ArrowUpward from '@material-ui/icons/ArrowUpward';
|
|
16
10
|
import Check from '@material-ui/icons/Check';
|
|
11
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
12
|
+
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
|
13
|
+
import { stringifyEntityRef } from '@backstage/catalog-model';
|
|
17
14
|
import { compact } from 'lodash';
|
|
18
15
|
import RelativeTime from 'react-relative-time';
|
|
16
|
+
import { Alert, Skeleton } from '@material-ui/lab';
|
|
17
|
+
import Delete from '@material-ui/icons/Delete';
|
|
18
|
+
import { useForm, Controller } from 'react-hook-form';
|
|
19
19
|
import HelpOutline from '@material-ui/icons/HelpOutline';
|
|
20
20
|
import LoyaltyOutlined from '@material-ui/icons/LoyaltyOutlined';
|
|
21
21
|
import '@backstage/errors';
|
|
22
22
|
import 'lodash/omitBy';
|
|
23
23
|
import 'lodash/isEmpty';
|
|
24
|
-
import '@backstage/catalog-client';
|
|
25
24
|
import 'react-use';
|
|
26
25
|
import '@material-ui/icons/FilterList';
|
|
27
|
-
|
|
28
|
-
const MarkdownEditor = (props) => {
|
|
29
|
-
const { value, onChange, height, error, placeholder } = props;
|
|
30
|
-
const [selectedTab, setSelectedTab] = React.useState(
|
|
31
|
-
"write"
|
|
32
|
-
);
|
|
33
|
-
const styles = useStyles();
|
|
34
|
-
return /* @__PURE__ */ React.createElement(
|
|
35
|
-
ReactMde,
|
|
36
|
-
{
|
|
37
|
-
classes: {
|
|
38
|
-
reactMde: styles.markdownEditor,
|
|
39
|
-
textArea: error ? styles.markdownEditorError : void 0
|
|
40
|
-
},
|
|
41
|
-
value,
|
|
42
|
-
onChange,
|
|
43
|
-
selectedTab,
|
|
44
|
-
onTabChange: setSelectedTab,
|
|
45
|
-
minEditorHeight: height,
|
|
46
|
-
minPreviewHeight: height,
|
|
47
|
-
childProps: { textArea: { required: true, placeholder } },
|
|
48
|
-
generateMarkdownPreview: (content) => Promise.resolve(/* @__PURE__ */ React.createElement(MarkdownContent, { content }))
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const getEntityTitle = (entity) => {
|
|
54
|
-
var _a;
|
|
55
|
-
return (_a = entity.metadata.title) != null ? _a : stringifyEntityRef(entity);
|
|
56
|
-
};
|
|
57
|
-
const getEntityUrl = (entity) => {
|
|
58
|
-
var _a;
|
|
59
|
-
return `/catalog/${(_a = entity.metadata.namespace) != null ? _a : "default"}/${entity.kind}/${entity.metadata.name}`.toLowerCase();
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const formToRequest = (form) => {
|
|
63
|
-
var _a;
|
|
64
|
-
return {
|
|
65
|
-
...form,
|
|
66
|
-
components: (_a = form.components) == null ? void 0 : _a.map(stringifyEntityRef)
|
|
67
|
-
};
|
|
68
|
-
};
|
|
69
|
-
const AskForm = () => {
|
|
70
|
-
const navigate = useNavigate();
|
|
71
|
-
const [error, setError] = React.useState(false);
|
|
72
|
-
const [availableTags, setAvailableTags] = React.useState([]);
|
|
73
|
-
const [availableComponents, setAvailableComponents] = React.useState([]);
|
|
74
|
-
const qetaApi = useApi(qetaApiRef);
|
|
75
|
-
const catalogApi = useApi(catalogApiRef);
|
|
76
|
-
const styles = useStyles();
|
|
77
|
-
const {
|
|
78
|
-
register,
|
|
79
|
-
handleSubmit,
|
|
80
|
-
control,
|
|
81
|
-
reset,
|
|
82
|
-
formState: { errors }
|
|
83
|
-
} = useForm();
|
|
84
|
-
const postQuestion = (data) => {
|
|
85
|
-
qetaApi.postQuestion(formToRequest(data)).then((q) => {
|
|
86
|
-
if (!q || !q.id) {
|
|
87
|
-
setError(true);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
reset();
|
|
91
|
-
navigate(`/qeta/questions/${q.id}`);
|
|
92
|
-
}).catch((_e) => setError(true));
|
|
93
|
-
};
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
qetaApi.getTags().catch((_) => setAvailableTags(null)).then(
|
|
96
|
-
(data) => data ? setAvailableTags(data.map((tag) => tag.tag)) : setAvailableTags(null)
|
|
97
|
-
);
|
|
98
|
-
}, [qetaApi]);
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
catalogApi.getEntities().catch((_) => setAvailableComponents(null)).then(
|
|
101
|
-
(data) => data ? setAvailableComponents(data.items) : setAvailableComponents(null)
|
|
102
|
-
);
|
|
103
|
-
}, [catalogApi]);
|
|
104
|
-
return /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit(postQuestion) }, error && /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not post question" }), /* @__PURE__ */ React.createElement(
|
|
105
|
-
TextField,
|
|
106
|
-
{
|
|
107
|
-
label: "Title",
|
|
108
|
-
required: true,
|
|
109
|
-
fullWidth: true,
|
|
110
|
-
error: "title" in errors,
|
|
111
|
-
margin: "normal",
|
|
112
|
-
variant: "outlined",
|
|
113
|
-
helperText: "Write good title for your question that people can understand",
|
|
114
|
-
...register("title", { required: true, maxLength: 255 })
|
|
115
|
-
}
|
|
116
|
-
), /* @__PURE__ */ React.createElement(
|
|
117
|
-
Controller,
|
|
118
|
-
{
|
|
119
|
-
control,
|
|
120
|
-
defaultValue: "",
|
|
121
|
-
rules: {
|
|
122
|
-
required: true
|
|
123
|
-
},
|
|
124
|
-
render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
|
|
125
|
-
MarkdownEditor,
|
|
126
|
-
{
|
|
127
|
-
value,
|
|
128
|
-
onChange,
|
|
129
|
-
height: 400,
|
|
130
|
-
error: "content" in errors,
|
|
131
|
-
placeholder: "Your question"
|
|
132
|
-
}
|
|
133
|
-
),
|
|
134
|
-
name: "content"
|
|
135
|
-
}
|
|
136
|
-
), availableTags && /* @__PURE__ */ React.createElement(
|
|
137
|
-
Controller,
|
|
138
|
-
{
|
|
139
|
-
control,
|
|
140
|
-
defaultValue: [],
|
|
141
|
-
render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
|
|
142
|
-
Autocomplete,
|
|
143
|
-
{
|
|
144
|
-
multiple: true,
|
|
145
|
-
id: "tags-standard",
|
|
146
|
-
value,
|
|
147
|
-
options: availableTags,
|
|
148
|
-
freeSolo: true,
|
|
149
|
-
onChange: (_e, newValue) => {
|
|
150
|
-
if (!value || value.length < 5) {
|
|
151
|
-
onChange(newValue);
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
renderInput: (params) => /* @__PURE__ */ React.createElement(
|
|
155
|
-
TextField,
|
|
156
|
-
{
|
|
157
|
-
...params,
|
|
158
|
-
variant: "outlined",
|
|
159
|
-
margin: "normal",
|
|
160
|
-
label: "Tags",
|
|
161
|
-
placeholder: "Type or select tags",
|
|
162
|
-
helperText: "Add up to 5 tags to categorize your question"
|
|
163
|
-
}
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
),
|
|
167
|
-
name: "tags"
|
|
168
|
-
}
|
|
169
|
-
), availableComponents && /* @__PURE__ */ React.createElement(
|
|
170
|
-
Controller,
|
|
171
|
-
{
|
|
172
|
-
control,
|
|
173
|
-
defaultValue: [],
|
|
174
|
-
render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
|
|
175
|
-
Autocomplete,
|
|
176
|
-
{
|
|
177
|
-
multiple: true,
|
|
178
|
-
id: "tags-standard",
|
|
179
|
-
options: availableComponents,
|
|
180
|
-
getOptionLabel: getEntityTitle,
|
|
181
|
-
getOptionSelected: (o, v) => stringifyEntityRef(o) === stringifyEntityRef(v),
|
|
182
|
-
onChange: (_e, newValue) => {
|
|
183
|
-
if (!value || value.length < 3) {
|
|
184
|
-
onChange(newValue);
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
renderInput: (params) => /* @__PURE__ */ React.createElement(
|
|
188
|
-
TextField,
|
|
189
|
-
{
|
|
190
|
-
...params,
|
|
191
|
-
variant: "outlined",
|
|
192
|
-
margin: "normal",
|
|
193
|
-
label: "Components",
|
|
194
|
-
placeholder: "Type or select components",
|
|
195
|
-
helperText: "Add up to 3 components this question relates to"
|
|
196
|
-
}
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
),
|
|
200
|
-
name: "components"
|
|
201
|
-
}
|
|
202
|
-
), /* @__PURE__ */ React.createElement(Button, { type: "submit", variant: "contained", className: styles.postButton }, "Post"));
|
|
203
|
-
};
|
|
26
|
+
import 'react-mde';
|
|
204
27
|
|
|
205
28
|
const AskPage = () => {
|
|
206
|
-
|
|
29
|
+
const { id } = useParams();
|
|
30
|
+
return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: id ? "Edit question" : "Ask question" }, /* @__PURE__ */ React.createElement(Button, { href: "/qeta", startIcon: /* @__PURE__ */ React.createElement(HomeOutlined, null) }, "Back to questions")), /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(InfoCard, null, /* @__PURE__ */ React.createElement(AskForm, { id })))));
|
|
207
31
|
};
|
|
208
32
|
|
|
209
33
|
const VoteButtons = (props) => {
|
|
@@ -216,8 +40,10 @@ const VoteButtons = (props) => {
|
|
|
216
40
|
);
|
|
217
41
|
const qetaApi = useApi(qetaApiRef);
|
|
218
42
|
const theme = useTheme();
|
|
43
|
+
const isQuestion = "title" in entity;
|
|
44
|
+
const own = (_b = props.entity.own) != null ? _b : false;
|
|
219
45
|
const voteUp = () => {
|
|
220
|
-
if (
|
|
46
|
+
if (isQuestion) {
|
|
221
47
|
qetaApi.voteQuestionUp(entity.id).then((response) => {
|
|
222
48
|
setOwnVote(1);
|
|
223
49
|
setEntity(response);
|
|
@@ -230,7 +56,7 @@ const VoteButtons = (props) => {
|
|
|
230
56
|
}
|
|
231
57
|
};
|
|
232
58
|
const voteDown = () => {
|
|
233
|
-
if (
|
|
59
|
+
if (isQuestion) {
|
|
234
60
|
qetaApi.voteQuestionDown(entity.id).then((response) => {
|
|
235
61
|
setOwnVote(-1);
|
|
236
62
|
setEntity(response);
|
|
@@ -243,9 +69,17 @@ const VoteButtons = (props) => {
|
|
|
243
69
|
}
|
|
244
70
|
};
|
|
245
71
|
let correctTooltip = correct ? "Mark answer as incorrect" : "Mark answer as correct";
|
|
246
|
-
if (!((
|
|
72
|
+
if (!((_c = props.question) == null ? void 0 : _c.own)) {
|
|
247
73
|
correctTooltip = correct ? "This answer has been marked as correct" : "";
|
|
248
74
|
}
|
|
75
|
+
let voteUpTooltip = isQuestion ? "This question is good" : "This answer is good";
|
|
76
|
+
if (own) {
|
|
77
|
+
voteUpTooltip = isQuestion ? "You cannot vote your own question" : "You cannot vote your own answer";
|
|
78
|
+
}
|
|
79
|
+
let voteDownTooltip = isQuestion ? "This question is not good" : "This answer is not good";
|
|
80
|
+
if (own) {
|
|
81
|
+
voteDownTooltip = voteUpTooltip;
|
|
82
|
+
}
|
|
249
83
|
const toggleCorrectAnswer = () => {
|
|
250
84
|
if (!("questionId" in entity)) {
|
|
251
85
|
return;
|
|
@@ -262,39 +96,27 @@ const VoteButtons = (props) => {
|
|
|
262
96
|
});
|
|
263
97
|
}
|
|
264
98
|
};
|
|
265
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
266
|
-
|
|
99
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip, { title: voteUpTooltip }, /* @__PURE__ */ React.createElement(
|
|
100
|
+
IconButton,
|
|
267
101
|
{
|
|
268
|
-
|
|
102
|
+
"aria-label": "vote up",
|
|
103
|
+
color: ownVote > 0 ? "primary" : "default",
|
|
104
|
+
disabled: own,
|
|
105
|
+
size: "small",
|
|
106
|
+
onClick: voteUp
|
|
269
107
|
},
|
|
270
|
-
/* @__PURE__ */ React.createElement(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"aria-label": "vote up",
|
|
274
|
-
color: ownVote > 0 ? "primary" : "default",
|
|
275
|
-
disabled: (_c = props.entity.own) != null ? _c : false,
|
|
276
|
-
size: "small",
|
|
277
|
-
onClick: voteUp
|
|
278
|
-
},
|
|
279
|
-
/* @__PURE__ */ React.createElement(ArrowUpward, null)
|
|
280
|
-
)
|
|
281
|
-
), /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, entity.score), /* @__PURE__ */ React.createElement(
|
|
282
|
-
Tooltip,
|
|
108
|
+
/* @__PURE__ */ React.createElement(ArrowUpward, null)
|
|
109
|
+
)), /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, entity.score), /* @__PURE__ */ React.createElement(Tooltip, { title: voteDownTooltip }, /* @__PURE__ */ React.createElement(
|
|
110
|
+
IconButton,
|
|
283
111
|
{
|
|
284
|
-
|
|
112
|
+
"aria-label": "vote down",
|
|
113
|
+
color: ownVote < 0 ? "primary" : "default",
|
|
114
|
+
disabled: own,
|
|
115
|
+
size: "small",
|
|
116
|
+
onClick: voteDown
|
|
285
117
|
},
|
|
286
|
-
/* @__PURE__ */ React.createElement(
|
|
287
|
-
|
|
288
|
-
{
|
|
289
|
-
"aria-label": "vote down",
|
|
290
|
-
color: ownVote < 0 ? "primary" : "default",
|
|
291
|
-
disabled: (_d = props.entity.own) != null ? _d : false,
|
|
292
|
-
size: "small",
|
|
293
|
-
onClick: voteDown
|
|
294
|
-
},
|
|
295
|
-
/* @__PURE__ */ React.createElement(ArrowDownward, null)
|
|
296
|
-
)
|
|
297
|
-
), "correct" in props.entity && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Tooltip, { title: correctTooltip }, /* @__PURE__ */ React.createElement(
|
|
118
|
+
/* @__PURE__ */ React.createElement(ArrowDownward, null)
|
|
119
|
+
)), "correct" in props.entity && (((_d = props.question) == null ? void 0 : _d.own) || correct) && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Tooltip, { title: correctTooltip }, /* @__PURE__ */ React.createElement(
|
|
298
120
|
IconButton,
|
|
299
121
|
{
|
|
300
122
|
"aria-label": "mark correct",
|
|
@@ -302,28 +124,82 @@ const VoteButtons = (props) => {
|
|
|
302
124
|
color: correct ? theme.palette.success.main : void 0
|
|
303
125
|
},
|
|
304
126
|
size: "small",
|
|
305
|
-
onClick: toggleCorrectAnswer
|
|
306
|
-
disabled: !((_e = props.question) == null ? void 0 : _e.own)
|
|
127
|
+
onClick: ((_e = props.question) == null ? void 0 : _e.own) ? toggleCorrectAnswer : void 0
|
|
307
128
|
},
|
|
308
129
|
/* @__PURE__ */ React.createElement(Check, null)
|
|
309
130
|
))));
|
|
310
131
|
};
|
|
311
132
|
|
|
133
|
+
const DeleteModal = (props) => {
|
|
134
|
+
const qetaApi = useApi(qetaApiRef);
|
|
135
|
+
const navigate = useNavigate();
|
|
136
|
+
const { entity, open, question, onClose } = props;
|
|
137
|
+
const styles = useStyles();
|
|
138
|
+
const [error, setError] = React.useState(false);
|
|
139
|
+
const isQuestion = "title" in entity;
|
|
140
|
+
const title = isQuestion ? "Are you sure you want to delete this question?" : "Are you sure you want to delete this answer?";
|
|
141
|
+
const handleDelete = () => {
|
|
142
|
+
if (isQuestion) {
|
|
143
|
+
qetaApi.deleteQuestion(entity.id).catch((_) => setError(true)).then((ret) => {
|
|
144
|
+
if (ret) {
|
|
145
|
+
onClose();
|
|
146
|
+
navigate(`/qeta`);
|
|
147
|
+
} else {
|
|
148
|
+
setError(true);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
} else if (question) {
|
|
152
|
+
qetaApi.deleteAnswer(question.id, entity.id).catch((_) => setError(true)).then((ret) => {
|
|
153
|
+
if (ret) {
|
|
154
|
+
onClose();
|
|
155
|
+
window.location.reload();
|
|
156
|
+
} else {
|
|
157
|
+
setError(true);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
return /* @__PURE__ */ React.createElement(
|
|
163
|
+
Modal,
|
|
164
|
+
{
|
|
165
|
+
open,
|
|
166
|
+
onClose,
|
|
167
|
+
"aria-labelledby": "modal-modal-title",
|
|
168
|
+
"aria-describedby": "modal-modal-description",
|
|
169
|
+
closeAfterTransition: true,
|
|
170
|
+
BackdropComponent: Backdrop,
|
|
171
|
+
BackdropProps: {
|
|
172
|
+
timeout: 500
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
/* @__PURE__ */ React.createElement(Box, { className: styles.deleteModal }, error && /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, "Failed to delete"), /* @__PURE__ */ React.createElement(Typography, { id: "modal-modal-title", variant: "h6", component: "h2" }, title), /* @__PURE__ */ React.createElement(Button, { onClick: handleDelete, startIcon: /* @__PURE__ */ React.createElement(Delete, null), color: "secondary" }, "Delete"), /* @__PURE__ */ React.createElement(Button, { onClick: onClose }, "Cancel"))
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
312
179
|
const QuestionCard = (props) => {
|
|
313
180
|
const { question } = props;
|
|
314
181
|
const styles = useStyles();
|
|
315
182
|
const catalogApi = useApi(catalogApiRef);
|
|
316
|
-
const [
|
|
183
|
+
const [entities, setEntities] = React.useState([]);
|
|
184
|
+
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
|
185
|
+
const handleDeleteModalOpen = () => setDeleteModalOpen(true);
|
|
186
|
+
const handleDeleteModalClose = () => setDeleteModalOpen(false);
|
|
317
187
|
useEffect(() => {
|
|
318
|
-
if (question.
|
|
188
|
+
if (question.entities && question.entities.length > 0) {
|
|
319
189
|
catalogApi.getEntitiesByRefs({
|
|
320
|
-
entityRefs: question.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
190
|
+
entityRefs: question.entities,
|
|
191
|
+
fields: [
|
|
192
|
+
"kind",
|
|
193
|
+
"metadata.name",
|
|
194
|
+
"metadata.namespace",
|
|
195
|
+
"metadata.title"
|
|
196
|
+
]
|
|
197
|
+
}).catch((_) => setEntities([])).then(
|
|
198
|
+
(data) => data ? setEntities(compact(data.items)) : setEntities([])
|
|
199
|
+
);
|
|
324
200
|
}
|
|
325
201
|
}, [catalogApi, question]);
|
|
326
|
-
return /* @__PURE__ */ React.createElement(Card, { variant: "outlined" }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
|
|
202
|
+
return /* @__PURE__ */ React.createElement(Card, { variant: "outlined" }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement("div", { className: styles.questionCardVote }, /* @__PURE__ */ React.createElement(VoteButtons, { entity: question })), /* @__PURE__ */ React.createElement("div", { className: styles.questionCardContent }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", gutterBottom: true }, /* @__PURE__ */ React.createElement(MarkdownContent, { content: question.content, dialect: "gfm" })), /* @__PURE__ */ React.createElement(Box, { className: styles.questionCardMetadata }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 0, justifyContent: "space-around" }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 8 }, question.tags && question.tags.map((tag) => /* @__PURE__ */ React.createElement(
|
|
327
203
|
Chip,
|
|
328
204
|
{
|
|
329
205
|
label: tag,
|
|
@@ -332,7 +208,7 @@ const QuestionCard = (props) => {
|
|
|
332
208
|
href: `/qeta/tags/${tag}`,
|
|
333
209
|
clickable: true
|
|
334
210
|
}
|
|
335
|
-
)),
|
|
211
|
+
)), entities && entities.map((component) => {
|
|
336
212
|
var _a, _b;
|
|
337
213
|
return /* @__PURE__ */ React.createElement(
|
|
338
214
|
Tooltip,
|
|
@@ -352,11 +228,29 @@ const QuestionCard = (props) => {
|
|
|
352
228
|
}
|
|
353
229
|
)
|
|
354
230
|
);
|
|
355
|
-
})), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 4 }, "Asked ", /* @__PURE__ */ React.createElement(RelativeTime, { value: question.created }), " by", " ", /* @__PURE__ */ React.createElement(Link, { href: `/qeta/users/${question.author}` }, question.author)))))
|
|
231
|
+
})), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 4, className: styles.questionCardAuthor }, "Asked ", /* @__PURE__ */ React.createElement(RelativeTime, { value: question.created }), " by", " ", /* @__PURE__ */ React.createElement(Link, { href: `/qeta/users/${question.author}` }, formatUsername(question.author)))), question.own && /* @__PURE__ */ React.createElement(Box, { className: styles.questionCardActions }, /* @__PURE__ */ React.createElement(Link, { underline: "none", href: "#", onClick: handleDeleteModalOpen }, "Delete"), /* @__PURE__ */ React.createElement(
|
|
232
|
+
Link,
|
|
233
|
+
{
|
|
234
|
+
underline: "none",
|
|
235
|
+
href: `/qeta/questions/${question.id}/edit`
|
|
236
|
+
},
|
|
237
|
+
"Edit"
|
|
238
|
+
), /* @__PURE__ */ React.createElement(
|
|
239
|
+
DeleteModal,
|
|
240
|
+
{
|
|
241
|
+
open: deleteModalOpen,
|
|
242
|
+
onClose: handleDeleteModalClose,
|
|
243
|
+
entity: question
|
|
244
|
+
}
|
|
245
|
+
))))));
|
|
356
246
|
};
|
|
357
247
|
|
|
248
|
+
const getDefaultValues = (questionId) => {
|
|
249
|
+
return { questionId, answer: "" };
|
|
250
|
+
};
|
|
358
251
|
const AnswerForm = (props) => {
|
|
359
|
-
const { question, onPost } = props;
|
|
252
|
+
const { question, onPost, id } = props;
|
|
253
|
+
const [values, setValues] = React.useState(getDefaultValues(question.id));
|
|
360
254
|
const [error, setError] = React.useState(false);
|
|
361
255
|
const qetaApi = useApi(qetaApiRef);
|
|
362
256
|
const styles = useStyles();
|
|
@@ -365,8 +259,22 @@ const AnswerForm = (props) => {
|
|
|
365
259
|
control,
|
|
366
260
|
formState: { errors },
|
|
367
261
|
reset
|
|
368
|
-
} = useForm(
|
|
262
|
+
} = useForm({
|
|
263
|
+
values,
|
|
264
|
+
defaultValues: getDefaultValues(question.id)
|
|
265
|
+
});
|
|
369
266
|
const postAnswer = (data) => {
|
|
267
|
+
if (id) {
|
|
268
|
+
qetaApi.updateAnswer(id, { questionId: question.id, answer: data.answer }).then((a) => {
|
|
269
|
+
if (!a || !("id" in a)) {
|
|
270
|
+
setError(true);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
reset();
|
|
274
|
+
onPost(a);
|
|
275
|
+
}).catch((_e) => setError(true));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
370
278
|
qetaApi.postAnswer({ questionId: question.id, answer: data.answer }).then((a) => {
|
|
371
279
|
if (!a || !("id" in a)) {
|
|
372
280
|
setError(true);
|
|
@@ -376,6 +284,20 @@ const AnswerForm = (props) => {
|
|
|
376
284
|
onPost(a);
|
|
377
285
|
}).catch((_e) => setError(true));
|
|
378
286
|
};
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (id) {
|
|
289
|
+
qetaApi.getAnswer(question.id, id).then((a) => {
|
|
290
|
+
if ("content" in a) {
|
|
291
|
+
setValues({ questionId: question.id, answer: a.content });
|
|
292
|
+
} else {
|
|
293
|
+
setError(true);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}, [id, question, qetaApi]);
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
reset(values);
|
|
300
|
+
}, [values, reset]);
|
|
379
301
|
return /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit(postAnswer) }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "Your answer"), error && /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not post answer" }), /* @__PURE__ */ React.createElement(
|
|
380
302
|
Controller,
|
|
381
303
|
{
|
|
@@ -395,18 +317,65 @@ const AnswerForm = (props) => {
|
|
|
395
317
|
),
|
|
396
318
|
name: "answer"
|
|
397
319
|
}
|
|
398
|
-
), /* @__PURE__ */ React.createElement(Button, { variant: "contained", type: "submit", className: styles.postButton }, "Post"));
|
|
320
|
+
), /* @__PURE__ */ React.createElement(Button, { variant: "contained", type: "submit", className: styles.postButton }, id ? "Save" : "Post"));
|
|
399
321
|
};
|
|
400
322
|
|
|
401
323
|
const AnswerCard = (props) => {
|
|
402
324
|
const { answer, question } = props;
|
|
403
325
|
const styles = useStyles();
|
|
404
|
-
|
|
326
|
+
const [editMode, setEditMode] = React.useState(false);
|
|
327
|
+
const [answerEntity, setAnswerEntity] = React.useState(answer);
|
|
328
|
+
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
|
329
|
+
const handleDeleteModalOpen = () => setDeleteModalOpen(true);
|
|
330
|
+
const handleDeleteModalClose = () => setDeleteModalOpen(false);
|
|
331
|
+
const onAnswerEdit = (a) => {
|
|
332
|
+
setEditMode(false);
|
|
333
|
+
setAnswerEntity(a);
|
|
334
|
+
};
|
|
335
|
+
return /* @__PURE__ */ React.createElement(Card, { id: `a${answer.id}` }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 0 }, /* @__PURE__ */ React.createElement(Grid, { item: true, className: styles.questionCardVote }, /* @__PURE__ */ React.createElement(VoteButtons, { entity: answerEntity, question })), /* @__PURE__ */ React.createElement(Grid, { item: true }, editMode ? /* @__PURE__ */ React.createElement(
|
|
336
|
+
AnswerForm,
|
|
337
|
+
{
|
|
338
|
+
question,
|
|
339
|
+
onPost: onAnswerEdit,
|
|
340
|
+
id: answerEntity.id
|
|
341
|
+
}
|
|
342
|
+
) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", gutterBottom: true }, /* @__PURE__ */ React.createElement(
|
|
343
|
+
MarkdownContent,
|
|
344
|
+
{
|
|
345
|
+
content: answerEntity.content,
|
|
346
|
+
dialect: "gfm"
|
|
347
|
+
}
|
|
348
|
+
)), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { variant: "caption", gutterBottom: true }, "By", " ", /* @__PURE__ */ React.createElement(Link, { href: `/qeta/users/${answerEntity.author}` }, formatUsername(answerEntity.author)), " ", /* @__PURE__ */ React.createElement(RelativeTime, { value: answerEntity.created }), answerEntity.updated && /* @__PURE__ */ React.createElement(React.Fragment, null, " ", "(updated ", /* @__PURE__ */ React.createElement(RelativeTime, { value: answerEntity.updated }), ")"))), answerEntity.own && /* @__PURE__ */ React.createElement(Box, { className: styles.questionCardActions }, /* @__PURE__ */ React.createElement(
|
|
349
|
+
Link,
|
|
350
|
+
{
|
|
351
|
+
underline: "none",
|
|
352
|
+
href: "#",
|
|
353
|
+
onClick: handleDeleteModalOpen
|
|
354
|
+
},
|
|
355
|
+
"Delete"
|
|
356
|
+
), /* @__PURE__ */ React.createElement(
|
|
357
|
+
Link,
|
|
358
|
+
{
|
|
359
|
+
underline: "none",
|
|
360
|
+
href: "#",
|
|
361
|
+
onClick: () => setEditMode(true)
|
|
362
|
+
},
|
|
363
|
+
"Edit"
|
|
364
|
+
), /* @__PURE__ */ React.createElement(
|
|
365
|
+
DeleteModal,
|
|
366
|
+
{
|
|
367
|
+
open: deleteModalOpen,
|
|
368
|
+
onClose: handleDeleteModalClose,
|
|
369
|
+
entity: answerEntity,
|
|
370
|
+
question
|
|
371
|
+
}
|
|
372
|
+
)))))));
|
|
405
373
|
};
|
|
406
374
|
|
|
407
375
|
const QuestionPage = () => {
|
|
408
376
|
var _a;
|
|
409
377
|
const { id } = useParams();
|
|
378
|
+
const styles = useStyles();
|
|
410
379
|
const [newAnswers, setNewAnswers] = React.useState([]);
|
|
411
380
|
const {
|
|
412
381
|
value: question,
|
|
@@ -417,7 +386,7 @@ const QuestionPage = () => {
|
|
|
417
386
|
setNewAnswers(newAnswers.concat([answer]));
|
|
418
387
|
};
|
|
419
388
|
const getDescription = (q) => {
|
|
420
|
-
return /* @__PURE__ */ React.createElement("span", null, "Asked", " ", /* @__PURE__ */ React.createElement(Box, { fontWeight: "fontWeightMedium", display: "inline", sx: { mr: 2 } }, /* @__PURE__ */ React.createElement(RelativeTime, { value: q.created })), q.updated && /* @__PURE__ */ React.createElement(React.Fragment, null, "Updated", " ", /* @__PURE__ */ React.createElement(Box, { fontWeight: "fontWeightMedium", display: "inline" }, /* @__PURE__ */ React.createElement(RelativeTime, { value: q.
|
|
389
|
+
return /* @__PURE__ */ React.createElement("span", null, "Asked", " ", /* @__PURE__ */ React.createElement(Box, { fontWeight: "fontWeightMedium", display: "inline", sx: { mr: 2 } }, /* @__PURE__ */ React.createElement(RelativeTime, { value: q.created })), q.updated && /* @__PURE__ */ React.createElement(React.Fragment, null, "Updated", " ", /* @__PURE__ */ React.createElement(Box, { fontWeight: "fontWeightMedium", display: "inline", sx: { mr: 2 } }, /* @__PURE__ */ React.createElement(RelativeTime, { value: q.updated }))), "Viewed", " ", /* @__PURE__ */ React.createElement(Box, { fontWeight: "fontWeightMedium", display: "inline" }, q.views, " times"));
|
|
421
390
|
};
|
|
422
391
|
if (loading) {
|
|
423
392
|
return /* @__PURE__ */ React.createElement(Skeleton, { variant: "rect", height: 200 });
|
|
@@ -431,7 +400,15 @@ const QuestionPage = () => {
|
|
|
431
400
|
title: question.title,
|
|
432
401
|
description: getDescription(question)
|
|
433
402
|
},
|
|
434
|
-
/* @__PURE__ */ React.createElement(
|
|
403
|
+
/* @__PURE__ */ React.createElement(
|
|
404
|
+
Button,
|
|
405
|
+
{
|
|
406
|
+
className: styles.marginRight,
|
|
407
|
+
href: "/qeta",
|
|
408
|
+
startIcon: /* @__PURE__ */ React.createElement(HomeOutlined, null)
|
|
409
|
+
},
|
|
410
|
+
"Back to questions"
|
|
411
|
+
),
|
|
435
412
|
/* @__PURE__ */ React.createElement(
|
|
436
413
|
Button,
|
|
437
414
|
{
|
|
@@ -493,7 +470,16 @@ const TagsContainer = () => {
|
|
|
493
470
|
|
|
494
471
|
const TagPage = () => {
|
|
495
472
|
const { tag } = useParams();
|
|
496
|
-
|
|
473
|
+
const styles = useStyles();
|
|
474
|
+
return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: tag ? `Questions tagged [${tag}]` : "Tags" }, /* @__PURE__ */ React.createElement(
|
|
475
|
+
Button,
|
|
476
|
+
{
|
|
477
|
+
href: "/qeta",
|
|
478
|
+
className: styles.marginRight,
|
|
479
|
+
startIcon: /* @__PURE__ */ React.createElement(HomeOutlined, null)
|
|
480
|
+
},
|
|
481
|
+
"Back to questions"
|
|
482
|
+
), /* @__PURE__ */ React.createElement(
|
|
497
483
|
Button,
|
|
498
484
|
{
|
|
499
485
|
variant: "contained",
|
|
@@ -507,7 +493,8 @@ const TagPage = () => {
|
|
|
507
493
|
const UserPage = () => {
|
|
508
494
|
var _a;
|
|
509
495
|
const identity = (_a = useParams()["*"]) != null ? _a : "unknown";
|
|
510
|
-
|
|
496
|
+
const styles = useStyles();
|
|
497
|
+
return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: `Questions by ${formatUsername(identity)}` }, /* @__PURE__ */ React.createElement(Button, { href: "/qeta", className: styles.marginRight }, "Back to questions"), /* @__PURE__ */ React.createElement(Button, { variant: "contained", href: "/qeta/ask" }, "Ask question")), /* @__PURE__ */ React.createElement(QuestionsContainer, { author: identity != null ? identity : "" }));
|
|
511
498
|
};
|
|
512
499
|
|
|
513
500
|
const QuestionHighlightList = (props) => {
|
|
@@ -558,7 +545,16 @@ const QuestionHighlightList = (props) => {
|
|
|
558
545
|
};
|
|
559
546
|
|
|
560
547
|
const HomePageContent = () => {
|
|
561
|
-
|
|
548
|
+
const styles = useStyles();
|
|
549
|
+
return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React.createElement(Grid, { item: true, md: 12, lg: 9, xl: 10 }, /* @__PURE__ */ React.createElement(ContentHeader, { title: "All questions" }, /* @__PURE__ */ React.createElement(
|
|
550
|
+
Button,
|
|
551
|
+
{
|
|
552
|
+
href: "/qeta/tags",
|
|
553
|
+
className: styles.marginRight,
|
|
554
|
+
startIcon: /* @__PURE__ */ React.createElement(LoyaltyOutlined, null)
|
|
555
|
+
},
|
|
556
|
+
"Tags"
|
|
557
|
+
), /* @__PURE__ */ React.createElement(
|
|
562
558
|
Button,
|
|
563
559
|
{
|
|
564
560
|
variant: "contained",
|
|
@@ -582,7 +578,7 @@ const HomePageContent = () => {
|
|
|
582
578
|
}
|
|
583
579
|
))));
|
|
584
580
|
};
|
|
585
|
-
const HomePage = () => /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(Header, { title: "Q&A" }, /* @__PURE__ */ React.createElement(
|
|
581
|
+
const HomePage = () => /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(Header, { title: "Q&A", style: { paddingTop: "1rem", paddingBottom: "1rem" } }), /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: "/", element: /* @__PURE__ */ React.createElement(HomePageContent, null) }), /* @__PURE__ */ React.createElement(Route, { path: "/ask", element: /* @__PURE__ */ React.createElement(AskPage, null) }), /* @__PURE__ */ React.createElement(Route, { path: "/questions/:id/edit", element: /* @__PURE__ */ React.createElement(AskPage, null) }), /* @__PURE__ */ React.createElement(Route, { path: "/questions/:id", element: /* @__PURE__ */ React.createElement(QuestionPage, null) }), /* @__PURE__ */ React.createElement(Route, { path: "/tags/:tag?", element: /* @__PURE__ */ React.createElement(TagPage, null) }), /* @__PURE__ */ React.createElement(Route, { path: "/users/*", element: /* @__PURE__ */ React.createElement(UserPage, null) })));
|
|
586
582
|
|
|
587
583
|
export { HomePage };
|
|
588
|
-
//# sourceMappingURL=index-
|
|
584
|
+
//# sourceMappingURL=index-0a5a1a59.esm.js.map
|