@drodil/backstage-plugin-qeta 2.3.0 → 2.3.2

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.
Files changed (103) hide show
  1. package/dist/api/QetaClient.esm.js +434 -0
  2. package/dist/api/QetaClient.esm.js.map +1 -0
  3. package/dist/components/AskAnonymouslyCheckbox/AskAnonymouslyCheckbox.esm.js +32 -0
  4. package/dist/components/AskAnonymouslyCheckbox/AskAnonymouslyCheckbox.esm.js.map +1 -0
  5. package/dist/components/AskForm/AskForm.esm.js +218 -0
  6. package/dist/components/AskForm/AskForm.esm.js.map +1 -0
  7. package/dist/components/AskForm/EntitiesInput.esm.js +98 -0
  8. package/dist/components/AskForm/EntitiesInput.esm.js.map +1 -0
  9. package/dist/components/AskForm/TagInput.esm.js +86 -0
  10. package/dist/components/AskForm/TagInput.esm.js.map +1 -0
  11. package/dist/components/AskPage/AskPage.esm.js +38 -0
  12. package/dist/components/AskPage/AskPage.esm.js.map +1 -0
  13. package/dist/components/Buttons/AskQuestionButton.esm.js +43 -0
  14. package/dist/components/Buttons/AskQuestionButton.esm.js.map +1 -0
  15. package/dist/components/Buttons/BackToQuestionsButton.esm.js +38 -0
  16. package/dist/components/Buttons/BackToQuestionsButton.esm.js.map +1 -0
  17. package/dist/components/CommentSection/CommentList.esm.js +47 -0
  18. package/dist/components/CommentSection/CommentList.esm.js.map +1 -0
  19. package/dist/components/CommentSection/CommentSection.esm.js +97 -0
  20. package/dist/components/CommentSection/CommentSection.esm.js.map +1 -0
  21. package/dist/components/DeleteModal/DeleteModal.esm.js +77 -0
  22. package/dist/components/DeleteModal/DeleteModal.esm.js.map +1 -0
  23. package/dist/components/FavoritePage/FavoritePage.esm.js +13 -0
  24. package/dist/components/FavoritePage/FavoritePage.esm.js.map +1 -0
  25. package/dist/components/HomePage/HomePage.esm.js +144 -0
  26. package/dist/components/HomePage/HomePage.esm.js.map +1 -0
  27. package/dist/components/HomePage/index.esm.js +2 -0
  28. package/dist/components/HomePage/index.esm.js.map +1 -0
  29. package/dist/components/Links/Links.esm.js +31 -0
  30. package/dist/components/Links/Links.esm.js.map +1 -0
  31. package/dist/components/MarkdownEditor/MarkdownEditor.esm.js +80 -0
  32. package/dist/components/MarkdownEditor/MarkdownEditor.esm.js.map +1 -0
  33. package/dist/components/QuestionHighlightList/QuestionHighlightList.esm.js +61 -0
  34. package/dist/components/QuestionHighlightList/QuestionHighlightList.esm.js.map +1 -0
  35. package/dist/components/QuestionPage/AnswerCard.esm.js +101 -0
  36. package/dist/components/QuestionPage/AnswerCard.esm.js.map +1 -0
  37. package/dist/components/QuestionPage/AnswerForm.esm.js +130 -0
  38. package/dist/components/QuestionPage/AnswerForm.esm.js.map +1 -0
  39. package/dist/components/QuestionPage/AuthorBox.esm.js +25 -0
  40. package/dist/components/QuestionPage/AuthorBox.esm.js.map +1 -0
  41. package/dist/components/QuestionPage/EntityChip.esm.js +26 -0
  42. package/dist/components/QuestionPage/EntityChip.esm.js.map +1 -0
  43. package/dist/components/QuestionPage/FavoriteButton.esm.js +43 -0
  44. package/dist/components/QuestionPage/FavoriteButton.esm.js.map +1 -0
  45. package/dist/components/QuestionPage/LinkButton.esm.js +32 -0
  46. package/dist/components/QuestionPage/LinkButton.esm.js.map +1 -0
  47. package/dist/components/QuestionPage/QuestionCard.esm.js +103 -0
  48. package/dist/components/QuestionPage/QuestionCard.esm.js.map +1 -0
  49. package/dist/components/QuestionPage/QuestionPage.esm.js +131 -0
  50. package/dist/components/QuestionPage/QuestionPage.esm.js.map +1 -0
  51. package/dist/components/QuestionPage/TagsAndEntities.esm.js +44 -0
  52. package/dist/components/QuestionPage/TagsAndEntities.esm.js.map +1 -0
  53. package/dist/components/QuestionPage/VoteButtons.esm.js +146 -0
  54. package/dist/components/QuestionPage/VoteButtons.esm.js.map +1 -0
  55. package/dist/components/QuestionTableCard/Content.esm.js +9 -0
  56. package/dist/components/QuestionTableCard/Content.esm.js.map +1 -0
  57. package/dist/components/QuestionTableCard/QuestionTableRow.esm.js +21 -0
  58. package/dist/components/QuestionTableCard/QuestionTableRow.esm.js.map +1 -0
  59. package/dist/components/QuestionTableCard/QuestionsTable.esm.js +130 -0
  60. package/dist/components/QuestionTableCard/QuestionsTable.esm.js.map +1 -0
  61. package/dist/components/QuestionTableCard/index.esm.js +3 -0
  62. package/dist/components/QuestionTableCard/index.esm.js.map +1 -0
  63. package/dist/components/QuestionsContainer/FilterPanel.esm.js +230 -0
  64. package/dist/components/QuestionsContainer/FilterPanel.esm.js.map +1 -0
  65. package/dist/components/QuestionsContainer/NoQuestionsCard.esm.js +46 -0
  66. package/dist/components/QuestionsContainer/NoQuestionsCard.esm.js.map +1 -0
  67. package/dist/components/QuestionsContainer/QuestionList.esm.js +102 -0
  68. package/dist/components/QuestionsContainer/QuestionList.esm.js.map +1 -0
  69. package/dist/components/QuestionsContainer/QuestionListItem.esm.js +119 -0
  70. package/dist/components/QuestionsContainer/QuestionListItem.esm.js.map +1 -0
  71. package/dist/components/QuestionsContainer/QuestionsContainer.esm.js +231 -0
  72. package/dist/components/QuestionsContainer/QuestionsContainer.esm.js.map +1 -0
  73. package/dist/components/RelativeTimeWithTooltip/RelativeTimeWithTooltip.esm.js +22 -0
  74. package/dist/components/RelativeTimeWithTooltip/RelativeTimeWithTooltip.esm.js.map +1 -0
  75. package/dist/components/Statistics/StatisticsPage.esm.js +13 -0
  76. package/dist/components/Statistics/StatisticsPage.esm.js.map +1 -0
  77. package/dist/components/Statistics/TopRankingUsersCard.esm.js +163 -0
  78. package/dist/components/Statistics/TopRankingUsersCard.esm.js.map +1 -0
  79. package/dist/components/Statistics/TrophyIcon.esm.js +19 -0
  80. package/dist/components/Statistics/TrophyIcon.esm.js.map +1 -0
  81. package/dist/components/Statistics/styles.esm.js +23 -0
  82. package/dist/components/Statistics/styles.esm.js.map +1 -0
  83. package/dist/components/TagPage/TagPage.esm.js +16 -0
  84. package/dist/components/TagPage/TagPage.esm.js.map +1 -0
  85. package/dist/components/TagPage/TagsContainer.esm.js +63 -0
  86. package/dist/components/TagPage/TagsContainer.esm.js.map +1 -0
  87. package/dist/components/UserPage/UserPage.esm.js +24 -0
  88. package/dist/components/UserPage/UserPage.esm.js.map +1 -0
  89. package/dist/index.esm.js +9 -32
  90. package/dist/index.esm.js.map +1 -1
  91. package/dist/plugin.esm.js +60 -0
  92. package/dist/plugin.esm.js.map +1 -0
  93. package/dist/utils/hooks.esm.js +283 -0
  94. package/dist/utils/hooks.esm.js.map +1 -0
  95. package/dist/utils/utils.esm.js +31 -0
  96. package/dist/utils/utils.esm.js.map +1 -0
  97. package/package.json +15 -15
  98. package/dist/esm/index-C7A2tOkT.esm.js +0 -33
  99. package/dist/esm/index-C7A2tOkT.esm.js.map +0 -1
  100. package/dist/esm/index-CFNWBJNR.esm.js +0 -2412
  101. package/dist/esm/index-CFNWBJNR.esm.js.map +0 -1
  102. package/dist/esm/index-rSPV8dxK.esm.js +0 -1120
  103. package/dist/esm/index-rSPV8dxK.esm.js.map +0 -1
@@ -1,2412 +0,0 @@
1
- import { createApiRef, createPlugin, createApiFactory, fetchApiRef, discoveryApiRef, createRoutableExtension, useApi, identityApiRef, configApiRef, useRouteRef, useAnalytics, errorApiRef } from '@backstage/core-plugin-api';
2
- import { createCardExtension } from '@backstage/plugin-home-react';
3
- import { CustomErrorBase } from '@backstage/errors';
4
- import omitBy from 'lodash/omitBy';
5
- import isEmpty from 'lodash/isEmpty';
6
- import { qetaRouteRef, tagRouteRef, questionRouteRef, userRouteRef, askRouteRef } from '@drodil/backstage-plugin-qeta-react';
7
- import useAsync from 'react-use/lib/useAsync';
8
- import { makeStyles, Box, Grid, FormGroup, FormControlLabel, Checkbox, FormControl, FormLabel, RadioGroup, TextField, Radio, Tooltip, Chip, useTheme, Card, CardContent, Typography, Avatar, Divider, Select, MenuItem, Button, Collapse, TableRow, TableCell, ButtonGroup, TableContainer, Table, TableHead, TableBody, TablePagination, SvgIcon, ListItem, ListItemAvatar, ListItemText, List, Container } from '@material-ui/core';
9
- import { catalogApiRef, useEntityPresentation, entityRouteRef, EntityRefLink } from '@backstage/plugin-catalog-react';
10
- import { trimEnd, compact } from 'lodash';
11
- import { useSearchParams, useNavigate } from 'react-router-dom';
12
- import React, { useEffect, useState, useRef, useMemo } from 'react';
13
- import useDebounce from 'react-use/lib/useDebounce';
14
- import { Autocomplete, Pagination, Alert } from '@material-ui/lab';
15
- import { stringifyEntityRef, getCompoundEntityRef, parseEntityRef } from '@backstage/catalog-model';
16
- import { Link, LinkButton, Progress, WarningPanel, MarkdownContent, CardTab, TabbedCard, Content as Content$1, ContentHeader } from '@backstage/core-components';
17
- import DOMPurify from 'dompurify';
18
- import RelativeTime from 'react-relative-time';
19
- import { useSignal } from '@backstage/plugin-signals-react';
20
- import HelpOutline from '@material-ui/icons/HelpOutline';
21
- import FilterList from '@material-ui/icons/FilterList';
22
- import { RequirePermission } from '@backstage/plugin-permission-react';
23
- import { qetaCreateQuestionPermission, filterTags } from '@drodil/backstage-plugin-qeta-common';
24
- import { Controller, useForm } from 'react-hook-form';
25
- import ReactMde from 'react-mde';
26
- import 'react-mde/lib/styles/css/react-mde.css';
27
- import 'react-mde/lib/styles/css/react-mde-editor.css';
28
- import 'react-mde/lib/styles/css/react-mde-toolbar.css';
29
- import FileType from 'file-type';
30
- import RefreshIcon from '@material-ui/icons/Refresh';
31
- import HomeOutlined from '@material-ui/icons/HomeOutlined';
32
-
33
- var __defProp = Object.defineProperty;
34
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
35
- var __publicField = (obj, key, value) => {
36
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
37
- return value;
38
- };
39
- const qetaApiRef = createApiRef({
40
- id: "plugin.qeta.service"
41
- });
42
- class QetaError extends CustomErrorBase {
43
- constructor(message, errors) {
44
- super(message);
45
- __publicField(this, "errors");
46
- this.errors = errors;
47
- }
48
- }
49
- class QetaClient {
50
- constructor(options) {
51
- __publicField(this, "fetchApi");
52
- __publicField(this, "discoveryApi");
53
- this.fetchApi = options.fetchApi;
54
- this.discoveryApi = options.discoveryApi;
55
- }
56
- async getBaseUrl() {
57
- return this.discoveryApi.getBaseUrl("qeta");
58
- }
59
- async getQuestions(options) {
60
- const query = this.getQueryParameters(options).toString();
61
- let url = `${await this.getBaseUrl()}/questions`;
62
- if (query) {
63
- url += `?${query}`;
64
- }
65
- const response = await this.fetchApi.fetch(url);
66
- if (response.status === 403) {
67
- return { questions: [], total: 0 };
68
- }
69
- const data = await response.json();
70
- if ("errors" in data) {
71
- throw new QetaError("Failed to fetch", data.errors);
72
- }
73
- return data;
74
- }
75
- async getQuestionsList(type) {
76
- const query = new URLSearchParams({ limit: "7" }).toString();
77
- let url = `${await this.getBaseUrl()}/questions/list/${type}`;
78
- if (query) {
79
- url += `?${query}`;
80
- }
81
- const response = await this.fetchApi.fetch(url);
82
- if (response.status === 403) {
83
- return { questions: [], total: 0 };
84
- }
85
- const data = await response.json();
86
- if ("errors" in data) {
87
- throw new QetaError("Failed to fetch", data.errors);
88
- }
89
- return data;
90
- }
91
- async postQuestion(question) {
92
- const response = await this.fetchApi.fetch(
93
- `${await this.getBaseUrl()}/questions`,
94
- {
95
- method: "POST",
96
- body: JSON.stringify(question),
97
- headers: { "Content-Type": "application/json" }
98
- }
99
- );
100
- const data = await response.json();
101
- if ("errors" in data) {
102
- throw new QetaError("Failed to fetch", data.errors);
103
- }
104
- return data;
105
- }
106
- async commentQuestion(id, content) {
107
- const response = await this.fetchApi.fetch(
108
- `${await this.getBaseUrl()}/questions/${id}/comments`,
109
- {
110
- method: "POST",
111
- body: JSON.stringify({ content }),
112
- headers: { "Content-Type": "application/json" }
113
- }
114
- );
115
- const data = await response.json();
116
- if ("errors" in data) {
117
- throw new QetaError("Failed to fetch", data.errors);
118
- }
119
- return data;
120
- }
121
- async deleteQuestionComment(questionId, id) {
122
- const response = await this.fetchApi.fetch(
123
- `${await this.getBaseUrl()}/questions/${questionId}/comments/${id}`,
124
- {
125
- method: "DELETE"
126
- }
127
- );
128
- const data = await response.json();
129
- if ("errors" in data) {
130
- throw new QetaError("Failed to fetch", data.errors);
131
- }
132
- return data;
133
- }
134
- async getQuestion(id) {
135
- if (!id) {
136
- throw new QetaError("Invalid id provided", void 0);
137
- }
138
- const response = await this.fetchApi.fetch(
139
- `${await this.getBaseUrl()}/questions/${id}`
140
- );
141
- const data = await response.json();
142
- if ("errors" in data) {
143
- throw new QetaError("Failed to fetch", data.errors);
144
- }
145
- return data;
146
- }
147
- async getTags() {
148
- const response = await this.fetchApi.fetch(
149
- `${await this.getBaseUrl()}/tags`
150
- );
151
- return await response.json();
152
- }
153
- async getEntities() {
154
- const response = await this.fetchApi.fetch(
155
- `${await this.getBaseUrl()}/entities`
156
- );
157
- return await response.json();
158
- }
159
- async voteQuestionUp(id) {
160
- if (!id) {
161
- throw new QetaError("Invalid id provided", void 0);
162
- }
163
- const response = await this.fetchApi.fetch(
164
- `${await this.getBaseUrl()}/questions/${id}/upvote`
165
- );
166
- const data = await response.json();
167
- if ("errors" in data) {
168
- throw new QetaError("Failed to fetch", data.errors);
169
- }
170
- return data;
171
- }
172
- async voteQuestionDown(id) {
173
- if (!id) {
174
- throw new QetaError("Invalid id provided", void 0);
175
- }
176
- const response = await this.fetchApi.fetch(
177
- `${await this.getBaseUrl()}/questions/${id}/downvote`
178
- );
179
- const data = await response.json();
180
- if ("errors" in data) {
181
- throw new QetaError("Failed to fetch", data.errors);
182
- }
183
- return data;
184
- }
185
- async favoriteQuestion(id) {
186
- if (!id) {
187
- throw new QetaError("Invalid id provided", void 0);
188
- }
189
- const response = await this.fetchApi.fetch(
190
- `${await this.getBaseUrl()}/questions/${id}/favorite`
191
- );
192
- const data = await response.json();
193
- if ("errors" in data) {
194
- throw new QetaError("Failed to fetch", data.errors);
195
- }
196
- return data;
197
- }
198
- async unfavoriteQuestion(id) {
199
- if (!id) {
200
- throw new QetaError("Invalid id provided", void 0);
201
- }
202
- const response = await this.fetchApi.fetch(
203
- `${await this.getBaseUrl()}/questions/${id}/unfavorite`
204
- );
205
- const data = await response.json();
206
- if ("errors" in data) {
207
- throw new QetaError("Failed to fetch", data.errors);
208
- }
209
- return data;
210
- }
211
- async postAnswer(answer) {
212
- const response = await this.fetchApi.fetch(
213
- `${await this.getBaseUrl()}/questions/${answer.questionId}/answers`,
214
- {
215
- method: "POST",
216
- body: JSON.stringify({
217
- answer: answer.answer,
218
- images: answer.images,
219
- anonymous: answer.anonymous
220
- }),
221
- headers: { "Content-Type": "application/json" }
222
- }
223
- );
224
- const data = await response.json();
225
- if ("errors" in data) {
226
- throw new QetaError("Failed to fetch", data.errors);
227
- }
228
- return data;
229
- }
230
- async commentAnswer(questionId, id, content) {
231
- const response = await this.fetchApi.fetch(
232
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${id}/comments`,
233
- {
234
- method: "POST",
235
- body: JSON.stringify({ content }),
236
- headers: { "Content-Type": "application/json" }
237
- }
238
- );
239
- const data = await response.json();
240
- if ("errors" in data) {
241
- throw new QetaError("Failed to fetch", data.errors);
242
- }
243
- return data;
244
- }
245
- async deleteAnswerComment(questionId, answerId, id) {
246
- const response = await this.fetchApi.fetch(
247
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${answerId}/comments/${id}`,
248
- {
249
- method: "DELETE"
250
- }
251
- );
252
- const data = await response.json();
253
- if ("errors" in data) {
254
- throw new QetaError("Failed to fetch", data.errors);
255
- }
256
- return data;
257
- }
258
- async voteAnswerUp(questionId, id) {
259
- if (!id || !questionId) {
260
- throw new QetaError("Invalid id provided", void 0);
261
- }
262
- const response = await this.fetchApi.fetch(
263
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${id}/upvote`
264
- );
265
- const data = await response.json();
266
- if ("errors" in data) {
267
- throw new QetaError("Failed to fetch", data.errors);
268
- }
269
- return data;
270
- }
271
- async voteAnswerDown(questionId, id) {
272
- if (!id || !questionId) {
273
- throw new QetaError("Invalid id provided", void 0);
274
- }
275
- const response = await this.fetchApi.fetch(
276
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${id}/downvote`
277
- );
278
- const data = await response.json();
279
- if ("errors" in data) {
280
- throw new QetaError("Failed to fetch", data.errors);
281
- }
282
- return data;
283
- }
284
- async markAnswerCorrect(questionId, id) {
285
- if (!id || !questionId) {
286
- throw new QetaError("Invalid id provided", void 0);
287
- }
288
- const response = await this.fetchApi.fetch(
289
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${id}/correct`
290
- );
291
- const data = await response;
292
- return data.ok;
293
- }
294
- async markAnswerIncorrect(questionId, id) {
295
- if (!id || !questionId) {
296
- throw new QetaError("Invalid id provided", void 0);
297
- }
298
- const response = await this.fetchApi.fetch(
299
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${id}/incorrect`
300
- );
301
- const data = await response;
302
- return data.ok;
303
- }
304
- async deleteQuestion(questionId) {
305
- if (!questionId) {
306
- throw new QetaError("Invalid id provided", void 0);
307
- }
308
- const response = await this.fetchApi.fetch(
309
- `${await this.getBaseUrl()}/questions/${questionId}`,
310
- {
311
- method: "DELETE"
312
- }
313
- );
314
- const data = await response;
315
- return data.ok;
316
- }
317
- async deleteAnswer(questionId, id) {
318
- if (!questionId || !id) {
319
- throw new QetaError("Invalid id provided", void 0);
320
- }
321
- const response = await this.fetchApi.fetch(
322
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${id}`,
323
- {
324
- method: "DELETE"
325
- }
326
- );
327
- const data = await response;
328
- return data.ok;
329
- }
330
- async updateQuestion(id, question) {
331
- const response = await this.fetchApi.fetch(
332
- `${await this.getBaseUrl()}/questions/${id}`,
333
- {
334
- method: "POST",
335
- body: JSON.stringify(question),
336
- headers: { "Content-Type": "application/json" }
337
- }
338
- );
339
- const data = await response.json();
340
- if ("errors" in data) {
341
- throw new QetaError("Failed to update", data.errors);
342
- }
343
- return data;
344
- }
345
- async updateAnswer(id, answer) {
346
- const response = await this.fetchApi.fetch(
347
- `${await this.getBaseUrl()}/questions/${answer.questionId}/answers/${id}`,
348
- {
349
- method: "POST",
350
- body: JSON.stringify({ answer: answer.answer, images: answer.images }),
351
- headers: { "Content-Type": "application/json" }
352
- }
353
- );
354
- const data = await response.json();
355
- if ("errors" in data) {
356
- throw new QetaError("Failed to fetch", data.errors);
357
- }
358
- return data;
359
- }
360
- async getAnswer(questionId, id) {
361
- if (!questionId || !id) {
362
- throw new QetaError("Invalid id provided", void 0);
363
- }
364
- const response = await this.fetchApi.fetch(
365
- `${await this.getBaseUrl()}/questions/${questionId}/answers/${id}`
366
- );
367
- const data = await response.json();
368
- if ("errors" in data) {
369
- throw new QetaError("Failed to fetch", data.errors);
370
- }
371
- return data;
372
- }
373
- async postAttachment(file) {
374
- const qetaUrl = `${await this.getBaseUrl()}/attachments`;
375
- const formData = new FormData();
376
- formData.append("image", file);
377
- const requestOptions = {
378
- method: "POST",
379
- body: formData
380
- };
381
- const response = await this.fetchApi.fetch(qetaUrl, requestOptions);
382
- return await response.json();
383
- }
384
- async getMostUpvotedAnswers(options) {
385
- const query = this.getQueryParameters(options.options).toString();
386
- let url = `${await this.getBaseUrl()}/statistics/answers/top-upvoted-users`;
387
- if (query) {
388
- url += `?${query}`;
389
- }
390
- const response = await this.fetchApi.fetch(url);
391
- const data = await response.json();
392
- return data;
393
- }
394
- async getMostUpvotedCorrectAnswers(options) {
395
- const query = this.getQueryParameters(options.options).toString();
396
- let url = `${await this.getBaseUrl()}/statistics/answers/top-correct-upvoted-users`;
397
- if (query) {
398
- url += `?${query}`;
399
- }
400
- const response = await this.fetchApi.fetch(url);
401
- const data = await response.json();
402
- return data;
403
- }
404
- async getMostUpvotedQuestions(options) {
405
- const query = this.getQueryParameters(options.options).toString();
406
- let url = `${await this.getBaseUrl()}/statistics/questions/top-upvoted-users`;
407
- if (query) {
408
- url += `?${query}`;
409
- }
410
- const response = await this.fetchApi.fetch(url);
411
- const data = await response.json();
412
- return data;
413
- }
414
- async getMostQuestions(options) {
415
- const query = this.getQueryParameters(options.options).toString();
416
- let url = `${await this.getBaseUrl()}/statistics/questions/most-questions`;
417
- if (query) {
418
- url += `?${query}`;
419
- }
420
- const response = await this.fetchApi.fetch(url);
421
- const data = await response.json();
422
- return data;
423
- }
424
- async getMostAnswers(options) {
425
- const query = this.getQueryParameters(options.options).toString();
426
- let url = `${await this.getBaseUrl()}/statistics/answers/most-answers`;
427
- if (query) {
428
- url += `?${query}`;
429
- }
430
- const response = await this.fetchApi.fetch(url);
431
- const data = await response.json();
432
- return data;
433
- }
434
- async getTopStatisticsHomepage(options) {
435
- const response = await Promise.all([
436
- this.getMostQuestions(options),
437
- this.getMostAnswers(options),
438
- this.getMostUpvotedQuestions(options),
439
- this.getMostUpvotedAnswers(options),
440
- this.getMostUpvotedCorrectAnswers(options)
441
- ]);
442
- return response;
443
- }
444
- getQueryParameters(params) {
445
- const asStrings = Object.fromEntries(
446
- Object.entries(params).map(([k, v]) => {
447
- if (!v) {
448
- return [k, ""];
449
- }
450
- if (Array.isArray(v)) {
451
- return [k, v.join(",")];
452
- }
453
- return [k, `${v}`];
454
- })
455
- );
456
- return new URLSearchParams(omitBy(asStrings, isEmpty));
457
- }
458
- }
459
-
460
- const qetaPlugin = createPlugin({
461
- id: "qeta",
462
- routes: {
463
- root: qetaRouteRef
464
- },
465
- apis: [
466
- createApiFactory({
467
- api: qetaApiRef,
468
- deps: { fetchApi: fetchApiRef, discoveryApi: discoveryApiRef },
469
- factory: ({ fetchApi, discoveryApi }) => new QetaClient({ fetchApi, discoveryApi })
470
- })
471
- ]
472
- });
473
- const QetaPage = qetaPlugin.provide(
474
- createRoutableExtension({
475
- name: "QetaPage",
476
- component: () => import('./index-rSPV8dxK.esm.js').then((m) => m.HomePage),
477
- mountPoint: qetaRouteRef
478
- })
479
- );
480
- const QuestionTableCard = qetaPlugin.provide(
481
- createCardExtension({
482
- name: "QuestionsTableCard",
483
- title: "Q&A",
484
- description: "Shows Q&A questions",
485
- components: () => import('./index-C7A2tOkT.esm.js'),
486
- layout: {
487
- height: { minRows: 6 },
488
- width: { minColumns: 6 }
489
- },
490
- settings: {
491
- schema: {
492
- title: "Q&A",
493
- type: "object",
494
- properties: {
495
- rowsPerPage: {
496
- title: "Rows per page",
497
- type: "number",
498
- enum: [5, 10, 20, 30, 40, 50],
499
- default: 10
500
- },
501
- quickFilter: {
502
- title: "Default filter",
503
- type: "string",
504
- enum: ["latest", "favorites", "most_viewed"],
505
- default: "latest"
506
- }
507
- }
508
- }
509
- }
510
- })
511
- );
512
-
513
- function useQetaApi(f, deps = []) {
514
- const qetaApi = useApi(qetaApiRef);
515
- return useAsync(async () => {
516
- return await f(qetaApi);
517
- }, deps);
518
- }
519
- function useIdentityApi(f, deps = []) {
520
- const identityApi = useApi(identityApiRef);
521
- return useAsync(async () => {
522
- return await f(identityApi);
523
- }, deps);
524
- }
525
- function useEntityQueryParameter(entity) {
526
- const [searchParams] = useSearchParams();
527
- const [entityRef, setEntityRef] = React.useState(entity);
528
- useEffect(() => {
529
- var _a;
530
- setEntityRef((_a = searchParams.get("entity")) != null ? _a : void 0);
531
- }, [searchParams, setEntityRef]);
532
- return entityRef;
533
- }
534
- const useStyles$1 = makeStyles((theme) => {
535
- return {
536
- markdownEditor: {
537
- backgroundColor: "initial",
538
- color: theme.palette.text.primary,
539
- border: `1px solid ${theme.palette.action.disabled}`,
540
- borderRadius: theme.shape.borderRadius,
541
- "&:hover": {
542
- borderColor: theme.palette.action.active
543
- },
544
- "&:focus-within": {
545
- borderColor: theme.palette.primary.main
546
- },
547
- "& .mde-header": {
548
- backgroundColor: "initial",
549
- color: theme.palette.text.primary,
550
- borderBottom: `1px solid ${theme.palette.action.selected}`,
551
- "& .mde-tabs button, .mde-header-item > button": {
552
- color: `${theme.palette.text.primary} !important`
553
- }
554
- },
555
- "& .mde-preview-content": {
556
- padding: "10px"
557
- },
558
- "& .mde-text, .mde-preview": {
559
- fontSize: theme.typography.body1.fontSize,
560
- fontFamily: theme.typography.body1.fontFamily,
561
- lineHeight: theme.typography.body1.lineHeight
562
- },
563
- "& .mde-text": {
564
- backgroundColor: "initial",
565
- color: theme.palette.text.primary,
566
- outline: "none"
567
- },
568
- "& .image-tip": {
569
- color: theme.palette.text.primary,
570
- backgroundColor: "initial"
571
- }
572
- },
573
- markdownEditorError: {
574
- border: `1px solid ${theme.palette.error.main} !important`
575
- },
576
- markdownContent: {
577
- "& *": {
578
- wordBreak: "break-word"
579
- },
580
- "&.inline": {
581
- display: "inline-block"
582
- },
583
- "& > :first-child": {
584
- marginTop: "0px !important"
585
- },
586
- "& > :last-child": {
587
- marginBottom: "0px !important"
588
- }
589
- },
590
- successColor: {
591
- color: theme.palette.success.main
592
- },
593
- questionDivider: {
594
- marginTop: theme.spacing(2),
595
- marginBottom: theme.spacing(2)
596
- },
597
- questionCard: {
598
- marginBottom: theme.spacing(1),
599
- position: "relative"
600
- },
601
- questionCardVote: {
602
- textAlign: "center",
603
- width: "32px",
604
- marginRight: "26px",
605
- display: "inline-block",
606
- verticalAlign: "top"
607
- },
608
- questionCardContent: {
609
- display: "inline-block",
610
- width: "calc(100% - 70px)",
611
- minHeight: "160px"
612
- },
613
- questionListItemStats: {
614
- width: "80px",
615
- textAlign: "right",
616
- marginRight: "26px",
617
- display: "inline-block",
618
- verticalAlign: "top",
619
- paddingTop: "10px"
620
- },
621
- questionListItemContent: {
622
- display: "inline-block",
623
- width: "calc(100% - 120px)"
624
- },
625
- questionListItemAuthor: {
626
- display: "inline",
627
- float: "right"
628
- },
629
- questionListItemAvatar: {
630
- display: "inline-flex",
631
- marginRight: "0.25rem",
632
- fontSize: "1rem",
633
- maxWidth: "1rem",
634
- maxHeight: "1rem"
635
- },
636
- answerCardContent: {
637
- display: "inline-block",
638
- width: "calc(100% - 70px)"
639
- },
640
- questionCardAuthor: {
641
- padding: theme.spacing(1),
642
- float: "right",
643
- maxWidth: "200px",
644
- border: `1px solid ${theme.palette.action.selected}`,
645
- "& .avatar": {
646
- width: theme.spacing(3),
647
- height: theme.spacing(3),
648
- fontSize: "1rem"
649
- }
650
- },
651
- questionListPagination: {
652
- marginTop: theme.spacing(2)
653
- },
654
- postButton: {
655
- marginTop: theme.spacing(1),
656
- marginBottom: theme.spacing(1)
657
- },
658
- questionsPerPageInput: {
659
- paddingTop: "10px"
660
- },
661
- questionsPerPage: {
662
- marginRight: theme.spacing(3)
663
- },
664
- questionHighlightList: {
665
- width: "100%",
666
- border: `1px solid ${theme.palette.action.selected}`,
667
- borderRadius: theme.shape.borderRadius,
668
- "&:not(:first-child)": {
669
- marginTop: theme.spacing(2)
670
- }
671
- },
672
- filterPanel: {
673
- border: `1px solid ${theme.palette.action.selected}`,
674
- borderRadius: theme.shape.borderRadius,
675
- padding: theme.spacing(3)
676
- },
677
- questionCardMetadata: {
678
- marginTop: theme.spacing(3)
679
- },
680
- marginRight: {
681
- marginRight: theme.spacing(1)
682
- },
683
- questionCardActions: {
684
- marginTop: theme.spacing(2),
685
- "& a": {
686
- marginRight: theme.spacing(1)
687
- }
688
- },
689
- noPadding: {
690
- padding: `0 !important`
691
- },
692
- menuIcon: {
693
- minWidth: "26px"
694
- },
695
- deleteModal: {
696
- position: "absolute",
697
- top: "20%",
698
- left: "50%",
699
- transform: "translate(-50%, -50%)",
700
- width: 400,
701
- backgroundColor: theme.palette.background.default,
702
- border: `1px solid ${theme.palette.action.selected}`,
703
- borderRadius: theme.shape.borderRadius,
704
- padding: theme.spacing(2),
705
- "& button": {
706
- marginTop: theme.spacing(2),
707
- float: "right"
708
- }
709
- },
710
- authorLink: {
711
- textOverflow: "ellipsis",
712
- overflow: "hidden",
713
- whiteSpace: "nowrap"
714
- },
715
- highlight: {
716
- animation: "$highlight 2s"
717
- },
718
- "@keyframes highlight": {
719
- "0%": {
720
- boxShadow: `0px 0px 0px 3px ${theme.palette.secondary.light}`
721
- },
722
- "100%": {
723
- boxShadow: "none"
724
- }
725
- }
726
- };
727
- });
728
- const useBaseUrl = () => {
729
- try {
730
- const config = useApi(configApiRef);
731
- return config.getOptionalString("app.baseUrl");
732
- } catch {
733
- return void 0;
734
- }
735
- };
736
- const useBasePath = () => {
737
- var _a;
738
- const base = "http://sample.dev";
739
- const url = (_a = useBaseUrl()) != null ? _a : "/";
740
- const { pathname } = new URL(url, base);
741
- return trimEnd(pathname, "/");
742
- };
743
- const useEntityAuthor = (entity) => {
744
- var _a;
745
- const catalogApi = useApi(catalogApiRef);
746
- const identityApi = useApi(identityApiRef);
747
- const [name, setName] = React.useState(void 0);
748
- const [user, setUser] = React.useState(null);
749
- const [initials, setInitials] = React.useState(null);
750
- const [currentUser, setCurrentUser] = React.useState(null);
751
- const anonymous = (_a = entity.anonymous) != null ? _a : false;
752
- let author = entity.author;
753
- if (!author.startsWith("user:")) {
754
- author = `user:${author}`;
755
- }
756
- const { primaryTitle: userName } = useEntityPresentation(author);
757
- useEffect(() => {
758
- if (!anonymous) {
759
- catalogApi.getEntityByRef(entity.author).catch((_) => setUser(null)).then((data) => data ? setUser(data) : setUser(null));
760
- }
761
- }, [catalogApi, entity, anonymous]);
762
- useEffect(() => {
763
- identityApi.getBackstageIdentity().then((res) => {
764
- var _a2;
765
- setCurrentUser((_a2 = res.userEntityRef) != null ? _a2 : "user:default/guest");
766
- });
767
- }, [identityApi]);
768
- useEffect(() => {
769
- let displayName = userName;
770
- if (entity.author === currentUser) {
771
- displayName = "You";
772
- if (anonymous) {
773
- displayName += " (anonymous)";
774
- }
775
- }
776
- setName(displayName);
777
- }, [entity.author, anonymous, currentUser, userName]);
778
- useEffect(() => {
779
- const init = (name != null ? name : "").split(" ").map((p) => p[0]).join("").substring(0, 2).toUpperCase();
780
- setInitials(init);
781
- }, [name]);
782
- return { name, initials, user };
783
- };
784
-
785
- const formatEntityName = (username) => {
786
- if (!username) {
787
- return "";
788
- }
789
- const plainName = username.split(/[/:]+/).pop();
790
- return plainName == null ? void 0 : plainName.split(/[_.-]+/).map((a) => a.charAt(0).toUpperCase() + a.slice(1)).join(" ");
791
- };
792
- const getEntityTitle = (entity) => {
793
- var _a, _b;
794
- const stringified = stringifyEntityRef(entity);
795
- return (_b = formatEntityName((_a = entity.metadata.title) != null ? _a : stringified)) != null ? _b : stringified;
796
- };
797
- const truncate = (str, n) => {
798
- return str.length > n ? `${str.slice(0, n - 1)}...` : str;
799
- };
800
- const removeMarkdownFormatting = (text) => {
801
- let fixed = text.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*/gm, "");
802
- fixed = text.replace(/<[^>]*>/g, "");
803
- fixed = fixed.replace(/```[\s\S]*?```/g, (match) => {
804
- return match.replace(/(^```[a-z]*\n)|(```$)/g, "").trim();
805
- });
806
- fixed = fixed.replace(/`{1,2}([^`]*)`{1,2}/g, "$1");
807
- fixed = fixed.replace(/(?:\*\*|__)([^\n*]+)(?:\*\*|__)/g, "$1").replace(/(?:\*|_)([^\n*]+)(?:\*|_)/g, "$1").replace(/(?:~~)([^~]+)(?:~~)/g, "$1").replace(/^[>\t]{0,3}>+\s?/gm, "").replace(/\[\^.+?\](: .*?$)?/g, "").replace(/^([ \t]*)([*\-+]|\d+\.)\s+/gm, "").replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/^#{1,6}[ \t]+/gm, "").replace(/^[=-]{2,}\s*$/g, "").replace(/(?:\r\n|\r|\n)/g, " ").replace(/(^\s+|\s+$)/g, "");
808
- fixed = fixed.replace(/<[^>]*>/g, "");
809
- return fixed;
810
- };
811
-
812
- const radioSelect = (value, label) => {
813
- return /* @__PURE__ */ React.createElement(
814
- FormControlLabel,
815
- {
816
- value,
817
- control: /* @__PURE__ */ React.createElement(Radio, { size: "small" }),
818
- label
819
- }
820
- );
821
- };
822
- const filterKeys = [
823
- "orderBy",
824
- "order",
825
- "noAnswers",
826
- "noCorrectAnswer",
827
- "noVotes",
828
- "entity",
829
- "tags"
830
- ];
831
- const FilterPanel = (props) => {
832
- const {
833
- onChange,
834
- filters,
835
- showEntityFilter = true,
836
- showTagFilter = true
837
- } = props;
838
- const styles = useStyles$1();
839
- const { value: refs } = useQetaApi((api) => api.getEntities(), []);
840
- const { value: tags } = useQetaApi((api) => api.getTags(), []);
841
- const catalogApi = useApi(catalogApiRef);
842
- const [availableEntities, setAvailableEntities] = React.useState(null);
843
- const [selectedEntity, setSelectedEntity] = React.useState(void 0);
844
- const [availableTags, setAvailableTags] = React.useState(
845
- null
846
- );
847
- useEffect(() => {
848
- if (tags && tags.length > 0 || filters.tags) {
849
- const ts = (tags != null ? tags : []).map((t) => t.tag);
850
- if (filters.tags) {
851
- ts.push(...filters.tags);
852
- }
853
- setAvailableTags([...new Set(ts)]);
854
- }
855
- }, [tags, filters.tags]);
856
- useEffect(() => {
857
- const entityRefs = [];
858
- if (filters.entity && !Array.isArray(filters.entity)) {
859
- entityRefs.push(filters.entity);
860
- }
861
- if (refs && (refs == null ? void 0 : refs.length) > 0) {
862
- refs == null ? void 0 : refs.forEach((ref) => {
863
- if (ref.entityRef !== filters.entity) {
864
- entityRefs.push(ref.entityRef);
865
- }
866
- });
867
- }
868
- if (entityRefs.length > 0) {
869
- catalogApi.getEntitiesByRefs({
870
- entityRefs,
871
- fields: [
872
- "kind",
873
- "metadata.name",
874
- "metadata.namespace",
875
- "metadata.title"
876
- ]
877
- }).then((resp) => {
878
- const filtered = resp.items.filter((i) => i !== void 0);
879
- setAvailableEntities(filtered);
880
- });
881
- }
882
- }, [filters.entity, catalogApi, refs]);
883
- useEffect(() => {
884
- if (filters.entity && availableEntities) {
885
- const value = availableEntities.find(
886
- (e) => stringifyEntityRef(e) === filters.entity
887
- );
888
- setSelectedEntity(value);
889
- if (!value) {
890
- onChange("entity", "");
891
- }
892
- } else {
893
- setSelectedEntity(void 0);
894
- }
895
- }, [availableEntities, filters.entity, onChange]);
896
- const handleChange = (event) => {
897
- let value = event.target.value;
898
- if (event.target.type === "checkbox") {
899
- value = event.target.checked ? "true" : "false";
900
- }
901
- onChange(event.target.name, value);
902
- };
903
- return /* @__PURE__ */ React.createElement(Box, { className: `qetaFilterPanel ${styles.filterPanel}` }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 4 }, /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3, xs: 4 }, /* @__PURE__ */ React.createElement(FormGroup, null, /* @__PURE__ */ React.createElement(
904
- FormControlLabel,
905
- {
906
- control: /* @__PURE__ */ React.createElement(
907
- Checkbox,
908
- {
909
- size: "small",
910
- name: "noAnswers",
911
- onChange: handleChange,
912
- checked: filters.noAnswers === "true"
913
- }
914
- ),
915
- label: "No answers"
916
- }
917
- ), /* @__PURE__ */ React.createElement(
918
- FormControlLabel,
919
- {
920
- control: /* @__PURE__ */ React.createElement(
921
- Checkbox,
922
- {
923
- size: "small",
924
- name: "noCorrectAnswer",
925
- checked: filters.noCorrectAnswer === "true",
926
- onChange: handleChange
927
- }
928
- ),
929
- label: "No correct answers"
930
- }
931
- ), /* @__PURE__ */ React.createElement(
932
- FormControlLabel,
933
- {
934
- control: /* @__PURE__ */ React.createElement(
935
- Checkbox,
936
- {
937
- size: "small",
938
- name: "noVotes",
939
- checked: filters.noVotes === "true",
940
- onChange: handleChange
941
- }
942
- ),
943
- label: "No votes"
944
- }
945
- ))), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 2, xs: 4 }, /* @__PURE__ */ React.createElement(FormControl, null, /* @__PURE__ */ React.createElement(FormLabel, { id: "qeta-filter-order-by" }, "Order by"), /* @__PURE__ */ React.createElement(
946
- RadioGroup,
947
- {
948
- "aria-labelledby": "qeta-filter-order-by",
949
- name: "orderBy",
950
- value: filters.orderBy,
951
- onChange: handleChange
952
- },
953
- radioSelect("created", "Created"),
954
- radioSelect("views", "Views"),
955
- radioSelect("score", "Score"),
956
- radioSelect("answersCount", "Answers")
957
- ))), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 2, xs: 4 }, /* @__PURE__ */ React.createElement(FormControl, null, /* @__PURE__ */ React.createElement(FormLabel, { id: "qeta-filter-order" }, "Order"), /* @__PURE__ */ React.createElement(
958
- RadioGroup,
959
- {
960
- "aria-labelledby": "qeta-filter-order",
961
- name: "order",
962
- value: filters.order,
963
- onChange: handleChange
964
- },
965
- radioSelect("desc", "Descending"),
966
- radioSelect("asc", "Ascending")
967
- ))), (availableEntities && availableEntities.length > 0 || availableTags && availableTags.length > 0) && (showEntityFilter || showTagFilter) && /* @__PURE__ */ React.createElement(Grid, { item: true, md: 4, xs: 8 }, /* @__PURE__ */ React.createElement(FormLabel, { id: "qeta-filter-entity" }, "Filters"), showEntityFilter && availableEntities && availableEntities.length > 0 && /* @__PURE__ */ React.createElement(
968
- Autocomplete,
969
- {
970
- multiple: false,
971
- className: "qetaEntityFilter",
972
- value: selectedEntity != null ? selectedEntity : null,
973
- id: "entities-select",
974
- options: availableEntities,
975
- getOptionLabel: getEntityTitle,
976
- getOptionSelected: (o, v) => {
977
- if (!o || !v) {
978
- return false;
979
- }
980
- return stringifyEntityRef(o) === stringifyEntityRef(v);
981
- },
982
- onChange: (_e, newValue) => {
983
- handleChange({
984
- target: {
985
- name: "entity",
986
- value: newValue ? stringifyEntityRef(newValue) : ""
987
- }
988
- });
989
- },
990
- renderInput: (params) => /* @__PURE__ */ React.createElement(
991
- TextField,
992
- {
993
- ...params,
994
- variant: "outlined",
995
- margin: "normal",
996
- label: "Entity",
997
- placeholder: "Type or select entity"
998
- }
999
- )
1000
- }
1001
- ), showTagFilter && availableTags && availableTags.length > 0 && /* @__PURE__ */ React.createElement(
1002
- Autocomplete,
1003
- {
1004
- multiple: true,
1005
- className: "qetaTagFilter",
1006
- value: filters.tags,
1007
- id: "tags-select",
1008
- options: availableTags,
1009
- onChange: (_e, newValue) => {
1010
- handleChange({
1011
- target: {
1012
- name: "tags",
1013
- value: newValue
1014
- }
1015
- });
1016
- },
1017
- renderInput: (params) => /* @__PURE__ */ React.createElement(
1018
- TextField,
1019
- {
1020
- ...params,
1021
- variant: "outlined",
1022
- margin: "normal",
1023
- label: "Tag",
1024
- placeholder: "Type or select tag"
1025
- }
1026
- )
1027
- }
1028
- ))));
1029
- };
1030
-
1031
- const EntityChip = (props) => {
1032
- const { entity } = props;
1033
- const entityRoute = useRouteRef(entityRouteRef);
1034
- const { primaryTitle, secondaryTitle } = useEntityPresentation(entity);
1035
- return /* @__PURE__ */ React.createElement(Tooltip, { title: secondaryTitle != null ? secondaryTitle : primaryTitle, arrow: true }, /* @__PURE__ */ React.createElement(
1036
- Chip,
1037
- {
1038
- label: primaryTitle,
1039
- size: "small",
1040
- variant: "outlined",
1041
- className: "qetaEntityChip",
1042
- component: "a",
1043
- href: entityRoute(getCompoundEntityRef(entity)),
1044
- clickable: true
1045
- }
1046
- ));
1047
- };
1048
-
1049
- const TagsAndEntities = (props) => {
1050
- const { question } = props;
1051
- const catalogApi = useApi(catalogApiRef);
1052
- const tagRoute = useRouteRef(tagRouteRef);
1053
- const [entities, setEntities] = React.useState([]);
1054
- useEffect(() => {
1055
- if (question.entities && question.entities.length > 0) {
1056
- catalogApi.getEntitiesByRefs({
1057
- entityRefs: question.entities,
1058
- fields: [
1059
- "kind",
1060
- "metadata.name",
1061
- "metadata.namespace",
1062
- "metadata.title"
1063
- ]
1064
- }).catch((_) => setEntities([])).then(
1065
- (data) => data ? setEntities(compact(data.items)) : setEntities([])
1066
- );
1067
- }
1068
- }, [catalogApi, question]);
1069
- return /* @__PURE__ */ React.createElement(React.Fragment, null, question.tags && question.tags.map((tag) => /* @__PURE__ */ React.createElement(
1070
- Chip,
1071
- {
1072
- key: tag,
1073
- label: tag,
1074
- size: "small",
1075
- className: "qetaTagChip",
1076
- component: "a",
1077
- href: tagRoute({ tag }),
1078
- clickable: true
1079
- }
1080
- )), entities && entities.map((component, i) => /* @__PURE__ */ React.createElement(EntityChip, { entity: component, key: i })));
1081
- };
1082
-
1083
- const RelativeTimeWithTooltip = (props) => {
1084
- const { value } = props;
1085
- let date = value;
1086
- const [updates, setUpdates] = React.useState(1);
1087
- useEffect(() => {
1088
- const interval = setInterval(() => {
1089
- setUpdates(updates === 1 ? 0 : 1);
1090
- }, 3e4);
1091
- return () => clearInterval(interval);
1092
- }, [updates, setUpdates]);
1093
- if (typeof date === "string" || date instanceof String) {
1094
- date = new Date(date);
1095
- }
1096
- return /* @__PURE__ */ React.createElement(Tooltip, { title: date.toLocaleString(navigator.languages) }, /* @__PURE__ */ React.createElement(RelativeTime, { value: date }));
1097
- };
1098
-
1099
- const QuestionListItem = (props) => {
1100
- var _a, _b;
1101
- const { question, entity } = props;
1102
- const [correctAnswer, setCorrectAnswer] = useState(question.correctAnswer);
1103
- const [answersCount, setAnswersCount] = useState(question.answersCount);
1104
- const [score, setScore] = useState(question.score);
1105
- const [views, setViews] = useState(question.views);
1106
- const { lastSignal } = useSignal(`qeta:question_${question.id}`);
1107
- useEffect(() => {
1108
- if ((lastSignal == null ? void 0 : lastSignal.type) === "question_stats") {
1109
- setCorrectAnswer(lastSignal.correctAnswer);
1110
- setAnswersCount(lastSignal.answersCount);
1111
- setScore(lastSignal.score);
1112
- setViews(lastSignal.views);
1113
- }
1114
- }, [lastSignal]);
1115
- const questionRoute = useRouteRef(questionRouteRef);
1116
- const userRoute = useRouteRef(userRouteRef);
1117
- const theme = useTheme();
1118
- const styles = useStyles$1();
1119
- const { name, initials, user } = useEntityAuthor(question);
1120
- return /* @__PURE__ */ React.createElement(Card, { className: "qetaQuestionListItem" }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Box, { className: styles.questionListItemStats }, /* @__PURE__ */ React.createElement(
1121
- Typography,
1122
- {
1123
- display: "block",
1124
- variant: "caption",
1125
- className: "qetaQuestionListItemScore"
1126
- },
1127
- score,
1128
- " score"
1129
- ), /* @__PURE__ */ React.createElement(
1130
- Typography,
1131
- {
1132
- variant: "caption",
1133
- display: "block",
1134
- className: `qetaQuestionListItemAnswers ${correctAnswer ? "qetaQuestionListItemCorrectAnswer" : "quetaQuestionListItemNoCorrectAnswer"}`,
1135
- style: {
1136
- color: correctAnswer ? theme.palette.success.main : void 0
1137
- }
1138
- },
1139
- answersCount,
1140
- " answers"
1141
- ), /* @__PURE__ */ React.createElement(
1142
- Typography,
1143
- {
1144
- display: "block",
1145
- variant: "caption",
1146
- className: "qetaQuestionListItemViews"
1147
- },
1148
- views,
1149
- " views"
1150
- )), /* @__PURE__ */ React.createElement(Box, { className: styles.questionListItemContent }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5", component: "div" }, /* @__PURE__ */ React.createElement(
1151
- Link,
1152
- {
1153
- to: entity ? `${questionRoute({
1154
- id: question.id.toString(10)
1155
- })}?entity=${entity}` : questionRoute({ id: question.id.toString(10) }),
1156
- className: "qetaQuestionListItemQuestionBtn"
1157
- },
1158
- question.title
1159
- )), /* @__PURE__ */ React.createElement(
1160
- Typography,
1161
- {
1162
- variant: "caption",
1163
- noWrap: true,
1164
- component: "div",
1165
- className: "qetaQuestionListItemContent",
1166
- style: { marginBottom: "5px" }
1167
- },
1168
- DOMPurify.sanitize(
1169
- truncate(removeMarkdownFormatting(question.content), 150)
1170
- )
1171
- ), /* @__PURE__ */ React.createElement(TagsAndEntities, { question }), /* @__PURE__ */ React.createElement(
1172
- Typography,
1173
- {
1174
- variant: "caption",
1175
- display: "inline",
1176
- className: `${styles.questionListItemAuthor} qetaQuestionListItemAuthor`
1177
- },
1178
- /* @__PURE__ */ React.createElement(
1179
- Avatar,
1180
- {
1181
- src: (_b = (_a = user == null ? void 0 : user.spec) == null ? void 0 : _a.profile) == null ? void 0 : _b.picture,
1182
- className: styles.questionListItemAvatar,
1183
- alt: name,
1184
- variant: "rounded"
1185
- },
1186
- initials
1187
- ),
1188
- question.author === "anonymous" ? "Anonymous" : /* @__PURE__ */ React.createElement(Link, { to: `${userRoute()}/${question.author}` }, name),
1189
- " ",
1190
- /* @__PURE__ */ React.createElement(
1191
- Link,
1192
- {
1193
- to: entity ? `${questionRoute({
1194
- id: question.id.toString(10)
1195
- })}?entity=${entity}` : questionRoute({ id: question.id.toString(10) }),
1196
- className: "qetaQuestionListItemQuestionBtn"
1197
- },
1198
- "asked ",
1199
- /* @__PURE__ */ React.createElement(RelativeTimeWithTooltip, { value: question.created })
1200
- )
1201
- ))));
1202
- };
1203
-
1204
- const NoQuestionsCard = (props) => {
1205
- const { showNoQuestionsBtn, entity, entityPage, tags } = props;
1206
- const askRoute = useRouteRef(askRouteRef);
1207
- const entityRef = useEntityQueryParameter(entity);
1208
- const queryParams = new URLSearchParams();
1209
- if (entityRef) {
1210
- queryParams.set("entity", entityRef);
1211
- }
1212
- if (entityPage) {
1213
- queryParams.set("entityPage", "true");
1214
- }
1215
- if (tags && tags.length > 0) {
1216
- queryParams.set("tags", tags.join(","));
1217
- }
1218
- return /* @__PURE__ */ React.createElement(Card, { style: { marginTop: "2rem" } }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1219
- Grid,
1220
- {
1221
- container: true,
1222
- justifyContent: "center",
1223
- alignItems: "center",
1224
- direction: "column"
1225
- },
1226
- /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "No questions found")),
1227
- showNoQuestionsBtn && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1228
- LinkButton,
1229
- {
1230
- to: entityRef || tags ? `${askRoute()}?${queryParams.toString()}` : `${askRoute()}`,
1231
- startIcon: /* @__PURE__ */ React.createElement(HelpOutline, null),
1232
- color: "primary",
1233
- variant: "outlined"
1234
- },
1235
- "Go ahead and ask one!"
1236
- ))
1237
- )));
1238
- };
1239
-
1240
- const QuestionList = (props) => {
1241
- const {
1242
- loading,
1243
- error,
1244
- response,
1245
- onPageChange,
1246
- entity,
1247
- page,
1248
- onPageSizeChange,
1249
- showNoQuestionsBtn = true,
1250
- entityPage,
1251
- tags
1252
- } = props;
1253
- const styles = useStyles$1();
1254
- const listRef = useRef(null);
1255
- const [initialLoad, setInitialLoad] = useState(true);
1256
- useEffect(() => {
1257
- if (!initialLoad) {
1258
- setInitialLoad(false);
1259
- }
1260
- }, [initialLoad, loading]);
1261
- const handlePageChange = (_event, value) => {
1262
- if (listRef.current) {
1263
- listRef.current.scrollIntoView();
1264
- }
1265
- onPageChange(value);
1266
- };
1267
- const handlePageSizeChange = (event) => {
1268
- if (listRef.current) {
1269
- listRef.current.scrollIntoView();
1270
- }
1271
- onPageSizeChange(Number.parseInt(event.target.value, 10));
1272
- };
1273
- if (loading && initialLoad) {
1274
- return /* @__PURE__ */ React.createElement(Progress, null);
1275
- }
1276
- if (error || response === void 0) {
1277
- return /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not load questions." }, error == null ? void 0 : error.message);
1278
- }
1279
- if (initialLoad && (!response.questions || response.questions.length === 0)) {
1280
- return /* @__PURE__ */ React.createElement(
1281
- NoQuestionsCard,
1282
- {
1283
- showNoQuestionsBtn,
1284
- entity,
1285
- entityPage,
1286
- tags
1287
- }
1288
- );
1289
- }
1290
- const pageCount = response.total < props.pageSize ? 1 : Math.ceil(response.total / props.pageSize);
1291
- return /* @__PURE__ */ React.createElement("div", { ref: listRef }, /* @__PURE__ */ React.createElement(Box, { sx: { mt: 2 }, className: "qetaQuestionList" }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2, className: "qetaQuestionListGrid" }, response.questions.map((question) => {
1292
- return /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, key: question.id }, /* @__PURE__ */ React.createElement(QuestionListItem, { question, entity }), /* @__PURE__ */ React.createElement(Divider, null));
1293
- })), /* @__PURE__ */ React.createElement(
1294
- Grid,
1295
- {
1296
- container: true,
1297
- spacing: 0,
1298
- className: `qetaQuestionListPaginationGrid ${styles.questionListPagination}`,
1299
- alignItems: "center",
1300
- justifyContent: "space-between"
1301
- },
1302
- /* @__PURE__ */ React.createElement(Tooltip, { title: "Questions per page", arrow: true }, /* @__PURE__ */ React.createElement(FormControl, { variant: "filled" }, /* @__PURE__ */ React.createElement(
1303
- Select,
1304
- {
1305
- value: props.pageSize,
1306
- onChange: handlePageSizeChange,
1307
- className: `qetaQuestionListPaginationSizeSelect ${styles.questionsPerPage}`,
1308
- inputProps: { className: styles.questionsPerPageInput }
1309
- },
1310
- /* @__PURE__ */ React.createElement(MenuItem, { value: 5 }, "5"),
1311
- /* @__PURE__ */ React.createElement(MenuItem, { value: 10 }, "10"),
1312
- /* @__PURE__ */ React.createElement(MenuItem, { value: 25 }, "25"),
1313
- /* @__PURE__ */ React.createElement(MenuItem, { value: 50 }, "50"),
1314
- /* @__PURE__ */ React.createElement(MenuItem, { value: 100 }, "100")
1315
- ))),
1316
- /* @__PURE__ */ React.createElement(
1317
- Pagination,
1318
- {
1319
- page,
1320
- onChange: handlePageChange,
1321
- count: pageCount,
1322
- size: "large",
1323
- variant: "outlined",
1324
- className: "qetaQuestionListPagination",
1325
- showFirstButton: true,
1326
- showLastButton: true
1327
- }
1328
- )
1329
- )));
1330
- };
1331
-
1332
- const AskQuestionButton = (props) => {
1333
- const { entity, entityPage, tags } = props;
1334
- const askRoute = useRouteRef(askRouteRef);
1335
- const params = new URLSearchParams();
1336
- if (entity) {
1337
- params.set("entity", entity);
1338
- }
1339
- if (entityPage) {
1340
- params.set("entityPage", "true");
1341
- }
1342
- if (tags && tags.length > 0) {
1343
- params.set("tags", tags.join(","));
1344
- }
1345
- return /* @__PURE__ */ React.createElement(
1346
- RequirePermission,
1347
- {
1348
- permission: qetaCreateQuestionPermission,
1349
- errorPage: /* @__PURE__ */ React.createElement(React.Fragment, null)
1350
- },
1351
- /* @__PURE__ */ React.createElement(
1352
- LinkButton,
1353
- {
1354
- variant: "contained",
1355
- to: entity || tags ? `${askRoute()}?${params.toString()}` : askRoute(),
1356
- color: "primary",
1357
- className: "qetaAskQuestionBtn",
1358
- startIcon: /* @__PURE__ */ React.createElement(HelpOutline, null)
1359
- },
1360
- "Ask question"
1361
- )
1362
- );
1363
- };
1364
-
1365
- const QuestionsContainer = (props) => {
1366
- var _a;
1367
- const {
1368
- tags,
1369
- author,
1370
- entity,
1371
- showFilters,
1372
- showTitle,
1373
- title,
1374
- favorite,
1375
- showAskButton,
1376
- showNoQuestionsBtn
1377
- } = props;
1378
- const analytics = useAnalytics();
1379
- const [page, setPage] = React.useState(1);
1380
- const [questionsPerPage, setQuestionsPerPage] = React.useState(10);
1381
- const [showFilterPanel, setShowFilterPanel] = React.useState(false);
1382
- const [searchParams, setSearchParams] = useSearchParams();
1383
- const [searchQuery, setSearchQuery] = React.useState("");
1384
- const [filters, setFilters] = React.useState({
1385
- order: "desc",
1386
- orderBy: "created",
1387
- noAnswers: "false",
1388
- noCorrectAnswer: "false",
1389
- noVotes: "false",
1390
- searchQuery: "",
1391
- entity: entity != null ? entity : "",
1392
- tags: tags != null ? tags : []
1393
- });
1394
- const onPageChange = (value) => {
1395
- setPage(value);
1396
- setSearchParams((prev) => {
1397
- const newValue = prev;
1398
- newValue.set("page", String(value));
1399
- return newValue;
1400
- });
1401
- };
1402
- const onFilterChange = (key, value) => {
1403
- if (filters[key] === value) {
1404
- return;
1405
- }
1406
- setPage(1);
1407
- setFilters({ ...filters, ...{ [key]: value } });
1408
- setSearchParams((prev) => {
1409
- const newValue = prev;
1410
- if (!value || value === "false") {
1411
- newValue.delete(key);
1412
- } else if (Array.isArray(value)) {
1413
- if (value.length === 0) {
1414
- newValue.delete(key);
1415
- } else {
1416
- newValue.set(key, value.join(","));
1417
- }
1418
- } else if (value.length > 0) {
1419
- newValue.set(key, value);
1420
- } else {
1421
- newValue.delete(key);
1422
- }
1423
- return newValue;
1424
- });
1425
- };
1426
- const onSearchQueryChange = (event) => {
1427
- onPageChange(1);
1428
- if (event.target.value) {
1429
- analytics.captureEvent("qeta_search", event.target.value);
1430
- }
1431
- setSearchQuery(event.target.value);
1432
- };
1433
- useDebounce(
1434
- () => {
1435
- if (filters.searchQuery !== searchQuery) {
1436
- setFilters({ ...filters, searchQuery });
1437
- }
1438
- },
1439
- 400,
1440
- [searchQuery]
1441
- );
1442
- useEffect(() => {
1443
- let filtersApplied = false;
1444
- searchParams.forEach((value, key) => {
1445
- var _a2;
1446
- try {
1447
- if (key === "page") {
1448
- const pv = Number.parseInt(value, 10);
1449
- if (pv > 0) {
1450
- setPage(pv);
1451
- } else {
1452
- setPage(1);
1453
- }
1454
- } else if (key === "questionsPerPage") {
1455
- const qpp = Number.parseInt(value, 10);
1456
- if (qpp > 0)
1457
- setQuestionsPerPage(qpp);
1458
- } else if (filterKeys.includes(key)) {
1459
- filtersApplied = true;
1460
- if (key === "tags") {
1461
- filters.tags = (_a2 = filterTags(value)) != null ? _a2 : [];
1462
- } else {
1463
- filters[key] = value;
1464
- }
1465
- }
1466
- } catch (_e) {
1467
- }
1468
- });
1469
- setFilters(filters);
1470
- if (filtersApplied) {
1471
- setShowFilterPanel(true);
1472
- }
1473
- }, [searchParams, filters]);
1474
- const {
1475
- value: response,
1476
- loading,
1477
- error
1478
- } = useQetaApi(
1479
- (api) => {
1480
- return api.getQuestions({
1481
- limit: questionsPerPage,
1482
- offset: (page - 1) * questionsPerPage,
1483
- includeEntities: true,
1484
- author,
1485
- favorite,
1486
- ...filters
1487
- });
1488
- },
1489
- [page, filters, questionsPerPage]
1490
- );
1491
- const onPageSizeChange = (value) => {
1492
- if (response) {
1493
- let newPage = page;
1494
- while (newPage * value > response.total) {
1495
- newPage -= 1;
1496
- }
1497
- onPageChange(Math.max(1, newPage));
1498
- }
1499
- setQuestionsPerPage(value);
1500
- setSearchParams((prev) => {
1501
- const newValue = prev;
1502
- newValue.set("questionsPerPage", String(value));
1503
- return newValue;
1504
- });
1505
- };
1506
- let shownTitle = title;
1507
- let link = void 0;
1508
- if (author) {
1509
- shownTitle = `Questions by `;
1510
- link = /* @__PURE__ */ React.createElement(EntityRefLink, { entityRef: author, hideIcon: true, defaultKind: "user" });
1511
- } else if (entity) {
1512
- shownTitle = `Questions about `;
1513
- link = /* @__PURE__ */ React.createElement(EntityRefLink, { entityRef: entity });
1514
- } else if (tags) {
1515
- shownTitle = `Questions tagged with [${tags.join(", ")}]`;
1516
- } else if (favorite) {
1517
- shownTitle = "Your favorite questions";
1518
- }
1519
- return /* @__PURE__ */ React.createElement(Box, { className: "qetaQuestionsContainer" }, showTitle && /* @__PURE__ */ React.createElement(
1520
- Typography,
1521
- {
1522
- variant: "h5",
1523
- className: "qetaQuestionsContainerTitle",
1524
- style: { marginBottom: "1.5rem" }
1525
- },
1526
- shownTitle,
1527
- link
1528
- ), /* @__PURE__ */ React.createElement(Grid, { container: true, justifyContent: "space-between" }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, md: 4 }, /* @__PURE__ */ React.createElement(
1529
- TextField,
1530
- {
1531
- id: "search-bar",
1532
- fullWidth: true,
1533
- onChange: onSearchQueryChange,
1534
- label: "Search for questions",
1535
- className: "qetaQuestionsContainerSearchInput",
1536
- variant: "outlined",
1537
- placeholder: "Search...",
1538
- size: "small",
1539
- style: { marginBottom: "5px" }
1540
- }
1541
- )), showAskButton && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1542
- AskQuestionButton,
1543
- {
1544
- entity: entity != null ? entity : filters.entity,
1545
- entityPage: entity !== void 0,
1546
- tags
1547
- }
1548
- ))), /* @__PURE__ */ React.createElement(Grid, { container: true, justifyContent: "space-between" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1549
- Typography,
1550
- {
1551
- variant: "h6",
1552
- className: "qetaQuestionsContainerQuestionCount"
1553
- },
1554
- `${(_a = response == null ? void 0 : response.total) != null ? _a : 0} ${(response == null ? void 0 : response.total) === 1 ? "question" : "questions"}`
1555
- )), (showFilters != null ? showFilters : true) && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1556
- Button,
1557
- {
1558
- onClick: () => setShowFilterPanel(!showFilterPanel),
1559
- className: "qetaQuestionsContainerFilterPanelBtn",
1560
- startIcon: /* @__PURE__ */ React.createElement(FilterList, null)
1561
- },
1562
- "Filter"
1563
- ))), (showFilters != null ? showFilters : true) && /* @__PURE__ */ React.createElement(Collapse, { in: showFilterPanel }, /* @__PURE__ */ React.createElement(FilterPanel, { onChange: onFilterChange, filters })), /* @__PURE__ */ React.createElement(
1564
- QuestionList,
1565
- {
1566
- loading,
1567
- error,
1568
- response,
1569
- onPageChange,
1570
- onPageSizeChange,
1571
- entity,
1572
- page,
1573
- pageSize: questionsPerPage,
1574
- showNoQuestionsBtn,
1575
- entityPage: entity !== void 0,
1576
- tags
1577
- }
1578
- ));
1579
- };
1580
-
1581
- const MarkdownEditor = (props) => {
1582
- const { config, value, onChange, height, error, placeholder } = props;
1583
- const [selectedTab, setSelectedTab] = React.useState(
1584
- "write"
1585
- );
1586
- const styles = useStyles$1();
1587
- const errorApi = useApi(errorApiRef);
1588
- const qetaApi = useApi(qetaApiRef);
1589
- const imageUpload = () => {
1590
- return async function* (data) {
1591
- var _a, _b;
1592
- const fileType = await FileType.fromBuffer(data);
1593
- const mimeType = fileType ? fileType.mime : "text/plain";
1594
- const attachment = await qetaApi.postAttachment(
1595
- new Blob([data], { type: mimeType })
1596
- );
1597
- if ("errors" in attachment) {
1598
- errorApi.post({
1599
- name: "Upload failed",
1600
- message: (_b = (_a = attachment.errors) == null ? void 0 : _a.map((e) => e.message).join(", ")) != null ? _b : ""
1601
- });
1602
- return false;
1603
- }
1604
- props.onImageUpload(attachment.id);
1605
- yield attachment.locationUri;
1606
- return true;
1607
- };
1608
- };
1609
- const isUploadDisabled = (config == null ? void 0 : config.getOptionalBoolean("qeta.storage.disabled")) || false;
1610
- return /* @__PURE__ */ React.createElement(
1611
- ReactMde,
1612
- {
1613
- classes: {
1614
- reactMde: `qetaMarkdownEditorEdit ${styles.markdownEditor}`,
1615
- textArea: error ? `qetaMarkdownEditorError ${styles.markdownEditorError}` : void 0,
1616
- preview: "qetaMarkdownEditorPreview",
1617
- toolbar: "qetaMarkdownEditorToolbar"
1618
- },
1619
- value,
1620
- onChange,
1621
- selectedTab,
1622
- onTabChange: setSelectedTab,
1623
- minEditorHeight: height,
1624
- minPreviewHeight: height - 10,
1625
- childProps: {
1626
- textArea: {
1627
- required: true,
1628
- placeholder
1629
- }
1630
- },
1631
- generateMarkdownPreview: (content) => Promise.resolve(
1632
- /* @__PURE__ */ React.createElement(
1633
- MarkdownContent,
1634
- {
1635
- content,
1636
- dialect: "gfm",
1637
- className: `qetaMarkdownEditorPreview ${styles.markdownContent}`
1638
- }
1639
- )
1640
- ),
1641
- paste: isUploadDisabled ? void 0 : {
1642
- saveImage: imageUpload()
1643
- }
1644
- }
1645
- );
1646
- };
1647
-
1648
- const TagInput = (props) => {
1649
- const { control } = props;
1650
- const qetaApi = useApi(qetaApiRef);
1651
- const config = useApi(configApiRef);
1652
- const allowCreation = useMemo(
1653
- () => {
1654
- var _a;
1655
- return (_a = config.getOptionalBoolean("qeta.tags.allowCreation")) != null ? _a : true;
1656
- },
1657
- [config]
1658
- );
1659
- const allowedTags = useMemo(
1660
- () => {
1661
- var _a;
1662
- return (_a = config.getOptionalStringArray("qeta.tags.allowedTags")) != null ? _a : null;
1663
- },
1664
- [config]
1665
- );
1666
- const maximumTags = useMemo(
1667
- () => {
1668
- var _a;
1669
- return (_a = config.getOptionalNumber("qeta.tags.max")) != null ? _a : 5;
1670
- },
1671
- [config]
1672
- );
1673
- const [availableTags, setAvailableTags] = React.useState([]);
1674
- useEffect(() => {
1675
- if (allowCreation) {
1676
- qetaApi.getTags().catch((_) => setAvailableTags(null)).then(
1677
- (data) => data ? setAvailableTags(data.map((tag) => tag.tag)) : setAvailableTags(null)
1678
- );
1679
- } else {
1680
- setAvailableTags(allowedTags);
1681
- }
1682
- }, [qetaApi, allowCreation, allowedTags]);
1683
- if (!allowCreation && (allowedTags === null || allowedTags.length === 0)) {
1684
- return null;
1685
- }
1686
- return /* @__PURE__ */ React.createElement(
1687
- Controller,
1688
- {
1689
- control,
1690
- render: ({ field: { onChange, value }, fieldState: { error } }) => /* @__PURE__ */ React.createElement(
1691
- Autocomplete,
1692
- {
1693
- multiple: true,
1694
- id: "tags-select",
1695
- className: "qetaAskFormTags",
1696
- value,
1697
- options: availableTags != null ? availableTags : [],
1698
- freeSolo: allowCreation,
1699
- onChange: (_e, newValue) => {
1700
- const tags = filterTags(newValue);
1701
- if (tags && tags.length <= maximumTags && tags.length === newValue.length) {
1702
- onChange(newValue);
1703
- }
1704
- },
1705
- renderInput: (params) => /* @__PURE__ */ React.createElement(
1706
- TextField,
1707
- {
1708
- ...params,
1709
- variant: "outlined",
1710
- margin: "normal",
1711
- label: "Tags",
1712
- placeholder: "Type or select tags",
1713
- helperText: `Add up to ${maximumTags} tags to categorize your question`,
1714
- error: error !== void 0
1715
- }
1716
- )
1717
- }
1718
- ),
1719
- name: "tags"
1720
- }
1721
- );
1722
- };
1723
-
1724
- const EntitiesInput = (props) => {
1725
- const { control, entityRef } = props;
1726
- const configApi = useApi(configApiRef);
1727
- const catalogApi = useApi(catalogApiRef);
1728
- const [availableEntities, setAvailableEntities] = React.useState([]);
1729
- const entityKinds = useMemo(() => {
1730
- let kinds = configApi.getOptionalStringArray("qeta.entityKinds");
1731
- if (!kinds) {
1732
- kinds = configApi.getOptionalStringArray("qeta.entities.kinds");
1733
- }
1734
- return kinds || ["Component", "System"];
1735
- }, [configApi]);
1736
- const max = useMemo(
1737
- () => {
1738
- var _a;
1739
- return (_a = configApi.getOptionalNumber("qeta.entities.max")) != null ? _a : 3;
1740
- },
1741
- [configApi]
1742
- );
1743
- useEffect(() => {
1744
- if (entityRef) {
1745
- catalogApi.getEntityByRef(entityRef).then((data) => {
1746
- if (data) {
1747
- setAvailableEntities([data]);
1748
- }
1749
- });
1750
- }
1751
- }, [catalogApi, entityRef]);
1752
- useEffect(() => {
1753
- if (entityRef) {
1754
- return;
1755
- }
1756
- if (entityKinds && entityKinds.length > 0) {
1757
- catalogApi.getEntities({
1758
- filter: { kind: entityKinds },
1759
- fields: [
1760
- "kind",
1761
- "metadata.name",
1762
- "metadata.namespace",
1763
- "metadata.title"
1764
- ]
1765
- }).catch((_) => setAvailableEntities(null)).then(
1766
- (data) => data ? setAvailableEntities(data.items) : setAvailableEntities(null)
1767
- );
1768
- }
1769
- }, [catalogApi, entityRef, configApi, entityKinds]);
1770
- if (!availableEntities || availableEntities.length === 0) {
1771
- return null;
1772
- }
1773
- return /* @__PURE__ */ React.createElement(
1774
- Controller,
1775
- {
1776
- control,
1777
- render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
1778
- Autocomplete,
1779
- {
1780
- multiple: true,
1781
- className: "qetaAskFormEntities",
1782
- value,
1783
- groupBy: entityKinds.length > 1 ? (option) => option.kind : void 0,
1784
- id: "entities-select",
1785
- options: availableEntities,
1786
- getOptionLabel: getEntityTitle,
1787
- getOptionSelected: (o, v) => stringifyEntityRef(o) === stringifyEntityRef(v),
1788
- onChange: (_e, newValue) => {
1789
- if (!value || value.length < max) {
1790
- onChange(newValue);
1791
- }
1792
- },
1793
- renderInput: (params) => /* @__PURE__ */ React.createElement(
1794
- TextField,
1795
- {
1796
- ...params,
1797
- variant: "outlined",
1798
- margin: "normal",
1799
- label: "Entities",
1800
- placeholder: "Type or select entities",
1801
- helperText: `Add up to ${max} entities this question relates to`
1802
- }
1803
- )
1804
- }
1805
- ),
1806
- name: "entities"
1807
- }
1808
- );
1809
- };
1810
-
1811
- const AskAnonymouslyCheckbox = (props) => {
1812
- const { control, label } = props;
1813
- return /* @__PURE__ */ React.createElement(Box, { style: { marginLeft: "0.2rem" } }, /* @__PURE__ */ React.createElement(
1814
- Controller,
1815
- {
1816
- control,
1817
- render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
1818
- FormControlLabel,
1819
- {
1820
- control: /* @__PURE__ */ React.createElement(Tooltip, { title: "By enabling this, other users won't be able to see you as an author" }, /* @__PURE__ */ React.createElement(
1821
- Checkbox,
1822
- {
1823
- onChange,
1824
- value,
1825
- size: "small",
1826
- name: "anonymous"
1827
- }
1828
- )),
1829
- label
1830
- }
1831
- ),
1832
- name: "anonymous"
1833
- }
1834
- ));
1835
- };
1836
-
1837
- const formToRequest = (form, images) => {
1838
- var _a;
1839
- return {
1840
- ...form,
1841
- entities: (_a = form.entities) == null ? void 0 : _a.map(stringifyEntityRef),
1842
- images
1843
- };
1844
- };
1845
- const getDefaultValues = (props) => {
1846
- var _a;
1847
- return {
1848
- title: "",
1849
- content: "",
1850
- tags: (_a = props.tags) != null ? _a : [],
1851
- entities: []
1852
- };
1853
- };
1854
- const getValues = async (api, catalogApi, id) => {
1855
- var _a;
1856
- if (!id) {
1857
- return getDefaultValues({});
1858
- }
1859
- const question = await api.getQuestion(id);
1860
- const entities = question.entities && question.entities.length > 0 ? await catalogApi.getEntitiesByRefs({
1861
- entityRefs: question.entities,
1862
- fields: [
1863
- "kind",
1864
- "metadata.name",
1865
- "metadata.namespace",
1866
- "metadata.title"
1867
- ]
1868
- }) : [];
1869
- return {
1870
- title: question.title,
1871
- content: question.content,
1872
- tags: (_a = question.tags) != null ? _a : [],
1873
- entities: "items" in entities ? compact(entities.items) : []
1874
- };
1875
- };
1876
- const AskForm = (props) => {
1877
- const { id, entity, onPost, entityPage } = props;
1878
- const questionRoute = useRouteRef(questionRouteRef);
1879
- const navigate = useNavigate();
1880
- const analytics = useAnalytics();
1881
- const [entityRef, setEntityRef] = React.useState(entity);
1882
- const [posting, setPosting] = React.useState(false);
1883
- const [values, setValues] = React.useState(getDefaultValues(props));
1884
- const [error, setError] = React.useState(false);
1885
- const [images, setImages] = React.useState([]);
1886
- const [searchParams, _setSearchParams] = useSearchParams();
1887
- const qetaApi = useApi(qetaApiRef);
1888
- const catalogApi = useApi(catalogApiRef);
1889
- const configApi = useApi(configApiRef);
1890
- const allowAnonymouns = configApi.getOptionalBoolean("qeta.allowAnonymous");
1891
- const styles = useStyles$1();
1892
- const {
1893
- register,
1894
- handleSubmit,
1895
- control,
1896
- reset,
1897
- formState: { errors }
1898
- } = useForm({
1899
- values,
1900
- defaultValues: getDefaultValues(props)
1901
- });
1902
- const postQuestion = (data) => {
1903
- setPosting(true);
1904
- const queryParams = new URLSearchParams();
1905
- if (entity) {
1906
- queryParams.set("entity", entity);
1907
- }
1908
- if (entityPage) {
1909
- queryParams.set("entityPage", "true");
1910
- }
1911
- if (id) {
1912
- qetaApi.updateQuestion(id, formToRequest(data, images)).then((q) => {
1913
- if (!q || !q.id) {
1914
- setError(true);
1915
- return;
1916
- }
1917
- reset();
1918
- analytics.captureEvent("edit", "question");
1919
- if (onPost) {
1920
- onPost(q);
1921
- } else if (entity) {
1922
- navigate(
1923
- `${questionRoute({
1924
- id: q.id.toString(10)
1925
- })}?${queryParams.toString()}`
1926
- );
1927
- } else {
1928
- navigate(questionRoute({ id: q.id.toString(10) }));
1929
- }
1930
- }).catch((_e) => {
1931
- setError(true);
1932
- setPosting(false);
1933
- });
1934
- return;
1935
- }
1936
- qetaApi.postQuestion(formToRequest(data, images)).then((q) => {
1937
- if (!q || !q.id) {
1938
- setError(true);
1939
- return;
1940
- }
1941
- analytics.captureEvent("post", "question");
1942
- reset();
1943
- if (entity) {
1944
- navigate(
1945
- `${questionRoute({
1946
- id: q.id.toString(10)
1947
- })}?${queryParams.toString()}`
1948
- );
1949
- } else {
1950
- navigate(questionRoute({ id: q.id.toString(10) }));
1951
- }
1952
- }).catch((_e) => {
1953
- setError(true);
1954
- setPosting(false);
1955
- });
1956
- };
1957
- useEffect(() => {
1958
- if (!entityRef) {
1959
- const e = searchParams.get("entity");
1960
- if (e) {
1961
- setEntityRef(e);
1962
- }
1963
- }
1964
- }, [entityRef, searchParams]);
1965
- useEffect(() => {
1966
- if (id) {
1967
- getValues(qetaApi, catalogApi, id).then((data) => {
1968
- setValues(data);
1969
- });
1970
- }
1971
- }, [qetaApi, catalogApi, id]);
1972
- useEffect(() => {
1973
- if (entityRef) {
1974
- catalogApi.getEntityByRef(entityRef).then((data) => {
1975
- if (data) {
1976
- setValues((v) => {
1977
- return { ...v, entities: [data] };
1978
- });
1979
- }
1980
- });
1981
- }
1982
- }, [catalogApi, entityRef]);
1983
- useEffect(() => {
1984
- reset(values);
1985
- }, [values, reset]);
1986
- return /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit(postQuestion), className: "qetaAskForm" }, error && /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, "Could not post question"), /* @__PURE__ */ React.createElement(
1987
- TextField,
1988
- {
1989
- label: "Title",
1990
- className: "qetaAskFormTitle",
1991
- required: true,
1992
- fullWidth: true,
1993
- error: "title" in errors,
1994
- margin: "normal",
1995
- variant: "outlined",
1996
- helperText: "Write good title for your question that people can understand",
1997
- // @ts-ignore
1998
- ...register("title", { required: true, maxLength: 255 })
1999
- }
2000
- ), /* @__PURE__ */ React.createElement(
2001
- Controller,
2002
- {
2003
- control,
2004
- rules: {
2005
- required: true
2006
- },
2007
- render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
2008
- MarkdownEditor,
2009
- {
2010
- value,
2011
- onChange,
2012
- height: 400,
2013
- error: "content" in errors,
2014
- placeholder: "Your question",
2015
- config: configApi,
2016
- onImageUpload: (imageId) => {
2017
- setImages((prevImages) => [...prevImages, imageId]);
2018
- }
2019
- }
2020
- ),
2021
- name: "content"
2022
- }
2023
- ), /* @__PURE__ */ React.createElement(TagInput, { control }), /* @__PURE__ */ React.createElement(EntitiesInput, { control, entityRef }), allowAnonymouns && !id && /* @__PURE__ */ React.createElement(AskAnonymouslyCheckbox, { control, label: "Ask anonymously" }), /* @__PURE__ */ React.createElement(
2024
- Button,
2025
- {
2026
- color: "primary",
2027
- type: "submit",
2028
- variant: "contained",
2029
- disabled: posting,
2030
- className: `qetaAskFormSubmitBtn ${styles.postButton}`
2031
- },
2032
- id ? "Save" : "Post"
2033
- ));
2034
- };
2035
-
2036
- const UserLink = (props) => {
2037
- const { entityRef, linkProps } = props;
2038
- const userRoute = useRouteRef(userRouteRef);
2039
- const { primaryTitle: userName } = useEntityPresentation(
2040
- entityRef.startsWith("user:") ? entityRef : `user:${entityRef}`
2041
- );
2042
- if (entityRef === "anonymous") {
2043
- return /* @__PURE__ */ React.createElement(React.Fragment, null, "Anonymous");
2044
- }
2045
- return /* @__PURE__ */ React.createElement(Link, { to: `${userRoute()}/${entityRef}`, ...linkProps }, userName);
2046
- };
2047
- const AuthorLink = (props) => {
2048
- const { entity, linkProps } = props;
2049
- return /* @__PURE__ */ React.createElement(UserLink, { entityRef: entity.author, linkProps });
2050
- };
2051
- const UpdatedByLink = (props) => {
2052
- const { entity, linkProps } = props;
2053
- if (!entity.updatedBy) {
2054
- return null;
2055
- }
2056
- return /* @__PURE__ */ React.createElement(UserLink, { entityRef: entity.updatedBy, linkProps });
2057
- };
2058
-
2059
- const QuestionTableRow = (props) => {
2060
- const { question } = props;
2061
- const questionRoute = useRouteRef(questionRouteRef);
2062
- return /* @__PURE__ */ React.createElement(TableRow, { key: question.id }, /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(Link, { to: questionRoute({ id: question.id.toString(10) }) }, question.title)), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(AuthorLink, { entity: question })), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(RelativeTimeWithTooltip, { value: question.created })), /* @__PURE__ */ React.createElement(TableCell, null, /* @__PURE__ */ React.createElement(
2063
- RelativeTimeWithTooltip,
2064
- {
2065
- value: question.updated ? question.updated : question.created
2066
- }
2067
- )));
2068
- };
2069
-
2070
- const QuestionsTable = (props) => {
2071
- var _a, _b;
2072
- const [page, setPage] = React.useState(1);
2073
- const [questionsPerPage, setQuestionsPerPage] = React.useState(
2074
- (_a = props.rowsPerPage) != null ? _a : 10
2075
- );
2076
- const [quickFilter, setQuickFilter] = React.useState(
2077
- (_b = props.quickFilter) != null ? _b : "latest"
2078
- );
2079
- const [refresh, setRefresh] = React.useState(0);
2080
- const [filters, setFilters] = React.useState({
2081
- order: "desc",
2082
- orderBy: "created",
2083
- noAnswers: "false",
2084
- noCorrectAnswer: "false",
2085
- noVotes: "false",
2086
- searchQuery: "",
2087
- favorite: false
2088
- });
2089
- const {
2090
- value: response,
2091
- loading,
2092
- error
2093
- } = useQetaApi(
2094
- (api) => api.getQuestions({
2095
- limit: questionsPerPage,
2096
- offset: (page - 1) * questionsPerPage,
2097
- includeEntities: true,
2098
- ...filters
2099
- }),
2100
- [page, filters, questionsPerPage, refresh]
2101
- );
2102
- const handleQuickFilterChange = (filter) => {
2103
- setQuickFilter(filter);
2104
- if (filter === "latest") {
2105
- setFilters({
2106
- ...filters,
2107
- order: "desc",
2108
- orderBy: "created",
2109
- favorite: false
2110
- });
2111
- } else if (filter === "favorites") {
2112
- setFilters({
2113
- ...filters,
2114
- order: "desc",
2115
- orderBy: "created",
2116
- favorite: true
2117
- });
2118
- } else if (filter === "most_viewed") {
2119
- setFilters({
2120
- ...filters,
2121
- order: "desc",
2122
- orderBy: "views",
2123
- favorite: false
2124
- });
2125
- }
2126
- };
2127
- const handleChangePage = (_, newPage) => {
2128
- setPage(newPage + 1);
2129
- };
2130
- const handleChangeRowsPerPage = (event) => {
2131
- setQuestionsPerPage(parseInt(event.target.value, 10));
2132
- setPage(1);
2133
- };
2134
- if (error || response === void 0) {
2135
- return /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not load questions." }, error == null ? void 0 : error.message);
2136
- }
2137
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
2138
- Grid,
2139
- {
2140
- container: true,
2141
- justifyContent: "space-between",
2142
- alignItems: "center",
2143
- style: { marginBottom: "1em" },
2144
- className: "qetaQuestionsTableGrid"
2145
- },
2146
- /* @__PURE__ */ React.createElement(Grid, { item: true }, props.hideTitle === true ? null : /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Q&A")),
2147
- /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(ButtonGroup, null, /* @__PURE__ */ React.createElement(
2148
- Button,
2149
- {
2150
- color: quickFilter === "latest" ? "primary" : void 0,
2151
- onClick: () => handleQuickFilterChange("latest")
2152
- },
2153
- "Latest"
2154
- ), /* @__PURE__ */ React.createElement(
2155
- Button,
2156
- {
2157
- color: quickFilter === "favorites" ? "primary" : void 0,
2158
- onClick: () => handleQuickFilterChange("favorites")
2159
- },
2160
- "Favorites"
2161
- ), /* @__PURE__ */ React.createElement(
2162
- Button,
2163
- {
2164
- color: quickFilter === "most_viewed" ? "primary" : void 0,
2165
- onClick: () => handleQuickFilterChange("most_viewed")
2166
- },
2167
- "Most viewed"
2168
- )), /* @__PURE__ */ React.createElement(
2169
- LinkButton,
2170
- {
2171
- to: "#",
2172
- variant: "text",
2173
- onClick: () => setRefresh(refresh + 1)
2174
- },
2175
- /* @__PURE__ */ React.createElement(RefreshIcon, null)
2176
- ))
2177
- ), /* @__PURE__ */ React.createElement(TableContainer, null, /* @__PURE__ */ React.createElement(Table, { className: "qetaQuestionsTable" }, /* @__PURE__ */ React.createElement(TableHead, null, /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(TableCell, null, "Title"), /* @__PURE__ */ React.createElement(TableCell, null, "Author"), /* @__PURE__ */ React.createElement(TableCell, null, "Asked"), /* @__PURE__ */ React.createElement(TableCell, null, "Last updated"))), /* @__PURE__ */ React.createElement(TableBody, null, loading ? /* @__PURE__ */ React.createElement(Progress, null) : null, response.questions.map((q) => /* @__PURE__ */ React.createElement(QuestionTableRow, { question: q })))), /* @__PURE__ */ React.createElement(
2178
- TablePagination,
2179
- {
2180
- rowsPerPageOptions: [5, 10, 20, 30, 40, 50],
2181
- component: "div",
2182
- count: response.total,
2183
- rowsPerPage: questionsPerPage,
2184
- page: page - 1,
2185
- onPageChange: handleChangePage,
2186
- onRowsPerPageChange: handleChangeRowsPerPage
2187
- }
2188
- )));
2189
- };
2190
-
2191
- const Content = (props) => {
2192
- return /* @__PURE__ */ React.createElement(QuestionsTable, { hideTitle: true, ...props });
2193
- };
2194
-
2195
- const TrophyIcon = (props) => /* @__PURE__ */ React.createElement(SvgIcon, { ...props, viewBox: "0 0 24 24" }, /* @__PURE__ */ React.createElement(
2196
- "path",
2197
- {
2198
- id: "secondary",
2199
- d: "M17,12a1,1,0,0,1,0-2,3,3,0,0,0,3-3V6H17.17a1,1,0,0,1,0-2H20a2,2,0,0,1,2,2V7A5,5,0,0,1,17,12ZM8,11a1,1,0,0,0-1-1A3,3,0,0,1,4,7V6H6.74a1,1,0,0,0,0-2H4A2,2,0,0,0,2,6V7a5,5,0,0,0,5,5A1,1,0,0,0,8,11Zm5,10V16.18a1,1,0,0,0-2,0V21a1,1,0,0,0,2,0Z"
2200
- }
2201
- ), /* @__PURE__ */ React.createElement(
2202
- "path",
2203
- {
2204
- id: "primary",
2205
- d: "M16,22H8a1,1,0,0,1,0-2h8a1,1,0,0,1,0,2ZM17,2H7A1,1,0,0,0,6,3V9.57a7.75,7.75,0,0,0,4.89,7.22A3,3,0,0,0,12,17a3.13,3.13,0,0,0,1.12-.21A7.76,7.76,0,0,0,18,9.57V3A1,1,0,0,0,17,2Z"
2206
- }
2207
- ));
2208
-
2209
- const useStyles = makeStyles((theme) => {
2210
- return {
2211
- trophyIcon: {
2212
- backgroundColor: "initial",
2213
- color: theme.palette.text.primary,
2214
- borderRadius: "50%",
2215
- boxSizing: "border-box",
2216
- padding: "1rem",
2217
- height: 100,
2218
- width: 100
2219
- },
2220
- votesText: {
2221
- display: "grid",
2222
- placeItems: "center",
2223
- marginLeft: "16px"
2224
- }
2225
- };
2226
- });
2227
-
2228
- const DefaultRankingIcons = /* @__PURE__ */ new Map([
2229
- [
2230
- 1,
2231
- /* @__PURE__ */ React.createElement(
2232
- TrophyIcon,
2233
- {
2234
- style: { color: "#DAA520", height: "2.2rem", width: "2.2rem" }
2235
- }
2236
- )
2237
- ],
2238
- [
2239
- 2,
2240
- /* @__PURE__ */ React.createElement(
2241
- TrophyIcon,
2242
- {
2243
- style: { color: "#C0C0C0", height: "2.1rem", width: "2.1rem" }
2244
- }
2245
- )
2246
- ],
2247
- [
2248
- 3,
2249
- /* @__PURE__ */ React.createElement(TrophyIcon, { style: { color: "#B87333", height: "2rem", width: "2rem" } })
2250
- ]
2251
- ]);
2252
- const DefaultUserIcon = /* @__PURE__ */ React.createElement(TrophyIcon, { style: { height: "2rem", width: "2rem" } });
2253
- const getOrdinal = (n) => {
2254
- if (n % 10 === 1 && n % 100 !== 11) {
2255
- return `${n}st`;
2256
- } else if (n % 10 === 2 && n % 100 !== 12) {
2257
- return `${n}nd`;
2258
- } else if (n % 10 === 3 && n % 100 !== 13) {
2259
- return `${n}rd`;
2260
- }
2261
- return `${n}th`;
2262
- };
2263
- const RankingRow = (props) => {
2264
- var _a, _b;
2265
- const classes = useStyles();
2266
- const userRef = props.userRef;
2267
- const ordinalPosition = (props == null ? void 0 : props.position) ? getOrdinal(props == null ? void 0 : props.position) : "";
2268
- const userIcon = ((_a = props.rankingIcon) == null ? void 0 : _a.userRankingIcon) ? (_b = props.rankingIcon) == null ? void 0 : _b.userRankingIcon : DefaultUserIcon;
2269
- const topRankingIcon = props.rankingIcon ? props.rankingIcon.iconsByRanking.get(Number(props == null ? void 0 : props.position)) : DefaultRankingIcons.get(Number(props == null ? void 0 : props.position)) || DefaultUserIcon;
2270
- const rankingIcon = (props == null ? void 0 : props.position) > 3 ? userIcon : topRankingIcon;
2271
- return /* @__PURE__ */ React.createElement(ListItem, { className: "qetaRankingCardRow" }, /* @__PURE__ */ React.createElement(ListItemAvatar, null, /* @__PURE__ */ React.createElement(Avatar, { className: classes.trophyIcon }, rankingIcon)), /* @__PURE__ */ React.createElement(
2272
- ListItemText,
2273
- {
2274
- disableTypography: true,
2275
- style: {
2276
- display: "flex",
2277
- justifyContent: "center"
2278
- },
2279
- primary: /* @__PURE__ */ React.createElement("div", { style: { display: "flex" } }, /* @__PURE__ */ React.createElement(
2280
- Typography,
2281
- {
2282
- style: { marginRight: "10px", fontWeight: 400 },
2283
- variant: "subtitle1"
2284
- },
2285
- `${ordinalPosition}`
2286
- ), /* @__PURE__ */ React.createElement(UserLink, { entityRef: userRef != null ? userRef : "" }))
2287
- }
2288
- ), /* @__PURE__ */ React.createElement("div", { className: classes.votesText }, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, props == null ? void 0 : props.total, " ", props.unit)));
2289
- };
2290
- const RankingCard = (props) => {
2291
- var _a, _b, _c, _d, _e, _f, _g, _h;
2292
- const rankingStats = props.limit ? (_a = props.statistic) == null ? void 0 : _a.ranking.slice(0, props.limit) : (_b = props.statistic) == null ? void 0 : _b.ranking;
2293
- return /* @__PURE__ */ React.createElement("div", { style: { display: "block" }, className: "qetaRankingCard" }, /* @__PURE__ */ React.createElement("span", { className: "qetaRankingCardDescription" }, props.description), /* @__PURE__ */ React.createElement(List, { className: "qetaRankingCardList" }, rankingStats == null ? void 0 : rankingStats.map((authorStats) => {
2294
- return /* @__PURE__ */ React.createElement(
2295
- RankingRow,
2296
- {
2297
- total: authorStats.total || 0,
2298
- position: authorStats.position || 0,
2299
- userRef: authorStats.author,
2300
- unit: props.unit
2301
- }
2302
- );
2303
- }), !(rankingStats == null ? void 0 : rankingStats.some(
2304
- (authorStats) => {
2305
- var _a2, _b2;
2306
- return authorStats.author === ((_b2 = (_a2 = props.statistic) == null ? void 0 : _a2.loggedUser) == null ? void 0 : _b2.author);
2307
- }
2308
- )) && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("hr", null), /* @__PURE__ */ React.createElement(
2309
- RankingRow,
2310
- {
2311
- total: ((_d = (_c = props.statistic) == null ? void 0 : _c.loggedUser) == null ? void 0 : _d.total) || 0,
2312
- position: ((_f = (_e = props.statistic) == null ? void 0 : _e.loggedUser) == null ? void 0 : _f.position) || 0,
2313
- userRef: (_h = (_g = props.statistic) == null ? void 0 : _g.loggedUser) == null ? void 0 : _h.author,
2314
- unit: props.unit
2315
- }
2316
- ))));
2317
- };
2318
- const TopRankingUsers = (props) => {
2319
- const {
2320
- value: topStatistics,
2321
- loading,
2322
- error
2323
- } = useQetaApi(
2324
- (api) => api.getTopStatisticsHomepage({
2325
- options: { limit: 50 }
2326
- })
2327
- );
2328
- const tabData = [
2329
- {
2330
- title: "Most questions",
2331
- description: "People who have posted most questions",
2332
- unit: "questions"
2333
- },
2334
- {
2335
- title: "Most answers",
2336
- description: "People who have answered most questions",
2337
- unit: "answers"
2338
- },
2339
- {
2340
- title: "Top Upvoted Questions",
2341
- description: "People who have the highest rated questions",
2342
- unit: "votes"
2343
- },
2344
- {
2345
- title: "Top Upvoted Answers",
2346
- description: "People who have the highest rated answers",
2347
- unit: "votes"
2348
- },
2349
- {
2350
- title: "Top Upvoted Correct Answers",
2351
- description: "People who have the highest rated correct answers",
2352
- unit: "votes"
2353
- }
2354
- ];
2355
- if ((error || topStatistics === void 0) && !loading) {
2356
- return /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not load statistics." }, error == null ? void 0 : error.message);
2357
- }
2358
- let content;
2359
- if (loading) {
2360
- content = [
2361
- /* @__PURE__ */ React.createElement(CardTab, null, /* @__PURE__ */ React.createElement(Progress, null))
2362
- ];
2363
- } else if (topStatistics && topStatistics.length > 0) {
2364
- content = topStatistics == null ? void 0 : topStatistics.map((stats, index) => {
2365
- return /* @__PURE__ */ React.createElement(CardTab, { label: tabData[index].title }, /* @__PURE__ */ React.createElement(
2366
- RankingCard,
2367
- {
2368
- description: tabData[index].description,
2369
- limit: props.limit,
2370
- statistic: stats,
2371
- unit: tabData[index].unit
2372
- }
2373
- ));
2374
- });
2375
- } else {
2376
- content = [/* @__PURE__ */ React.createElement(CardTab, null, "No statistics available")];
2377
- }
2378
- return /* @__PURE__ */ React.createElement(TabbedCard, { title: props.title || "Ranking Q&A \u{1F3C6}" }, content);
2379
- };
2380
-
2381
- const BackToQuestionsButton = (props) => {
2382
- const styles = useStyles$1();
2383
- const entityRoute = useRouteRef(entityRouteRef);
2384
- const rootRoute = useRouteRef(qetaRouteRef);
2385
- let to = rootRoute();
2386
- const [searchParams] = useSearchParams();
2387
- const entity = searchParams.get("entity");
2388
- if (entity) {
2389
- const entityRef = parseEntityRef(entity);
2390
- if (props.entityPage) {
2391
- to = `${entityRoute(entityRef)}/qeta`;
2392
- } else {
2393
- to = `${rootRoute()}?entity=${entity}`;
2394
- }
2395
- }
2396
- return /* @__PURE__ */ React.createElement(
2397
- LinkButton,
2398
- {
2399
- className: `${styles.marginRight} qetaBackToQuestionsBtn`,
2400
- to,
2401
- startIcon: /* @__PURE__ */ React.createElement(HomeOutlined, null)
2402
- },
2403
- "Back to questions"
2404
- );
2405
- };
2406
-
2407
- const StatisticsPage = () => {
2408
- return /* @__PURE__ */ React.createElement(Content$1, { className: "qetaStatisticsPage" }, /* @__PURE__ */ React.createElement(Container, { maxWidth: "lg" }, /* @__PURE__ */ React.createElement(ContentHeader, { title: "Statistics" }, /* @__PURE__ */ React.createElement(BackToQuestionsButton, null), /* @__PURE__ */ React.createElement(AskQuestionButton, null)), /* @__PURE__ */ React.createElement(TopRankingUsers, { limit: 10 })));
2409
- };
2410
-
2411
- export { AskForm as A, BackToQuestionsButton as B, Content as C, MarkdownEditor as M, QuestionsTable as Q, RelativeTimeWithTooltip as R, StatisticsPage as S, TagsAndEntities as T, UpdatedByLink as U, useStyles$1 as a, useEntityAuthor as b, AuthorLink as c, AskAnonymouslyCheckbox as d, useQetaApi as e, AskQuestionButton as f, QuestionsContainer as g, useIdentityApi as h, TrophyIcon as i, qetaPlugin as j, QetaPage as k, QuestionTableCard as l, RankingRow as m, RankingCard as n, TopRankingUsers as o, QetaClient as p, qetaApiRef as q, useBasePath as u };
2412
- //# sourceMappingURL=index-CFNWBJNR.esm.js.map