@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.
@@ -1,209 +1,33 @@
1
1
  import React, { useEffect } from 'react';
2
- import { TextField, Button, Grid, useTheme, Tooltip, IconButton, Typography, Box, Card, CardContent, Chip, Link, Divider, Avatar, List, ListSubheader, ListItem, ListItemText } from '@material-ui/core';
3
- import { MarkdownContent, WarningPanel, Content, ContentHeader, InfoCard, Page, Header, HeaderLabel } from '@backstage/core-components';
4
- import { useNavigate, useParams, Routes, Route } from 'react-router-dom';
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 { u as useStyles, q as qetaApiRef, a as useQetaApi, Q as QuestionsContainer } from './index-c475e52e.esm.js';
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
- return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "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, null)))));
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 ("title" in entity) {
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 ("title" in entity) {
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 (!((_b = props.question) == null ? void 0 : _b.own)) {
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
- Tooltip,
99
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip, { title: voteUpTooltip }, /* @__PURE__ */ React.createElement(
100
+ IconButton,
267
101
  {
268
- title: props.entity.own ? "You cannot vote your own post" : "This answer is good"
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
- IconButton,
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
- title: props.entity.own ? "You cannot vote your own post" : "This answer is not good"
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
- IconButton,
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 [components, setComponents] = React.useState([]);
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.components) {
188
+ if (question.entities && question.entities.length > 0) {
319
189
  catalogApi.getEntitiesByRefs({
320
- entityRefs: question.components
321
- }).catch((_) => setComponents([])).then((data) => {
322
- data ? setComponents(compact(data.items)) : setComponents([]);
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(Grid, { container: true, spacing: 0 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 1, className: styles.questionCardVote }, /* @__PURE__ */ React.createElement(VoteButtons, { entity: question })), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 11 }, /* @__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(
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
- )), components && components.map((component) => {
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
- 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: answer, question })), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", gutterBottom: true }, /* @__PURE__ */ React.createElement(MarkdownContent, { content: answer.content, dialect: "gfm" })), /* @__PURE__ */ React.createElement(Box, null, "By", " ", /* @__PURE__ */ React.createElement(Link, { href: `/qeta/users/${answer.author}` }, answer.author))))));
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.created }))), "Viewed", " ", /* @__PURE__ */ React.createElement(Box, { fontWeight: "fontWeightMedium", display: "inline" }, q.views, " times"));
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(Button, { href: "/qeta", startIcon: /* @__PURE__ */ React.createElement(HomeOutlined, null) }, "Back to questions"),
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
- return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: tag ? `Questions tagged [${tag}]` : "Tags" }, /* @__PURE__ */ React.createElement(Button, { href: "/qeta", startIcon: /* @__PURE__ */ React.createElement(HomeOutlined, null) }, "Back to questions"), /* @__PURE__ */ React.createElement(
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
- return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: `Questions by [${identity}]` }, /* @__PURE__ */ React.createElement(Button, { href: "/qeta" }, "Back to questions"), /* @__PURE__ */ React.createElement(Button, { variant: "contained", href: "/qeta/ask" }, "Ask question")), /* @__PURE__ */ React.createElement(QuestionsContainer, { author: identity != null ? identity : "" }));
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
- 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(Button, { href: "/qeta/tags", startIcon: /* @__PURE__ */ React.createElement(LoyaltyOutlined, null) }, "Tags"), /* @__PURE__ */ React.createElement(
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(HeaderLabel, { label: "Lifecycle", value: "Alpha" })), /* @__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", 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) })));
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-567d8d04.esm.js.map
584
+ //# sourceMappingURL=index-0a5a1a59.esm.js.map