@drodil/backstage-plugin-qeta 2.2.1 → 2.3.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.
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-CuD-iIx9.esm.js +0 -2408
  99. package/dist/esm/index-CuD-iIx9.esm.js.map +0 -1
  100. package/dist/esm/index-Djg6aEGn.esm.js +0 -33
  101. package/dist/esm/index-Djg6aEGn.esm.js.map +0 -1
  102. package/dist/esm/index-Z9_0Lp_9.esm.js +0 -1120
  103. package/dist/esm/index-Z9_0Lp_9.esm.js.map +0 -1
@@ -1,2408 +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-Z9_0Lp_9.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-Djg6aEGn.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
- if ((filters.entity || refs && (refs == null ? void 0 : refs.length) > 0) && !Array.isArray(filters.entity)) {
858
- catalogApi.getEntitiesByRefs({
859
- entityRefs: [...(refs != null ? refs : []).map((e) => e.entityRef), filters.entity],
860
- fields: [
861
- "kind",
862
- "metadata.name",
863
- "metadata.namespace",
864
- "metadata.title"
865
- ]
866
- }).then((resp) => {
867
- const filtered = resp.items.filter((i) => i !== void 0);
868
- setAvailableEntities(filtered);
869
- });
870
- }
871
- }, [filters.entity, catalogApi, refs]);
872
- useEffect(() => {
873
- if (filters.entity && availableEntities) {
874
- const value = availableEntities.find(
875
- (e) => stringifyEntityRef(e) === filters.entity
876
- );
877
- setSelectedEntity(value);
878
- if (!value) {
879
- onChange("entity", "");
880
- }
881
- } else {
882
- setSelectedEntity(void 0);
883
- }
884
- }, [availableEntities, filters.entity, onChange]);
885
- const handleChange = (event) => {
886
- let value = event.target.value;
887
- if (event.target.type === "checkbox") {
888
- value = event.target.checked ? "true" : "false";
889
- }
890
- onChange(event.target.name, value);
891
- };
892
- 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(
893
- FormControlLabel,
894
- {
895
- control: /* @__PURE__ */ React.createElement(
896
- Checkbox,
897
- {
898
- size: "small",
899
- name: "noAnswers",
900
- onChange: handleChange,
901
- checked: filters.noAnswers === "true"
902
- }
903
- ),
904
- label: "No answers"
905
- }
906
- ), /* @__PURE__ */ React.createElement(
907
- FormControlLabel,
908
- {
909
- control: /* @__PURE__ */ React.createElement(
910
- Checkbox,
911
- {
912
- size: "small",
913
- name: "noCorrectAnswer",
914
- checked: filters.noCorrectAnswer === "true",
915
- onChange: handleChange
916
- }
917
- ),
918
- label: "No correct answers"
919
- }
920
- ), /* @__PURE__ */ React.createElement(
921
- FormControlLabel,
922
- {
923
- control: /* @__PURE__ */ React.createElement(
924
- Checkbox,
925
- {
926
- size: "small",
927
- name: "noVotes",
928
- checked: filters.noVotes === "true",
929
- onChange: handleChange
930
- }
931
- ),
932
- label: "No votes"
933
- }
934
- ))), /* @__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(
935
- RadioGroup,
936
- {
937
- "aria-labelledby": "qeta-filter-order-by",
938
- name: "orderBy",
939
- value: filters.orderBy,
940
- onChange: handleChange
941
- },
942
- radioSelect("created", "Created"),
943
- radioSelect("views", "Views"),
944
- radioSelect("score", "Score"),
945
- radioSelect("answersCount", "Answers")
946
- ))), /* @__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(
947
- RadioGroup,
948
- {
949
- "aria-labelledby": "qeta-filter-order",
950
- name: "order",
951
- value: filters.order,
952
- onChange: handleChange
953
- },
954
- radioSelect("desc", "Descending"),
955
- radioSelect("asc", "Ascending")
956
- ))), (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 && (!filters.entity || selectedEntity) && /* @__PURE__ */ React.createElement(
957
- Autocomplete,
958
- {
959
- multiple: false,
960
- className: "qetaEntityFilter",
961
- value: selectedEntity != null ? selectedEntity : null,
962
- id: "entities-select",
963
- options: availableEntities,
964
- getOptionLabel: getEntityTitle,
965
- getOptionSelected: (o, v) => {
966
- if (!o || !v) {
967
- return false;
968
- }
969
- return stringifyEntityRef(o) === stringifyEntityRef(v);
970
- },
971
- onChange: (_e, newValue) => {
972
- handleChange({
973
- target: {
974
- name: "entity",
975
- value: newValue ? stringifyEntityRef(newValue) : ""
976
- }
977
- });
978
- },
979
- renderInput: (params) => /* @__PURE__ */ React.createElement(
980
- TextField,
981
- {
982
- ...params,
983
- variant: "outlined",
984
- margin: "normal",
985
- label: "Entity",
986
- placeholder: "Type or select entity"
987
- }
988
- )
989
- }
990
- ), showTagFilter && availableTags && availableTags.length > 0 && /* @__PURE__ */ React.createElement(
991
- Autocomplete,
992
- {
993
- multiple: true,
994
- className: "qetaTagFilter",
995
- value: filters.tags,
996
- id: "tags-select",
997
- options: availableTags,
998
- onChange: (_e, newValue) => {
999
- handleChange({
1000
- target: {
1001
- name: "tags",
1002
- value: newValue
1003
- }
1004
- });
1005
- },
1006
- renderInput: (params) => /* @__PURE__ */ React.createElement(
1007
- TextField,
1008
- {
1009
- ...params,
1010
- variant: "outlined",
1011
- margin: "normal",
1012
- label: "Tag",
1013
- placeholder: "Type or select tag"
1014
- }
1015
- )
1016
- }
1017
- ))));
1018
- };
1019
-
1020
- const EntityChip = (props) => {
1021
- const { entity } = props;
1022
- const entityRoute = useRouteRef(entityRouteRef);
1023
- const { primaryTitle, secondaryTitle } = useEntityPresentation(entity);
1024
- return /* @__PURE__ */ React.createElement(Tooltip, { title: secondaryTitle != null ? secondaryTitle : primaryTitle, arrow: true }, /* @__PURE__ */ React.createElement(
1025
- Chip,
1026
- {
1027
- label: primaryTitle,
1028
- size: "small",
1029
- variant: "outlined",
1030
- className: "qetaEntityChip",
1031
- component: "a",
1032
- href: entityRoute(getCompoundEntityRef(entity)),
1033
- clickable: true
1034
- }
1035
- ));
1036
- };
1037
-
1038
- const TagsAndEntities = (props) => {
1039
- const { question } = props;
1040
- const catalogApi = useApi(catalogApiRef);
1041
- const tagRoute = useRouteRef(tagRouteRef);
1042
- const [entities, setEntities] = React.useState([]);
1043
- useEffect(() => {
1044
- if (question.entities && question.entities.length > 0) {
1045
- catalogApi.getEntitiesByRefs({
1046
- entityRefs: question.entities,
1047
- fields: [
1048
- "kind",
1049
- "metadata.name",
1050
- "metadata.namespace",
1051
- "metadata.title"
1052
- ]
1053
- }).catch((_) => setEntities([])).then(
1054
- (data) => data ? setEntities(compact(data.items)) : setEntities([])
1055
- );
1056
- }
1057
- }, [catalogApi, question]);
1058
- return /* @__PURE__ */ React.createElement(React.Fragment, null, question.tags && question.tags.map((tag) => /* @__PURE__ */ React.createElement(
1059
- Chip,
1060
- {
1061
- key: tag,
1062
- label: tag,
1063
- size: "small",
1064
- className: "qetaTagChip",
1065
- component: "a",
1066
- href: tagRoute({ tag }),
1067
- clickable: true
1068
- }
1069
- )), entities && entities.map((component, i) => /* @__PURE__ */ React.createElement(EntityChip, { entity: component, key: i })));
1070
- };
1071
-
1072
- const RelativeTimeWithTooltip = (props) => {
1073
- const { value } = props;
1074
- let date = value;
1075
- const [updates, setUpdates] = React.useState(1);
1076
- useEffect(() => {
1077
- const interval = setInterval(() => {
1078
- setUpdates(updates === 1 ? 0 : 1);
1079
- }, 3e4);
1080
- return () => clearInterval(interval);
1081
- }, [updates, setUpdates]);
1082
- if (typeof date === "string" || date instanceof String) {
1083
- date = new Date(date);
1084
- }
1085
- return /* @__PURE__ */ React.createElement(Tooltip, { title: date.toLocaleString(navigator.languages) }, /* @__PURE__ */ React.createElement(RelativeTime, { value: date }));
1086
- };
1087
-
1088
- const QuestionListItem = (props) => {
1089
- var _a, _b;
1090
- const { question, entity } = props;
1091
- const [correctAnswer, setCorrectAnswer] = useState(question.correctAnswer);
1092
- const [answersCount, setAnswersCount] = useState(question.answersCount);
1093
- const [score, setScore] = useState(question.score);
1094
- const [views, setViews] = useState(question.views);
1095
- const { lastSignal } = useSignal(`qeta:question_${question.id}`);
1096
- useEffect(() => {
1097
- if ((lastSignal == null ? void 0 : lastSignal.type) === "question_stats") {
1098
- setCorrectAnswer(lastSignal.correctAnswer);
1099
- setAnswersCount(lastSignal.answersCount);
1100
- setScore(lastSignal.score);
1101
- setViews(lastSignal.views);
1102
- }
1103
- }, [lastSignal]);
1104
- const questionRoute = useRouteRef(questionRouteRef);
1105
- const userRoute = useRouteRef(userRouteRef);
1106
- const theme = useTheme();
1107
- const styles = useStyles$1();
1108
- const { name, initials, user } = useEntityAuthor(question);
1109
- return /* @__PURE__ */ React.createElement(Card, { className: "qetaQuestionListItem" }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Box, { className: styles.questionListItemStats }, /* @__PURE__ */ React.createElement(
1110
- Typography,
1111
- {
1112
- display: "block",
1113
- variant: "caption",
1114
- className: "qetaQuestionListItemScore"
1115
- },
1116
- score,
1117
- " score"
1118
- ), /* @__PURE__ */ React.createElement(
1119
- Typography,
1120
- {
1121
- variant: "caption",
1122
- display: "block",
1123
- className: `qetaQuestionListItemAnswers ${correctAnswer ? "qetaQuestionListItemCorrectAnswer" : "quetaQuestionListItemNoCorrectAnswer"}`,
1124
- style: {
1125
- color: correctAnswer ? theme.palette.success.main : void 0
1126
- }
1127
- },
1128
- answersCount,
1129
- " answers"
1130
- ), /* @__PURE__ */ React.createElement(
1131
- Typography,
1132
- {
1133
- display: "block",
1134
- variant: "caption",
1135
- className: "qetaQuestionListItemViews"
1136
- },
1137
- views,
1138
- " views"
1139
- )), /* @__PURE__ */ React.createElement(Box, { className: styles.questionListItemContent }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5", component: "div" }, /* @__PURE__ */ React.createElement(
1140
- Link,
1141
- {
1142
- to: entity ? `${questionRoute({
1143
- id: question.id.toString(10)
1144
- })}?entity=${entity}` : questionRoute({ id: question.id.toString(10) }),
1145
- className: "qetaQuestionListItemQuestionBtn"
1146
- },
1147
- question.title
1148
- )), /* @__PURE__ */ React.createElement(
1149
- Typography,
1150
- {
1151
- variant: "caption",
1152
- noWrap: true,
1153
- component: "div",
1154
- className: "qetaQuestionListItemContent",
1155
- style: { marginBottom: "5px" }
1156
- },
1157
- DOMPurify.sanitize(
1158
- truncate(removeMarkdownFormatting(question.content), 150)
1159
- )
1160
- ), /* @__PURE__ */ React.createElement(TagsAndEntities, { question }), /* @__PURE__ */ React.createElement(
1161
- Typography,
1162
- {
1163
- variant: "caption",
1164
- display: "inline",
1165
- className: `${styles.questionListItemAuthor} qetaQuestionListItemAuthor`
1166
- },
1167
- /* @__PURE__ */ React.createElement(
1168
- Avatar,
1169
- {
1170
- src: (_b = (_a = user == null ? void 0 : user.spec) == null ? void 0 : _a.profile) == null ? void 0 : _b.picture,
1171
- className: styles.questionListItemAvatar,
1172
- alt: name,
1173
- variant: "rounded"
1174
- },
1175
- initials
1176
- ),
1177
- question.author === "anonymous" ? "Anonymous" : /* @__PURE__ */ React.createElement(Link, { to: `${userRoute()}/${question.author}` }, name),
1178
- " ",
1179
- /* @__PURE__ */ React.createElement(
1180
- Link,
1181
- {
1182
- to: entity ? `${questionRoute({
1183
- id: question.id.toString(10)
1184
- })}?entity=${entity}` : questionRoute({ id: question.id.toString(10) }),
1185
- className: "qetaQuestionListItemQuestionBtn"
1186
- },
1187
- "asked ",
1188
- /* @__PURE__ */ React.createElement(RelativeTimeWithTooltip, { value: question.created })
1189
- )
1190
- ))));
1191
- };
1192
-
1193
- const NoQuestionsCard = (props) => {
1194
- const { showNoQuestionsBtn, entity, entityPage, tags } = props;
1195
- const askRoute = useRouteRef(askRouteRef);
1196
- const entityRef = useEntityQueryParameter(entity);
1197
- const queryParams = new URLSearchParams();
1198
- if (entityRef) {
1199
- queryParams.set("entity", entityRef);
1200
- }
1201
- if (entityPage) {
1202
- queryParams.set("entityPage", "true");
1203
- }
1204
- if (tags && tags.length > 0) {
1205
- queryParams.set("tags", tags.join(","));
1206
- }
1207
- return /* @__PURE__ */ React.createElement(Card, { style: { marginTop: "2rem" } }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1208
- Grid,
1209
- {
1210
- container: true,
1211
- justifyContent: "center",
1212
- alignItems: "center",
1213
- direction: "column"
1214
- },
1215
- /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "No questions found")),
1216
- showNoQuestionsBtn && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1217
- LinkButton,
1218
- {
1219
- to: entityRef || tags ? `${askRoute()}?${queryParams.toString()}` : `${askRoute()}`,
1220
- startIcon: /* @__PURE__ */ React.createElement(HelpOutline, null),
1221
- color: "primary",
1222
- variant: "outlined"
1223
- },
1224
- "Go ahead and ask one!"
1225
- ))
1226
- )));
1227
- };
1228
-
1229
- const QuestionList = (props) => {
1230
- const {
1231
- loading,
1232
- error,
1233
- response,
1234
- onPageChange,
1235
- entity,
1236
- page,
1237
- onPageSizeChange,
1238
- showNoQuestionsBtn = true,
1239
- entityPage,
1240
- tags
1241
- } = props;
1242
- const styles = useStyles$1();
1243
- const listRef = useRef(null);
1244
- const [initialLoad, setInitialLoad] = useState(true);
1245
- useEffect(() => {
1246
- if (!initialLoad) {
1247
- setInitialLoad(false);
1248
- }
1249
- }, [initialLoad, loading]);
1250
- const handlePageChange = (_event, value) => {
1251
- if (listRef.current) {
1252
- listRef.current.scrollIntoView();
1253
- }
1254
- onPageChange(value);
1255
- };
1256
- const handlePageSizeChange = (event) => {
1257
- if (listRef.current) {
1258
- listRef.current.scrollIntoView();
1259
- }
1260
- onPageSizeChange(Number.parseInt(event.target.value, 10));
1261
- };
1262
- if (loading && initialLoad) {
1263
- return /* @__PURE__ */ React.createElement(Progress, null);
1264
- }
1265
- if (error || response === void 0) {
1266
- return /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not load questions." }, error == null ? void 0 : error.message);
1267
- }
1268
- if (initialLoad && (!response.questions || response.questions.length === 0)) {
1269
- return /* @__PURE__ */ React.createElement(
1270
- NoQuestionsCard,
1271
- {
1272
- showNoQuestionsBtn,
1273
- entity,
1274
- entityPage,
1275
- tags
1276
- }
1277
- );
1278
- }
1279
- const pageCount = response.total < props.pageSize ? 1 : Math.ceil(response.total / props.pageSize);
1280
- 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) => {
1281
- return /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, key: question.id }, /* @__PURE__ */ React.createElement(QuestionListItem, { question, entity }), /* @__PURE__ */ React.createElement(Divider, null));
1282
- })), /* @__PURE__ */ React.createElement(
1283
- Grid,
1284
- {
1285
- container: true,
1286
- spacing: 0,
1287
- className: `qetaQuestionListPaginationGrid ${styles.questionListPagination}`,
1288
- alignItems: "center",
1289
- justifyContent: "space-between"
1290
- },
1291
- /* @__PURE__ */ React.createElement(Tooltip, { title: "Questions per page", arrow: true }, /* @__PURE__ */ React.createElement(FormControl, { variant: "filled" }, /* @__PURE__ */ React.createElement(
1292
- Select,
1293
- {
1294
- value: props.pageSize,
1295
- onChange: handlePageSizeChange,
1296
- className: `qetaQuestionListPaginationSizeSelect ${styles.questionsPerPage}`,
1297
- inputProps: { className: styles.questionsPerPageInput }
1298
- },
1299
- /* @__PURE__ */ React.createElement(MenuItem, { value: 5 }, "5"),
1300
- /* @__PURE__ */ React.createElement(MenuItem, { value: 10 }, "10"),
1301
- /* @__PURE__ */ React.createElement(MenuItem, { value: 25 }, "25"),
1302
- /* @__PURE__ */ React.createElement(MenuItem, { value: 50 }, "50"),
1303
- /* @__PURE__ */ React.createElement(MenuItem, { value: 100 }, "100")
1304
- ))),
1305
- /* @__PURE__ */ React.createElement(
1306
- Pagination,
1307
- {
1308
- page,
1309
- onChange: handlePageChange,
1310
- count: pageCount,
1311
- size: "large",
1312
- variant: "outlined",
1313
- className: "qetaQuestionListPagination",
1314
- showFirstButton: true,
1315
- showLastButton: true
1316
- }
1317
- )
1318
- )));
1319
- };
1320
-
1321
- const AskQuestionButton = (props) => {
1322
- const { entity, entityPage, tags } = props;
1323
- const askRoute = useRouteRef(askRouteRef);
1324
- const params = new URLSearchParams();
1325
- if (entity) {
1326
- params.set("entity", entity);
1327
- }
1328
- if (entityPage) {
1329
- params.set("entityPage", "true");
1330
- }
1331
- if (tags && tags.length > 0) {
1332
- params.set("tags", tags.join(","));
1333
- }
1334
- return /* @__PURE__ */ React.createElement(
1335
- RequirePermission,
1336
- {
1337
- permission: qetaCreateQuestionPermission,
1338
- errorPage: /* @__PURE__ */ React.createElement(React.Fragment, null)
1339
- },
1340
- /* @__PURE__ */ React.createElement(
1341
- LinkButton,
1342
- {
1343
- variant: "contained",
1344
- to: entity || tags ? `${askRoute()}?${params.toString()}` : askRoute(),
1345
- color: "primary",
1346
- className: "qetaAskQuestionBtn",
1347
- startIcon: /* @__PURE__ */ React.createElement(HelpOutline, null)
1348
- },
1349
- "Ask question"
1350
- )
1351
- );
1352
- };
1353
-
1354
- const QuestionsContainer = (props) => {
1355
- var _a;
1356
- const {
1357
- tags,
1358
- author,
1359
- entity,
1360
- showFilters,
1361
- showTitle,
1362
- title,
1363
- favorite,
1364
- showAskButton,
1365
- showNoQuestionsBtn
1366
- } = props;
1367
- const analytics = useAnalytics();
1368
- const [page, setPage] = React.useState(1);
1369
- const [questionsPerPage, setQuestionsPerPage] = React.useState(10);
1370
- const [showFilterPanel, setShowFilterPanel] = React.useState(false);
1371
- const [searchParams, setSearchParams] = useSearchParams();
1372
- const [searchQuery, setSearchQuery] = React.useState("");
1373
- const [filters, setFilters] = React.useState({
1374
- order: "desc",
1375
- orderBy: "created",
1376
- noAnswers: "false",
1377
- noCorrectAnswer: "false",
1378
- noVotes: "false",
1379
- searchQuery: "",
1380
- entity: entity != null ? entity : "",
1381
- tags: tags != null ? tags : []
1382
- });
1383
- const onPageChange = (value) => {
1384
- setPage(value);
1385
- setSearchParams((prev) => {
1386
- const newValue = prev;
1387
- newValue.set("page", String(value));
1388
- return newValue;
1389
- });
1390
- };
1391
- const onFilterChange = (key, value) => {
1392
- if (filters[key] === value) {
1393
- return;
1394
- }
1395
- setPage(1);
1396
- setFilters({ ...filters, ...{ [key]: value } });
1397
- setSearchParams((prev) => {
1398
- const newValue = prev;
1399
- if (!value || value === "false") {
1400
- newValue.delete(key);
1401
- } else if (Array.isArray(value)) {
1402
- if (value.length === 0) {
1403
- newValue.delete(key);
1404
- } else {
1405
- newValue.set(key, value.join(","));
1406
- }
1407
- } else if (value.length > 0) {
1408
- newValue.set(key, value);
1409
- } else {
1410
- newValue.delete(key);
1411
- }
1412
- return newValue;
1413
- });
1414
- };
1415
- const onSearchQueryChange = (event) => {
1416
- onPageChange(1);
1417
- if (event.target.value) {
1418
- analytics.captureEvent("qeta_search", event.target.value);
1419
- }
1420
- setSearchQuery(event.target.value);
1421
- };
1422
- useDebounce(
1423
- () => {
1424
- if (filters.searchQuery !== searchQuery) {
1425
- setFilters({ ...filters, searchQuery });
1426
- }
1427
- },
1428
- 400,
1429
- [searchQuery]
1430
- );
1431
- useEffect(() => {
1432
- let filtersApplied = false;
1433
- searchParams.forEach((value, key) => {
1434
- var _a2;
1435
- try {
1436
- if (key === "page") {
1437
- const pv = Number.parseInt(value, 10);
1438
- if (pv > 0) {
1439
- setPage(pv);
1440
- } else {
1441
- setPage(1);
1442
- }
1443
- } else if (key === "questionsPerPage") {
1444
- const qpp = Number.parseInt(value, 10);
1445
- if (qpp > 0)
1446
- setQuestionsPerPage(qpp);
1447
- } else if (filterKeys.includes(key)) {
1448
- filtersApplied = true;
1449
- if (key === "tags") {
1450
- filters.tags = (_a2 = filterTags(value)) != null ? _a2 : [];
1451
- } else {
1452
- filters[key] = value;
1453
- }
1454
- }
1455
- } catch (_e) {
1456
- }
1457
- });
1458
- setFilters(filters);
1459
- if (filtersApplied) {
1460
- setShowFilterPanel(true);
1461
- }
1462
- }, [searchParams, filters]);
1463
- const {
1464
- value: response,
1465
- loading,
1466
- error
1467
- } = useQetaApi(
1468
- (api) => {
1469
- return api.getQuestions({
1470
- limit: questionsPerPage,
1471
- offset: (page - 1) * questionsPerPage,
1472
- includeEntities: true,
1473
- author,
1474
- favorite,
1475
- ...filters
1476
- });
1477
- },
1478
- [page, filters, questionsPerPage]
1479
- );
1480
- const onPageSizeChange = (value) => {
1481
- if (response) {
1482
- let newPage = page;
1483
- while (newPage * value > response.total) {
1484
- newPage -= 1;
1485
- }
1486
- onPageChange(Math.max(1, newPage));
1487
- }
1488
- setQuestionsPerPage(value);
1489
- setSearchParams((prev) => {
1490
- const newValue = prev;
1491
- newValue.set("questionsPerPage", String(value));
1492
- return newValue;
1493
- });
1494
- };
1495
- let shownTitle = title;
1496
- let link = void 0;
1497
- if (author) {
1498
- shownTitle = `Questions by `;
1499
- link = /* @__PURE__ */ React.createElement(EntityRefLink, { entityRef: author, hideIcon: true, defaultKind: "user" });
1500
- } else if (entity) {
1501
- shownTitle = `Questions about `;
1502
- link = /* @__PURE__ */ React.createElement(EntityRefLink, { entityRef: entity });
1503
- } else if (tags) {
1504
- shownTitle = `Questions tagged with [${tags.join(", ")}]`;
1505
- } else if (favorite) {
1506
- shownTitle = "Your favorite questions";
1507
- }
1508
- return /* @__PURE__ */ React.createElement(Box, { className: "qetaQuestionsContainer" }, showTitle && /* @__PURE__ */ React.createElement(
1509
- Typography,
1510
- {
1511
- variant: "h5",
1512
- className: "qetaQuestionsContainerTitle",
1513
- style: { marginBottom: "1.5rem" }
1514
- },
1515
- shownTitle,
1516
- link
1517
- ), /* @__PURE__ */ React.createElement(Grid, { container: true, justifyContent: "space-between" }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12, md: 4 }, /* @__PURE__ */ React.createElement(
1518
- TextField,
1519
- {
1520
- id: "search-bar",
1521
- fullWidth: true,
1522
- onChange: onSearchQueryChange,
1523
- label: "Search for questions",
1524
- className: "qetaQuestionsContainerSearchInput",
1525
- variant: "outlined",
1526
- placeholder: "Search...",
1527
- size: "small",
1528
- style: { marginBottom: "5px" }
1529
- }
1530
- )), showAskButton && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1531
- AskQuestionButton,
1532
- {
1533
- entity: entity != null ? entity : filters.entity,
1534
- entityPage: entity !== void 0,
1535
- tags
1536
- }
1537
- ))), /* @__PURE__ */ React.createElement(Grid, { container: true, justifyContent: "space-between" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1538
- Typography,
1539
- {
1540
- variant: "h6",
1541
- className: "qetaQuestionsContainerQuestionCount"
1542
- },
1543
- `${(_a = response == null ? void 0 : response.total) != null ? _a : 0} ${(response == null ? void 0 : response.total) === 1 ? "question" : "questions"}`
1544
- )), (showFilters != null ? showFilters : true) && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
1545
- Button,
1546
- {
1547
- onClick: () => setShowFilterPanel(!showFilterPanel),
1548
- className: "qetaQuestionsContainerFilterPanelBtn",
1549
- startIcon: /* @__PURE__ */ React.createElement(FilterList, null)
1550
- },
1551
- "Filter"
1552
- ))), (showFilters != null ? showFilters : true) && /* @__PURE__ */ React.createElement(Collapse, { in: showFilterPanel }, /* @__PURE__ */ React.createElement(
1553
- FilterPanel,
1554
- {
1555
- onChange: onFilterChange,
1556
- filters,
1557
- showEntityFilter: !entity
1558
- }
1559
- )), /* @__PURE__ */ React.createElement(
1560
- QuestionList,
1561
- {
1562
- loading,
1563
- error,
1564
- response,
1565
- onPageChange,
1566
- onPageSizeChange,
1567
- entity,
1568
- page,
1569
- pageSize: questionsPerPage,
1570
- showNoQuestionsBtn,
1571
- entityPage: entity !== void 0,
1572
- tags
1573
- }
1574
- ));
1575
- };
1576
-
1577
- const MarkdownEditor = (props) => {
1578
- const { config, value, onChange, height, error, placeholder } = props;
1579
- const [selectedTab, setSelectedTab] = React.useState(
1580
- "write"
1581
- );
1582
- const styles = useStyles$1();
1583
- const errorApi = useApi(errorApiRef);
1584
- const qetaApi = useApi(qetaApiRef);
1585
- const imageUpload = () => {
1586
- return async function* (data) {
1587
- var _a, _b;
1588
- const fileType = await FileType.fromBuffer(data);
1589
- const mimeType = fileType ? fileType.mime : "text/plain";
1590
- const attachment = await qetaApi.postAttachment(
1591
- new Blob([data], { type: mimeType })
1592
- );
1593
- if ("errors" in attachment) {
1594
- errorApi.post({
1595
- name: "Upload failed",
1596
- message: (_b = (_a = attachment.errors) == null ? void 0 : _a.map((e) => e.message).join(", ")) != null ? _b : ""
1597
- });
1598
- return false;
1599
- }
1600
- props.onImageUpload(attachment.id);
1601
- yield attachment.locationUri;
1602
- return true;
1603
- };
1604
- };
1605
- const isUploadDisabled = (config == null ? void 0 : config.getOptionalBoolean("qeta.storage.disabled")) || false;
1606
- return /* @__PURE__ */ React.createElement(
1607
- ReactMde,
1608
- {
1609
- classes: {
1610
- reactMde: `qetaMarkdownEditorEdit ${styles.markdownEditor}`,
1611
- textArea: error ? `qetaMarkdownEditorError ${styles.markdownEditorError}` : void 0,
1612
- preview: "qetaMarkdownEditorPreview",
1613
- toolbar: "qetaMarkdownEditorToolbar"
1614
- },
1615
- value,
1616
- onChange,
1617
- selectedTab,
1618
- onTabChange: setSelectedTab,
1619
- minEditorHeight: height,
1620
- minPreviewHeight: height - 10,
1621
- childProps: {
1622
- textArea: {
1623
- required: true,
1624
- placeholder
1625
- }
1626
- },
1627
- generateMarkdownPreview: (content) => Promise.resolve(
1628
- /* @__PURE__ */ React.createElement(
1629
- MarkdownContent,
1630
- {
1631
- content,
1632
- dialect: "gfm",
1633
- className: `qetaMarkdownEditorPreview ${styles.markdownContent}`
1634
- }
1635
- )
1636
- ),
1637
- paste: isUploadDisabled ? void 0 : {
1638
- saveImage: imageUpload()
1639
- }
1640
- }
1641
- );
1642
- };
1643
-
1644
- const TagInput = (props) => {
1645
- const { control } = props;
1646
- const qetaApi = useApi(qetaApiRef);
1647
- const config = useApi(configApiRef);
1648
- const allowCreation = useMemo(
1649
- () => {
1650
- var _a;
1651
- return (_a = config.getOptionalBoolean("qeta.tags.allowCreation")) != null ? _a : true;
1652
- },
1653
- [config]
1654
- );
1655
- const allowedTags = useMemo(
1656
- () => {
1657
- var _a;
1658
- return (_a = config.getOptionalStringArray("qeta.tags.allowedTags")) != null ? _a : null;
1659
- },
1660
- [config]
1661
- );
1662
- const maximumTags = useMemo(
1663
- () => {
1664
- var _a;
1665
- return (_a = config.getOptionalNumber("qeta.tags.max")) != null ? _a : 5;
1666
- },
1667
- [config]
1668
- );
1669
- const [availableTags, setAvailableTags] = React.useState([]);
1670
- useEffect(() => {
1671
- if (allowCreation) {
1672
- qetaApi.getTags().catch((_) => setAvailableTags(null)).then(
1673
- (data) => data ? setAvailableTags(data.map((tag) => tag.tag)) : setAvailableTags(null)
1674
- );
1675
- } else {
1676
- setAvailableTags(allowedTags);
1677
- }
1678
- }, [qetaApi, allowCreation, allowedTags]);
1679
- if (!allowCreation && (allowedTags === null || allowedTags.length === 0)) {
1680
- return null;
1681
- }
1682
- return /* @__PURE__ */ React.createElement(
1683
- Controller,
1684
- {
1685
- control,
1686
- render: ({ field: { onChange, value }, fieldState: { error } }) => /* @__PURE__ */ React.createElement(
1687
- Autocomplete,
1688
- {
1689
- multiple: true,
1690
- id: "tags-select",
1691
- className: "qetaAskFormTags",
1692
- value,
1693
- options: availableTags != null ? availableTags : [],
1694
- freeSolo: allowCreation,
1695
- onChange: (_e, newValue) => {
1696
- const tags = filterTags(newValue);
1697
- if (tags && tags.length <= maximumTags && tags.length === newValue.length) {
1698
- onChange(newValue);
1699
- }
1700
- },
1701
- renderInput: (params) => /* @__PURE__ */ React.createElement(
1702
- TextField,
1703
- {
1704
- ...params,
1705
- variant: "outlined",
1706
- margin: "normal",
1707
- label: "Tags",
1708
- placeholder: "Type or select tags",
1709
- helperText: `Add up to ${maximumTags} tags to categorize your question`,
1710
- error: error !== void 0
1711
- }
1712
- )
1713
- }
1714
- ),
1715
- name: "tags"
1716
- }
1717
- );
1718
- };
1719
-
1720
- const EntitiesInput = (props) => {
1721
- const { control, entityRef } = props;
1722
- const configApi = useApi(configApiRef);
1723
- const catalogApi = useApi(catalogApiRef);
1724
- const [availableEntities, setAvailableEntities] = React.useState([]);
1725
- const entityKinds = useMemo(() => {
1726
- let kinds = configApi.getOptionalStringArray("qeta.entityKinds");
1727
- if (!kinds) {
1728
- kinds = configApi.getOptionalStringArray("qeta.entities.kinds");
1729
- }
1730
- return kinds || ["Component", "System"];
1731
- }, [configApi]);
1732
- const max = useMemo(
1733
- () => {
1734
- var _a;
1735
- return (_a = configApi.getOptionalNumber("qeta.entities.max")) != null ? _a : 3;
1736
- },
1737
- [configApi]
1738
- );
1739
- useEffect(() => {
1740
- if (entityRef) {
1741
- catalogApi.getEntityByRef(entityRef).then((data) => {
1742
- if (data) {
1743
- setAvailableEntities([data]);
1744
- }
1745
- });
1746
- }
1747
- }, [catalogApi, entityRef]);
1748
- useEffect(() => {
1749
- if (entityRef) {
1750
- return;
1751
- }
1752
- if (entityKinds && entityKinds.length > 0) {
1753
- catalogApi.getEntities({
1754
- filter: { kind: entityKinds },
1755
- fields: [
1756
- "kind",
1757
- "metadata.name",
1758
- "metadata.namespace",
1759
- "metadata.title"
1760
- ]
1761
- }).catch((_) => setAvailableEntities(null)).then(
1762
- (data) => data ? setAvailableEntities(data.items) : setAvailableEntities(null)
1763
- );
1764
- }
1765
- }, [catalogApi, entityRef, configApi, entityKinds]);
1766
- if (!availableEntities || availableEntities.length === 0) {
1767
- return null;
1768
- }
1769
- return /* @__PURE__ */ React.createElement(
1770
- Controller,
1771
- {
1772
- control,
1773
- render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
1774
- Autocomplete,
1775
- {
1776
- multiple: true,
1777
- className: "qetaAskFormEntities",
1778
- value,
1779
- groupBy: entityKinds.length > 1 ? (option) => option.kind : void 0,
1780
- id: "entities-select",
1781
- options: availableEntities,
1782
- getOptionLabel: getEntityTitle,
1783
- getOptionSelected: (o, v) => stringifyEntityRef(o) === stringifyEntityRef(v),
1784
- onChange: (_e, newValue) => {
1785
- if (!value || value.length < max) {
1786
- onChange(newValue);
1787
- }
1788
- },
1789
- renderInput: (params) => /* @__PURE__ */ React.createElement(
1790
- TextField,
1791
- {
1792
- ...params,
1793
- variant: "outlined",
1794
- margin: "normal",
1795
- label: "Entities",
1796
- placeholder: "Type or select entities",
1797
- helperText: `Add up to ${max} entities this question relates to`
1798
- }
1799
- )
1800
- }
1801
- ),
1802
- name: "entities"
1803
- }
1804
- );
1805
- };
1806
-
1807
- const AskAnonymouslyCheckbox = (props) => {
1808
- const { control, label } = props;
1809
- return /* @__PURE__ */ React.createElement(Box, { style: { marginLeft: "0.2rem" } }, /* @__PURE__ */ React.createElement(
1810
- Controller,
1811
- {
1812
- control,
1813
- render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
1814
- FormControlLabel,
1815
- {
1816
- control: /* @__PURE__ */ React.createElement(Tooltip, { title: "By enabling this, other users won't be able to see you as an author" }, /* @__PURE__ */ React.createElement(
1817
- Checkbox,
1818
- {
1819
- onChange,
1820
- value,
1821
- size: "small",
1822
- name: "anonymous"
1823
- }
1824
- )),
1825
- label
1826
- }
1827
- ),
1828
- name: "anonymous"
1829
- }
1830
- ));
1831
- };
1832
-
1833
- const formToRequest = (form, images) => {
1834
- var _a;
1835
- return {
1836
- ...form,
1837
- entities: (_a = form.entities) == null ? void 0 : _a.map(stringifyEntityRef),
1838
- images
1839
- };
1840
- };
1841
- const getDefaultValues = (props) => {
1842
- var _a;
1843
- return {
1844
- title: "",
1845
- content: "",
1846
- tags: (_a = props.tags) != null ? _a : [],
1847
- entities: []
1848
- };
1849
- };
1850
- const getValues = async (api, catalogApi, id) => {
1851
- var _a;
1852
- if (!id) {
1853
- return getDefaultValues({});
1854
- }
1855
- const question = await api.getQuestion(id);
1856
- const entities = question.entities && question.entities.length > 0 ? await catalogApi.getEntitiesByRefs({
1857
- entityRefs: question.entities,
1858
- fields: [
1859
- "kind",
1860
- "metadata.name",
1861
- "metadata.namespace",
1862
- "metadata.title"
1863
- ]
1864
- }) : [];
1865
- return {
1866
- title: question.title,
1867
- content: question.content,
1868
- tags: (_a = question.tags) != null ? _a : [],
1869
- entities: "items" in entities ? compact(entities.items) : []
1870
- };
1871
- };
1872
- const AskForm = (props) => {
1873
- const { id, entity, onPost, entityPage } = props;
1874
- const questionRoute = useRouteRef(questionRouteRef);
1875
- const navigate = useNavigate();
1876
- const analytics = useAnalytics();
1877
- const [entityRef, setEntityRef] = React.useState(entity);
1878
- const [posting, setPosting] = React.useState(false);
1879
- const [values, setValues] = React.useState(getDefaultValues(props));
1880
- const [error, setError] = React.useState(false);
1881
- const [images, setImages] = React.useState([]);
1882
- const [searchParams, _setSearchParams] = useSearchParams();
1883
- const qetaApi = useApi(qetaApiRef);
1884
- const catalogApi = useApi(catalogApiRef);
1885
- const configApi = useApi(configApiRef);
1886
- const allowAnonymouns = configApi.getOptionalBoolean("qeta.allowAnonymous");
1887
- const styles = useStyles$1();
1888
- const {
1889
- register,
1890
- handleSubmit,
1891
- control,
1892
- reset,
1893
- formState: { errors }
1894
- } = useForm({
1895
- values,
1896
- defaultValues: getDefaultValues(props)
1897
- });
1898
- const postQuestion = (data) => {
1899
- setPosting(true);
1900
- const queryParams = new URLSearchParams();
1901
- if (entity) {
1902
- queryParams.set("entity", entity);
1903
- }
1904
- if (entityPage) {
1905
- queryParams.set("entityPage", "true");
1906
- }
1907
- if (id) {
1908
- qetaApi.updateQuestion(id, formToRequest(data, images)).then((q) => {
1909
- if (!q || !q.id) {
1910
- setError(true);
1911
- return;
1912
- }
1913
- reset();
1914
- analytics.captureEvent("edit", "question");
1915
- if (onPost) {
1916
- onPost(q);
1917
- } else if (entity) {
1918
- navigate(
1919
- `${questionRoute({
1920
- id: q.id.toString(10)
1921
- })}?${queryParams.toString()}`
1922
- );
1923
- } else {
1924
- navigate(questionRoute({ id: q.id.toString(10) }));
1925
- }
1926
- }).catch((_e) => {
1927
- setError(true);
1928
- setPosting(false);
1929
- });
1930
- return;
1931
- }
1932
- qetaApi.postQuestion(formToRequest(data, images)).then((q) => {
1933
- if (!q || !q.id) {
1934
- setError(true);
1935
- return;
1936
- }
1937
- analytics.captureEvent("post", "question");
1938
- reset();
1939
- if (entity) {
1940
- navigate(
1941
- `${questionRoute({
1942
- id: q.id.toString(10)
1943
- })}?${queryParams.toString()}`
1944
- );
1945
- } else {
1946
- navigate(questionRoute({ id: q.id.toString(10) }));
1947
- }
1948
- }).catch((_e) => {
1949
- setError(true);
1950
- setPosting(false);
1951
- });
1952
- };
1953
- useEffect(() => {
1954
- if (!entityRef) {
1955
- const e = searchParams.get("entity");
1956
- if (e) {
1957
- setEntityRef(e);
1958
- }
1959
- }
1960
- }, [entityRef, searchParams]);
1961
- useEffect(() => {
1962
- if (id) {
1963
- getValues(qetaApi, catalogApi, id).then((data) => {
1964
- setValues(data);
1965
- });
1966
- }
1967
- }, [qetaApi, catalogApi, id]);
1968
- useEffect(() => {
1969
- if (entityRef) {
1970
- catalogApi.getEntityByRef(entityRef).then((data) => {
1971
- if (data) {
1972
- setValues((v) => {
1973
- return { ...v, entities: [data] };
1974
- });
1975
- }
1976
- });
1977
- }
1978
- }, [catalogApi, entityRef]);
1979
- useEffect(() => {
1980
- reset(values);
1981
- }, [values, reset]);
1982
- return /* @__PURE__ */ React.createElement("form", { onSubmit: handleSubmit(postQuestion), className: "qetaAskForm" }, error && /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, "Could not post question"), /* @__PURE__ */ React.createElement(
1983
- TextField,
1984
- {
1985
- label: "Title",
1986
- className: "qetaAskFormTitle",
1987
- required: true,
1988
- fullWidth: true,
1989
- error: "title" in errors,
1990
- margin: "normal",
1991
- variant: "outlined",
1992
- helperText: "Write good title for your question that people can understand",
1993
- // @ts-ignore
1994
- ...register("title", { required: true, maxLength: 255 })
1995
- }
1996
- ), /* @__PURE__ */ React.createElement(
1997
- Controller,
1998
- {
1999
- control,
2000
- rules: {
2001
- required: true
2002
- },
2003
- render: ({ field: { onChange, value } }) => /* @__PURE__ */ React.createElement(
2004
- MarkdownEditor,
2005
- {
2006
- value,
2007
- onChange,
2008
- height: 400,
2009
- error: "content" in errors,
2010
- placeholder: "Your question",
2011
- config: configApi,
2012
- onImageUpload: (imageId) => {
2013
- setImages((prevImages) => [...prevImages, imageId]);
2014
- }
2015
- }
2016
- ),
2017
- name: "content"
2018
- }
2019
- ), /* @__PURE__ */ React.createElement(TagInput, { control }), /* @__PURE__ */ React.createElement(EntitiesInput, { control, entityRef }), allowAnonymouns && !id && /* @__PURE__ */ React.createElement(AskAnonymouslyCheckbox, { control, label: "Ask anonymously" }), /* @__PURE__ */ React.createElement(
2020
- Button,
2021
- {
2022
- color: "primary",
2023
- type: "submit",
2024
- variant: "contained",
2025
- disabled: posting,
2026
- className: `qetaAskFormSubmitBtn ${styles.postButton}`
2027
- },
2028
- id ? "Save" : "Post"
2029
- ));
2030
- };
2031
-
2032
- const UserLink = (props) => {
2033
- const { entityRef, linkProps } = props;
2034
- const userRoute = useRouteRef(userRouteRef);
2035
- const { primaryTitle: userName } = useEntityPresentation(
2036
- entityRef.startsWith("user:") ? entityRef : `user:${entityRef}`
2037
- );
2038
- if (entityRef === "anonymous") {
2039
- return /* @__PURE__ */ React.createElement(React.Fragment, null, "Anonymous");
2040
- }
2041
- return /* @__PURE__ */ React.createElement(Link, { to: `${userRoute()}/${entityRef}`, ...linkProps }, userName);
2042
- };
2043
- const AuthorLink = (props) => {
2044
- const { entity, linkProps } = props;
2045
- return /* @__PURE__ */ React.createElement(UserLink, { entityRef: entity.author, linkProps });
2046
- };
2047
- const UpdatedByLink = (props) => {
2048
- const { entity, linkProps } = props;
2049
- if (!entity.updatedBy) {
2050
- return null;
2051
- }
2052
- return /* @__PURE__ */ React.createElement(UserLink, { entityRef: entity.updatedBy, linkProps });
2053
- };
2054
-
2055
- const QuestionTableRow = (props) => {
2056
- const { question } = props;
2057
- const questionRoute = useRouteRef(questionRouteRef);
2058
- 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(
2059
- RelativeTimeWithTooltip,
2060
- {
2061
- value: question.updated ? question.updated : question.created
2062
- }
2063
- )));
2064
- };
2065
-
2066
- const QuestionsTable = (props) => {
2067
- var _a, _b;
2068
- const [page, setPage] = React.useState(1);
2069
- const [questionsPerPage, setQuestionsPerPage] = React.useState(
2070
- (_a = props.rowsPerPage) != null ? _a : 10
2071
- );
2072
- const [quickFilter, setQuickFilter] = React.useState(
2073
- (_b = props.quickFilter) != null ? _b : "latest"
2074
- );
2075
- const [refresh, setRefresh] = React.useState(0);
2076
- const [filters, setFilters] = React.useState({
2077
- order: "desc",
2078
- orderBy: "created",
2079
- noAnswers: "false",
2080
- noCorrectAnswer: "false",
2081
- noVotes: "false",
2082
- searchQuery: "",
2083
- favorite: false
2084
- });
2085
- const {
2086
- value: response,
2087
- loading,
2088
- error
2089
- } = useQetaApi(
2090
- (api) => api.getQuestions({
2091
- limit: questionsPerPage,
2092
- offset: (page - 1) * questionsPerPage,
2093
- includeEntities: true,
2094
- ...filters
2095
- }),
2096
- [page, filters, questionsPerPage, refresh]
2097
- );
2098
- const handleQuickFilterChange = (filter) => {
2099
- setQuickFilter(filter);
2100
- if (filter === "latest") {
2101
- setFilters({
2102
- ...filters,
2103
- order: "desc",
2104
- orderBy: "created",
2105
- favorite: false
2106
- });
2107
- } else if (filter === "favorites") {
2108
- setFilters({
2109
- ...filters,
2110
- order: "desc",
2111
- orderBy: "created",
2112
- favorite: true
2113
- });
2114
- } else if (filter === "most_viewed") {
2115
- setFilters({
2116
- ...filters,
2117
- order: "desc",
2118
- orderBy: "views",
2119
- favorite: false
2120
- });
2121
- }
2122
- };
2123
- const handleChangePage = (_, newPage) => {
2124
- setPage(newPage + 1);
2125
- };
2126
- const handleChangeRowsPerPage = (event) => {
2127
- setQuestionsPerPage(parseInt(event.target.value, 10));
2128
- setPage(1);
2129
- };
2130
- if (error || response === void 0) {
2131
- return /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not load questions." }, error == null ? void 0 : error.message);
2132
- }
2133
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
2134
- Grid,
2135
- {
2136
- container: true,
2137
- justifyContent: "space-between",
2138
- alignItems: "center",
2139
- style: { marginBottom: "1em" },
2140
- className: "qetaQuestionsTableGrid"
2141
- },
2142
- /* @__PURE__ */ React.createElement(Grid, { item: true }, props.hideTitle === true ? null : /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, "Q&A")),
2143
- /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(ButtonGroup, null, /* @__PURE__ */ React.createElement(
2144
- Button,
2145
- {
2146
- color: quickFilter === "latest" ? "primary" : void 0,
2147
- onClick: () => handleQuickFilterChange("latest")
2148
- },
2149
- "Latest"
2150
- ), /* @__PURE__ */ React.createElement(
2151
- Button,
2152
- {
2153
- color: quickFilter === "favorites" ? "primary" : void 0,
2154
- onClick: () => handleQuickFilterChange("favorites")
2155
- },
2156
- "Favorites"
2157
- ), /* @__PURE__ */ React.createElement(
2158
- Button,
2159
- {
2160
- color: quickFilter === "most_viewed" ? "primary" : void 0,
2161
- onClick: () => handleQuickFilterChange("most_viewed")
2162
- },
2163
- "Most viewed"
2164
- )), /* @__PURE__ */ React.createElement(
2165
- LinkButton,
2166
- {
2167
- to: "#",
2168
- variant: "text",
2169
- onClick: () => setRefresh(refresh + 1)
2170
- },
2171
- /* @__PURE__ */ React.createElement(RefreshIcon, null)
2172
- ))
2173
- ), /* @__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(
2174
- TablePagination,
2175
- {
2176
- rowsPerPageOptions: [5, 10, 20, 30, 40, 50],
2177
- component: "div",
2178
- count: response.total,
2179
- rowsPerPage: questionsPerPage,
2180
- page: page - 1,
2181
- onPageChange: handleChangePage,
2182
- onRowsPerPageChange: handleChangeRowsPerPage
2183
- }
2184
- )));
2185
- };
2186
-
2187
- const Content = (props) => {
2188
- return /* @__PURE__ */ React.createElement(QuestionsTable, { hideTitle: true, ...props });
2189
- };
2190
-
2191
- const TrophyIcon = (props) => /* @__PURE__ */ React.createElement(SvgIcon, { ...props, viewBox: "0 0 24 24" }, /* @__PURE__ */ React.createElement(
2192
- "path",
2193
- {
2194
- id: "secondary",
2195
- 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"
2196
- }
2197
- ), /* @__PURE__ */ React.createElement(
2198
- "path",
2199
- {
2200
- id: "primary",
2201
- 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"
2202
- }
2203
- ));
2204
-
2205
- const useStyles = makeStyles((theme) => {
2206
- return {
2207
- trophyIcon: {
2208
- backgroundColor: "initial",
2209
- color: theme.palette.text.primary,
2210
- borderRadius: "50%",
2211
- boxSizing: "border-box",
2212
- padding: "1rem",
2213
- height: 100,
2214
- width: 100
2215
- },
2216
- votesText: {
2217
- display: "grid",
2218
- placeItems: "center",
2219
- marginLeft: "16px"
2220
- }
2221
- };
2222
- });
2223
-
2224
- const DefaultRankingIcons = /* @__PURE__ */ new Map([
2225
- [
2226
- 1,
2227
- /* @__PURE__ */ React.createElement(
2228
- TrophyIcon,
2229
- {
2230
- style: { color: "#DAA520", height: "2.2rem", width: "2.2rem" }
2231
- }
2232
- )
2233
- ],
2234
- [
2235
- 2,
2236
- /* @__PURE__ */ React.createElement(
2237
- TrophyIcon,
2238
- {
2239
- style: { color: "#C0C0C0", height: "2.1rem", width: "2.1rem" }
2240
- }
2241
- )
2242
- ],
2243
- [
2244
- 3,
2245
- /* @__PURE__ */ React.createElement(TrophyIcon, { style: { color: "#B87333", height: "2rem", width: "2rem" } })
2246
- ]
2247
- ]);
2248
- const DefaultUserIcon = /* @__PURE__ */ React.createElement(TrophyIcon, { style: { height: "2rem", width: "2rem" } });
2249
- const getOrdinal = (n) => {
2250
- if (n % 10 === 1 && n % 100 !== 11) {
2251
- return `${n}st`;
2252
- } else if (n % 10 === 2 && n % 100 !== 12) {
2253
- return `${n}nd`;
2254
- } else if (n % 10 === 3 && n % 100 !== 13) {
2255
- return `${n}rd`;
2256
- }
2257
- return `${n}th`;
2258
- };
2259
- const RankingRow = (props) => {
2260
- var _a, _b;
2261
- const classes = useStyles();
2262
- const userRef = props.userRef;
2263
- const ordinalPosition = (props == null ? void 0 : props.position) ? getOrdinal(props == null ? void 0 : props.position) : "";
2264
- const userIcon = ((_a = props.rankingIcon) == null ? void 0 : _a.userRankingIcon) ? (_b = props.rankingIcon) == null ? void 0 : _b.userRankingIcon : DefaultUserIcon;
2265
- 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;
2266
- const rankingIcon = (props == null ? void 0 : props.position) > 3 ? userIcon : topRankingIcon;
2267
- return /* @__PURE__ */ React.createElement(ListItem, { className: "qetaRankingCardRow" }, /* @__PURE__ */ React.createElement(ListItemAvatar, null, /* @__PURE__ */ React.createElement(Avatar, { className: classes.trophyIcon }, rankingIcon)), /* @__PURE__ */ React.createElement(
2268
- ListItemText,
2269
- {
2270
- disableTypography: true,
2271
- style: {
2272
- display: "flex",
2273
- justifyContent: "center"
2274
- },
2275
- primary: /* @__PURE__ */ React.createElement("div", { style: { display: "flex" } }, /* @__PURE__ */ React.createElement(
2276
- Typography,
2277
- {
2278
- style: { marginRight: "10px", fontWeight: 400 },
2279
- variant: "subtitle1"
2280
- },
2281
- `${ordinalPosition}`
2282
- ), /* @__PURE__ */ React.createElement(UserLink, { entityRef: userRef != null ? userRef : "" }))
2283
- }
2284
- ), /* @__PURE__ */ React.createElement("div", { className: classes.votesText }, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, props == null ? void 0 : props.total, " ", props.unit)));
2285
- };
2286
- const RankingCard = (props) => {
2287
- var _a, _b, _c, _d, _e, _f, _g, _h;
2288
- const rankingStats = props.limit ? (_a = props.statistic) == null ? void 0 : _a.ranking.slice(0, props.limit) : (_b = props.statistic) == null ? void 0 : _b.ranking;
2289
- 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) => {
2290
- return /* @__PURE__ */ React.createElement(
2291
- RankingRow,
2292
- {
2293
- total: authorStats.total || 0,
2294
- position: authorStats.position || 0,
2295
- userRef: authorStats.author,
2296
- unit: props.unit
2297
- }
2298
- );
2299
- }), !(rankingStats == null ? void 0 : rankingStats.some(
2300
- (authorStats) => {
2301
- var _a2, _b2;
2302
- return authorStats.author === ((_b2 = (_a2 = props.statistic) == null ? void 0 : _a2.loggedUser) == null ? void 0 : _b2.author);
2303
- }
2304
- )) && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("hr", null), /* @__PURE__ */ React.createElement(
2305
- RankingRow,
2306
- {
2307
- total: ((_d = (_c = props.statistic) == null ? void 0 : _c.loggedUser) == null ? void 0 : _d.total) || 0,
2308
- position: ((_f = (_e = props.statistic) == null ? void 0 : _e.loggedUser) == null ? void 0 : _f.position) || 0,
2309
- userRef: (_h = (_g = props.statistic) == null ? void 0 : _g.loggedUser) == null ? void 0 : _h.author,
2310
- unit: props.unit
2311
- }
2312
- ))));
2313
- };
2314
- const TopRankingUsers = (props) => {
2315
- const {
2316
- value: topStatistics,
2317
- loading,
2318
- error
2319
- } = useQetaApi(
2320
- (api) => api.getTopStatisticsHomepage({
2321
- options: { limit: 50 }
2322
- })
2323
- );
2324
- const tabData = [
2325
- {
2326
- title: "Most questions",
2327
- description: "People who have posted most questions",
2328
- unit: "questions"
2329
- },
2330
- {
2331
- title: "Most answers",
2332
- description: "People who have answered most questions",
2333
- unit: "answers"
2334
- },
2335
- {
2336
- title: "Top Upvoted Questions",
2337
- description: "People who have the highest rated questions",
2338
- unit: "votes"
2339
- },
2340
- {
2341
- title: "Top Upvoted Answers",
2342
- description: "People who have the highest rated answers",
2343
- unit: "votes"
2344
- },
2345
- {
2346
- title: "Top Upvoted Correct Answers",
2347
- description: "People who have the highest rated correct answers",
2348
- unit: "votes"
2349
- }
2350
- ];
2351
- if ((error || topStatistics === void 0) && !loading) {
2352
- return /* @__PURE__ */ React.createElement(WarningPanel, { severity: "error", title: "Could not load statistics." }, error == null ? void 0 : error.message);
2353
- }
2354
- let content;
2355
- if (loading) {
2356
- content = [
2357
- /* @__PURE__ */ React.createElement(CardTab, null, /* @__PURE__ */ React.createElement(Progress, null))
2358
- ];
2359
- } else if (topStatistics && topStatistics.length > 0) {
2360
- content = topStatistics == null ? void 0 : topStatistics.map((stats, index) => {
2361
- return /* @__PURE__ */ React.createElement(CardTab, { label: tabData[index].title }, /* @__PURE__ */ React.createElement(
2362
- RankingCard,
2363
- {
2364
- description: tabData[index].description,
2365
- limit: props.limit,
2366
- statistic: stats,
2367
- unit: tabData[index].unit
2368
- }
2369
- ));
2370
- });
2371
- } else {
2372
- content = [/* @__PURE__ */ React.createElement(CardTab, null, "No statistics available")];
2373
- }
2374
- return /* @__PURE__ */ React.createElement(TabbedCard, { title: props.title || "Ranking Q&A \u{1F3C6}" }, content);
2375
- };
2376
-
2377
- const BackToQuestionsButton = (props) => {
2378
- const styles = useStyles$1();
2379
- const entityRoute = useRouteRef(entityRouteRef);
2380
- const rootRoute = useRouteRef(qetaRouteRef);
2381
- let to = rootRoute();
2382
- const [searchParams] = useSearchParams();
2383
- const entity = searchParams.get("entity");
2384
- if (entity) {
2385
- const entityRef = parseEntityRef(entity);
2386
- if (props.entityPage) {
2387
- to = `${entityRoute(entityRef)}/qeta`;
2388
- } else {
2389
- to = `${rootRoute()}?entity=${entity}`;
2390
- }
2391
- }
2392
- return /* @__PURE__ */ React.createElement(
2393
- LinkButton,
2394
- {
2395
- className: `${styles.marginRight} qetaBackToQuestionsBtn`,
2396
- to,
2397
- startIcon: /* @__PURE__ */ React.createElement(HomeOutlined, null)
2398
- },
2399
- "Back to questions"
2400
- );
2401
- };
2402
-
2403
- const StatisticsPage = () => {
2404
- 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 })));
2405
- };
2406
-
2407
- 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 };
2408
- //# sourceMappingURL=index-CuD-iIx9.esm.js.map