@terrafirmagreg-team/discussions 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -5
- package/src/components/comments-view.tsx +32 -32
- package/src/components/comments.tsx +8 -8
- package/src/components/feedback/block.tsx +366 -366
- package/src/components/feedback/page.tsx +181 -181
- package/src/hooks/comments/use-menu-item.ts +29 -29
- package/src/hooks/comments/use-session.ts +21 -21
- package/src/lib/comments/server.ts +204 -204
- package/src/lib/feedback.ts +74 -74
- package/src/locales/comments/en_us.json +0 -54
- package/src/locales/comments/ru_ru.json +0 -54
- package/src/locales/en_us.json +0 -13
- package/src/locales/ru_ru.json +0 -13
- package/src/locales.ts +0 -6
|
@@ -1,204 +1,204 @@
|
|
|
1
|
-
import 'server-only';
|
|
2
|
-
import { NextComment } from '@fuma-comment/server/next';
|
|
3
|
-
type RouteRequest = Request & { nextUrl: { pathname: string } };
|
|
4
|
-
import {
|
|
5
|
-
authConfig,
|
|
6
|
-
authCookie,
|
|
7
|
-
authEnabled,
|
|
8
|
-
authMode,
|
|
9
|
-
devAuthCookie,
|
|
10
|
-
devCookieAuth,
|
|
11
|
-
} from '@terrafirmagreg-team/auth';
|
|
12
|
-
import { env, githubAppToken } from '@terrafirmagreg-team/data';
|
|
13
|
-
import {
|
|
14
|
-
createAuth,
|
|
15
|
-
createGitHubClient,
|
|
16
|
-
createStorage,
|
|
17
|
-
type GitHubDiscussionsConfig,
|
|
18
|
-
} from 'fuma-comment-github-discussions';
|
|
19
|
-
import { discussionsConfig } from '../config';
|
|
20
|
-
import { createDiscussion } from './github';
|
|
21
|
-
import { localComments } from './local';
|
|
22
|
-
|
|
23
|
-
const APP_TOKEN = 'app';
|
|
24
|
-
const CATEGORY = 'General';
|
|
25
|
-
|
|
26
|
-
type DiscussionTarget = {
|
|
27
|
-
dataset: string;
|
|
28
|
-
repo: string;
|
|
29
|
-
repoId: string;
|
|
30
|
-
categoryId: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function datasets(): DiscussionTarget[] {
|
|
34
|
-
return Object.entries(discussionsConfig().datasets).flatMap(
|
|
35
|
-
([dataset, cfg]) =>
|
|
36
|
-
cfg.repo && cfg.discussions
|
|
37
|
-
? [
|
|
38
|
-
{
|
|
39
|
-
dataset,
|
|
40
|
-
repo: cfg.repo,
|
|
41
|
-
repoId: cfg.discussions.repoId,
|
|
42
|
-
categoryId: cfg.discussions.categoryId,
|
|
43
|
-
},
|
|
44
|
-
]
|
|
45
|
-
: [],
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function datasetSegs(): Set<string> {
|
|
50
|
-
return new Set(datasets().map(({ dataset }) => dataset));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function defaultDataset(): DiscussionTarget {
|
|
54
|
-
return datasets().find(({ dataset }) => dataset === 'dev')!;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function datasetOf(page: string) {
|
|
58
|
-
const segs = datasetSegs();
|
|
59
|
-
const seg = page.split('~').find((part) => segs.has(part));
|
|
60
|
-
return datasets().find(({ dataset }) => dataset === seg) ?? defaultDataset();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function pageToTitle(page: string): string {
|
|
64
|
-
const locales = new Set(discussionsConfig().knownLanguages);
|
|
65
|
-
const segs = datasetSegs();
|
|
66
|
-
const pretty = page
|
|
67
|
-
.split('~')
|
|
68
|
-
.slice(0, -1)
|
|
69
|
-
.filter((seg) => seg && !locales.has(seg) && !segs.has(seg))
|
|
70
|
-
.map((seg) =>
|
|
71
|
-
seg.replace(/[-_]+/g, ' ').replace(/\b\w/g, (ch) => ch.toUpperCase()),
|
|
72
|
-
);
|
|
73
|
-
return pretty.join(' › ') || 'Page';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
type Client = ReturnType<typeof createGitHubClient>;
|
|
77
|
-
type Storage = ReturnType<typeof createStorage>;
|
|
78
|
-
|
|
79
|
-
let siteUrl: string | undefined;
|
|
80
|
-
|
|
81
|
-
function currentSiteUrl(): string {
|
|
82
|
-
return siteUrl ?? discussionsConfig().siteUrl;
|
|
83
|
-
}
|
|
84
|
-
const storages = new Map<string, Storage>();
|
|
85
|
-
|
|
86
|
-
function cfgFor(d: DiscussionTarget): GitHubDiscussionsConfig {
|
|
87
|
-
return {
|
|
88
|
-
repo: d.repo,
|
|
89
|
-
repoId: d.repoId,
|
|
90
|
-
categoryId: d.categoryId,
|
|
91
|
-
category: CATEGORY,
|
|
92
|
-
readToken: APP_TOKEN,
|
|
93
|
-
tokenSecret: authConfig().tokenSecret,
|
|
94
|
-
cookieName: authCookie,
|
|
95
|
-
pageToTitle,
|
|
96
|
-
pageToUrl: (page) => {
|
|
97
|
-
const segs = page.split('~');
|
|
98
|
-
segs.pop();
|
|
99
|
-
return `${currentSiteUrl()}/${segs.filter(Boolean).join('/')}`;
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function wrapClient(d: DiscussionTarget, cfg: GitHubDiscussionsConfig): Client {
|
|
105
|
-
const base = createGitHubClient(cfg);
|
|
106
|
-
const wrapped = { ...base };
|
|
107
|
-
for (const key of Object.keys(base) as (keyof Client)[]) {
|
|
108
|
-
const fn = base[key] as (...args: unknown[]) => unknown;
|
|
109
|
-
(wrapped as Record<string, unknown>)[key] = async (...args: unknown[]) => {
|
|
110
|
-
if (args.includes(APP_TOKEN)) {
|
|
111
|
-
const token = await githubAppToken(d.repo);
|
|
112
|
-
args = args.map((arg) => (arg === APP_TOKEN ? token : arg));
|
|
113
|
-
}
|
|
114
|
-
return fn(...args);
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
wrapped.createDiscussion = async (title, url, token) => {
|
|
118
|
-
const resolved = token === APP_TOKEN ? await githubAppToken(d.repo) : token;
|
|
119
|
-
return createDiscussion(cfg, title, url, resolved);
|
|
120
|
-
};
|
|
121
|
-
return wrapped;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function storageFor(d: DiscussionTarget): Storage {
|
|
125
|
-
let storage = storages.get(d.dataset);
|
|
126
|
-
if (!storage) {
|
|
127
|
-
const cfg = cfgFor(d);
|
|
128
|
-
storage = createStorage(cfg, wrapClient(d, cfg));
|
|
129
|
-
storages.set(d.dataset, storage);
|
|
130
|
-
}
|
|
131
|
-
return storage;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
type RouteHandler = (
|
|
135
|
-
req: RouteRequest,
|
|
136
|
-
context: { params: Promise<{ comment: string[] }> },
|
|
137
|
-
) => Promise<Response>;
|
|
138
|
-
|
|
139
|
-
export function commentsEnabled(): boolean {
|
|
140
|
-
return authEnabled() && datasets().length > 0;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function feedbackEnabled(): boolean {
|
|
144
|
-
return commentsEnabled() && Boolean(env('APP_ID') && env('APP_PRIVATE_KEY'));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function createCommentsRouter(
|
|
148
|
-
options: { siteUrl?: string } = {},
|
|
149
|
-
):
|
|
150
|
-
| { GET: RouteHandler; POST: RouteHandler; PATCH: RouteHandler; DELETE: RouteHandler }
|
|
151
|
-
| undefined {
|
|
152
|
-
if (!commentsEnabled()) return undefined;
|
|
153
|
-
siteUrl = options.siteUrl ?? discussionsConfig().siteUrl;
|
|
154
|
-
|
|
155
|
-
if (authMode() === 'dev' || !(env('APP_ID') && env('APP_PRIVATE_KEY')))
|
|
156
|
-
return NextComment({
|
|
157
|
-
role: 'database',
|
|
158
|
-
mention: { enabled: true },
|
|
159
|
-
storage: localComments(),
|
|
160
|
-
auth: devCookieAuth({ cookieName: devAuthCookie }),
|
|
161
|
-
}) as unknown as { GET: RouteHandler; POST: RouteHandler; PATCH: RouteHandler; DELETE: RouteHandler };
|
|
162
|
-
|
|
163
|
-
const dispatch = (page: string | undefined) =>
|
|
164
|
-
storageFor(datasetOf(page ?? ''));
|
|
165
|
-
|
|
166
|
-
const storage = {
|
|
167
|
-
getComments: (args: Parameters<Storage['getComments']>[0]) =>
|
|
168
|
-
dispatch(args.page as string | undefined).getComments(args),
|
|
169
|
-
postComment: (args: Parameters<Storage['postComment']>[0]) =>
|
|
170
|
-
dispatch(args.page as string | undefined).postComment(args),
|
|
171
|
-
updateComment: (args: Parameters<Storage['updateComment']>[0]) =>
|
|
172
|
-
dispatch(args.page).updateComment(args),
|
|
173
|
-
deleteComment: (args: Parameters<Storage['deleteComment']>[0]) =>
|
|
174
|
-
dispatch(args.page).deleteComment(args),
|
|
175
|
-
setRate: (args: Parameters<Storage['setRate']>[0]) =>
|
|
176
|
-
dispatch(args.page).setRate(args),
|
|
177
|
-
deleteRate: (args: Parameters<Storage['deleteRate']>[0]) =>
|
|
178
|
-
dispatch(args.page).deleteRate(args),
|
|
179
|
-
getCommentAuthor: async (
|
|
180
|
-
args: Parameters<Storage['getCommentAuthor']>[0],
|
|
181
|
-
) => {
|
|
182
|
-
for (const d of datasets()) {
|
|
183
|
-
const author = await Promise.resolve(
|
|
184
|
-
storageFor(d).getCommentAuthor(args),
|
|
185
|
-
).catch(() => null);
|
|
186
|
-
if (author) return author;
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
},
|
|
190
|
-
getRole: (args: Parameters<Storage['getRole']>[0]) =>
|
|
191
|
-
storageFor(datasetOf(args.page)).getRole(args),
|
|
192
|
-
queryUsers: (args: Parameters<NonNullable<Storage['queryUsers']>>[0]) =>
|
|
193
|
-
dispatch(args.page).queryUsers?.(args) ?? [],
|
|
194
|
-
} as Storage;
|
|
195
|
-
|
|
196
|
-
const cfg = cfgFor(defaultDataset());
|
|
197
|
-
return NextComment({
|
|
198
|
-
role: 'database',
|
|
199
|
-
mention: { enabled: true },
|
|
200
|
-
storage,
|
|
201
|
-
auth: createAuth(cfg, wrapClient(defaultDataset(), cfg)),
|
|
202
|
-
}) as unknown as { GET: RouteHandler; POST: RouteHandler; PATCH: RouteHandler; DELETE: RouteHandler };
|
|
203
|
-
}
|
|
204
|
-
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { NextComment } from '@fuma-comment/server/next';
|
|
3
|
+
type RouteRequest = Request & { nextUrl: { pathname: string } };
|
|
4
|
+
import {
|
|
5
|
+
authConfig,
|
|
6
|
+
authCookie,
|
|
7
|
+
authEnabled,
|
|
8
|
+
authMode,
|
|
9
|
+
devAuthCookie,
|
|
10
|
+
devCookieAuth,
|
|
11
|
+
} from '@terrafirmagreg-team/auth';
|
|
12
|
+
import { env, githubAppToken } from '@terrafirmagreg-team/data';
|
|
13
|
+
import {
|
|
14
|
+
createAuth,
|
|
15
|
+
createGitHubClient,
|
|
16
|
+
createStorage,
|
|
17
|
+
type GitHubDiscussionsConfig,
|
|
18
|
+
} from 'fuma-comment-github-discussions';
|
|
19
|
+
import { discussionsConfig } from '../config';
|
|
20
|
+
import { createDiscussion } from './github';
|
|
21
|
+
import { localComments } from './local';
|
|
22
|
+
|
|
23
|
+
const APP_TOKEN = 'app';
|
|
24
|
+
const CATEGORY = 'General';
|
|
25
|
+
|
|
26
|
+
type DiscussionTarget = {
|
|
27
|
+
dataset: string;
|
|
28
|
+
repo: string;
|
|
29
|
+
repoId: string;
|
|
30
|
+
categoryId: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function datasets(): DiscussionTarget[] {
|
|
34
|
+
return Object.entries(discussionsConfig().datasets).flatMap(
|
|
35
|
+
([dataset, cfg]) =>
|
|
36
|
+
cfg.repo && cfg.discussions
|
|
37
|
+
? [
|
|
38
|
+
{
|
|
39
|
+
dataset,
|
|
40
|
+
repo: cfg.repo,
|
|
41
|
+
repoId: cfg.discussions.repoId,
|
|
42
|
+
categoryId: cfg.discussions.categoryId,
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
: [],
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function datasetSegs(): Set<string> {
|
|
50
|
+
return new Set(datasets().map(({ dataset }) => dataset));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function defaultDataset(): DiscussionTarget {
|
|
54
|
+
return datasets().find(({ dataset }) => dataset === 'dev')!;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function datasetOf(page: string) {
|
|
58
|
+
const segs = datasetSegs();
|
|
59
|
+
const seg = page.split('~').find((part) => segs.has(part));
|
|
60
|
+
return datasets().find(({ dataset }) => dataset === seg) ?? defaultDataset();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function pageToTitle(page: string): string {
|
|
64
|
+
const locales = new Set(discussionsConfig().knownLanguages);
|
|
65
|
+
const segs = datasetSegs();
|
|
66
|
+
const pretty = page
|
|
67
|
+
.split('~')
|
|
68
|
+
.slice(0, -1)
|
|
69
|
+
.filter((seg) => seg && !locales.has(seg) && !segs.has(seg))
|
|
70
|
+
.map((seg) =>
|
|
71
|
+
seg.replace(/[-_]+/g, ' ').replace(/\b\w/g, (ch) => ch.toUpperCase()),
|
|
72
|
+
);
|
|
73
|
+
return pretty.join(' › ') || 'Page';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type Client = ReturnType<typeof createGitHubClient>;
|
|
77
|
+
type Storage = ReturnType<typeof createStorage>;
|
|
78
|
+
|
|
79
|
+
let siteUrl: string | undefined;
|
|
80
|
+
|
|
81
|
+
function currentSiteUrl(): string {
|
|
82
|
+
return siteUrl ?? discussionsConfig().siteUrl;
|
|
83
|
+
}
|
|
84
|
+
const storages = new Map<string, Storage>();
|
|
85
|
+
|
|
86
|
+
function cfgFor(d: DiscussionTarget): GitHubDiscussionsConfig {
|
|
87
|
+
return {
|
|
88
|
+
repo: d.repo,
|
|
89
|
+
repoId: d.repoId,
|
|
90
|
+
categoryId: d.categoryId,
|
|
91
|
+
category: CATEGORY,
|
|
92
|
+
readToken: APP_TOKEN,
|
|
93
|
+
tokenSecret: authConfig().tokenSecret,
|
|
94
|
+
cookieName: authCookie,
|
|
95
|
+
pageToTitle,
|
|
96
|
+
pageToUrl: (page) => {
|
|
97
|
+
const segs = page.split('~');
|
|
98
|
+
segs.pop();
|
|
99
|
+
return `${currentSiteUrl()}/${segs.filter(Boolean).join('/')}`;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function wrapClient(d: DiscussionTarget, cfg: GitHubDiscussionsConfig): Client {
|
|
105
|
+
const base = createGitHubClient(cfg);
|
|
106
|
+
const wrapped = { ...base };
|
|
107
|
+
for (const key of Object.keys(base) as (keyof Client)[]) {
|
|
108
|
+
const fn = base[key] as (...args: unknown[]) => unknown;
|
|
109
|
+
(wrapped as Record<string, unknown>)[key] = async (...args: unknown[]) => {
|
|
110
|
+
if (args.includes(APP_TOKEN)) {
|
|
111
|
+
const token = await githubAppToken(d.repo);
|
|
112
|
+
args = args.map((arg) => (arg === APP_TOKEN ? token : arg));
|
|
113
|
+
}
|
|
114
|
+
return fn(...args);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
wrapped.createDiscussion = async (title, url, token) => {
|
|
118
|
+
const resolved = token === APP_TOKEN ? await githubAppToken(d.repo) : token;
|
|
119
|
+
return createDiscussion(cfg, title, url, resolved);
|
|
120
|
+
};
|
|
121
|
+
return wrapped;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function storageFor(d: DiscussionTarget): Storage {
|
|
125
|
+
let storage = storages.get(d.dataset);
|
|
126
|
+
if (!storage) {
|
|
127
|
+
const cfg = cfgFor(d);
|
|
128
|
+
storage = createStorage(cfg, wrapClient(d, cfg));
|
|
129
|
+
storages.set(d.dataset, storage);
|
|
130
|
+
}
|
|
131
|
+
return storage;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
type RouteHandler = (
|
|
135
|
+
req: RouteRequest,
|
|
136
|
+
context: { params: Promise<{ comment: string[] }> },
|
|
137
|
+
) => Promise<Response>;
|
|
138
|
+
|
|
139
|
+
export function commentsEnabled(): boolean {
|
|
140
|
+
return authEnabled() && datasets().length > 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function feedbackEnabled(): boolean {
|
|
144
|
+
return commentsEnabled() && Boolean(env('APP_ID') && env('APP_PRIVATE_KEY'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createCommentsRouter(
|
|
148
|
+
options: { siteUrl?: string } = {},
|
|
149
|
+
):
|
|
150
|
+
| { GET: RouteHandler; POST: RouteHandler; PATCH: RouteHandler; DELETE: RouteHandler }
|
|
151
|
+
| undefined {
|
|
152
|
+
if (!commentsEnabled()) return undefined;
|
|
153
|
+
siteUrl = options.siteUrl ?? discussionsConfig().siteUrl;
|
|
154
|
+
|
|
155
|
+
if (authMode() === 'dev' || !(env('APP_ID') && env('APP_PRIVATE_KEY')))
|
|
156
|
+
return NextComment({
|
|
157
|
+
role: 'database',
|
|
158
|
+
mention: { enabled: true },
|
|
159
|
+
storage: localComments(),
|
|
160
|
+
auth: devCookieAuth({ cookieName: devAuthCookie }),
|
|
161
|
+
}) as unknown as { GET: RouteHandler; POST: RouteHandler; PATCH: RouteHandler; DELETE: RouteHandler };
|
|
162
|
+
|
|
163
|
+
const dispatch = (page: string | undefined) =>
|
|
164
|
+
storageFor(datasetOf(page ?? ''));
|
|
165
|
+
|
|
166
|
+
const storage = {
|
|
167
|
+
getComments: (args: Parameters<Storage['getComments']>[0]) =>
|
|
168
|
+
dispatch(args.page as string | undefined).getComments(args),
|
|
169
|
+
postComment: (args: Parameters<Storage['postComment']>[0]) =>
|
|
170
|
+
dispatch(args.page as string | undefined).postComment(args),
|
|
171
|
+
updateComment: (args: Parameters<Storage['updateComment']>[0]) =>
|
|
172
|
+
dispatch(args.page).updateComment(args),
|
|
173
|
+
deleteComment: (args: Parameters<Storage['deleteComment']>[0]) =>
|
|
174
|
+
dispatch(args.page).deleteComment(args),
|
|
175
|
+
setRate: (args: Parameters<Storage['setRate']>[0]) =>
|
|
176
|
+
dispatch(args.page).setRate(args),
|
|
177
|
+
deleteRate: (args: Parameters<Storage['deleteRate']>[0]) =>
|
|
178
|
+
dispatch(args.page).deleteRate(args),
|
|
179
|
+
getCommentAuthor: async (
|
|
180
|
+
args: Parameters<Storage['getCommentAuthor']>[0],
|
|
181
|
+
) => {
|
|
182
|
+
for (const d of datasets()) {
|
|
183
|
+
const author = await Promise.resolve(
|
|
184
|
+
storageFor(d).getCommentAuthor(args),
|
|
185
|
+
).catch(() => null);
|
|
186
|
+
if (author) return author;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
},
|
|
190
|
+
getRole: (args: Parameters<Storage['getRole']>[0]) =>
|
|
191
|
+
storageFor(datasetOf(args.page)).getRole(args),
|
|
192
|
+
queryUsers: (args: Parameters<NonNullable<Storage['queryUsers']>>[0]) =>
|
|
193
|
+
dispatch(args.page).queryUsers?.(args) ?? [],
|
|
194
|
+
} as Storage;
|
|
195
|
+
|
|
196
|
+
const cfg = cfgFor(defaultDataset());
|
|
197
|
+
return NextComment({
|
|
198
|
+
role: 'database',
|
|
199
|
+
mention: { enabled: true },
|
|
200
|
+
storage,
|
|
201
|
+
auth: createAuth(cfg, wrapClient(defaultDataset(), cfg)),
|
|
202
|
+
}) as unknown as { GET: RouteHandler; POST: RouteHandler; PATCH: RouteHandler; DELETE: RouteHandler };
|
|
203
|
+
}
|
|
204
|
+
|
package/src/lib/feedback.ts
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
import { githubAppToken } from '@terrafirmagreg-team/data';
|
|
3
|
-
import { createGitHubClient } from 'fuma-comment-github-discussions';
|
|
4
|
-
import { discussionsConfig } from './config';
|
|
5
|
-
import { createDiscussion } from './comments/github';
|
|
6
|
-
import {
|
|
7
|
-
blockFeedback,
|
|
8
|
-
pageFeedback,
|
|
9
|
-
type ActionResponse,
|
|
10
|
-
type BlockFeedback,
|
|
11
|
-
type PageFeedback,
|
|
12
|
-
} from './feedback-schema';
|
|
13
|
-
|
|
14
|
-
function targetOf(pathname: string) {
|
|
15
|
-
const datasets = discussionsConfig().datasets;
|
|
16
|
-
const seg = pathname.split('/').find((part) => part in datasets);
|
|
17
|
-
const cfg = (seg && datasets[seg]) || datasets.dev;
|
|
18
|
-
if (!cfg?.repo || !cfg.discussions) return undefined;
|
|
19
|
-
return {
|
|
20
|
-
repo: cfg.repo,
|
|
21
|
-
repoId: cfg.discussions.repoId,
|
|
22
|
-
categoryId: cfg.discussions.categoryId,
|
|
23
|
-
category: 'General',
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function onPageFeedbackAction(
|
|
28
|
-
feedback: PageFeedback,
|
|
29
|
-
): Promise<ActionResponse> {
|
|
30
|
-
feedback = pageFeedback.parse(feedback);
|
|
31
|
-
const { pathname } = new URL(feedback.url);
|
|
32
|
-
|
|
33
|
-
return createDiscussionThread(
|
|
34
|
-
pathname,
|
|
35
|
-
`[${feedback.opinion}] ${feedback.message}\n\n> Forwarded from user feedback.`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function onBlockFeedbackAction(
|
|
40
|
-
feedback: BlockFeedback,
|
|
41
|
-
): Promise<ActionResponse> {
|
|
42
|
-
feedback = blockFeedback.parse(feedback);
|
|
43
|
-
const url = new URL(feedback.url);
|
|
44
|
-
url.hash = feedback.blockId;
|
|
45
|
-
|
|
46
|
-
return createDiscussionThread(
|
|
47
|
-
url.pathname,
|
|
48
|
-
`> ${feedback.blockBody}\n\n${feedback.message}\n\n> [Forwarded from user feedback](${url.href}).`,
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function createDiscussionThread(
|
|
53
|
-
pageId: string,
|
|
54
|
-
body: string,
|
|
55
|
-
): Promise<ActionResponse> {
|
|
56
|
-
const target = targetOf(pageId);
|
|
57
|
-
if (!target) return { githubUrl: '' };
|
|
58
|
-
const github = createGitHubClient(target);
|
|
59
|
-
const auth = await githubAppToken(target.repo);
|
|
60
|
-
const title = `Feedback for ${pageId}`;
|
|
61
|
-
|
|
62
|
-
const discussion =
|
|
63
|
-
(await github.findDiscussion(title, auth)) ??
|
|
64
|
-
(await createDiscussion(
|
|
65
|
-
target,
|
|
66
|
-
title,
|
|
67
|
-
`${discussionsConfig().siteUrl}${pageId}`,
|
|
68
|
-
auth,
|
|
69
|
-
));
|
|
70
|
-
await github.addComment(discussion.id, body, auth);
|
|
71
|
-
|
|
72
|
-
return { githubUrl: discussion.url };
|
|
73
|
-
}
|
|
74
|
-
|
|
1
|
+
'use server';
|
|
2
|
+
import { githubAppToken } from '@terrafirmagreg-team/data';
|
|
3
|
+
import { createGitHubClient } from 'fuma-comment-github-discussions';
|
|
4
|
+
import { discussionsConfig } from './config';
|
|
5
|
+
import { createDiscussion } from './comments/github';
|
|
6
|
+
import {
|
|
7
|
+
blockFeedback,
|
|
8
|
+
pageFeedback,
|
|
9
|
+
type ActionResponse,
|
|
10
|
+
type BlockFeedback,
|
|
11
|
+
type PageFeedback,
|
|
12
|
+
} from './feedback-schema';
|
|
13
|
+
|
|
14
|
+
function targetOf(pathname: string) {
|
|
15
|
+
const datasets = discussionsConfig().datasets;
|
|
16
|
+
const seg = pathname.split('/').find((part) => part in datasets);
|
|
17
|
+
const cfg = (seg && datasets[seg]) || datasets.dev;
|
|
18
|
+
if (!cfg?.repo || !cfg.discussions) return undefined;
|
|
19
|
+
return {
|
|
20
|
+
repo: cfg.repo,
|
|
21
|
+
repoId: cfg.discussions.repoId,
|
|
22
|
+
categoryId: cfg.discussions.categoryId,
|
|
23
|
+
category: 'General',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function onPageFeedbackAction(
|
|
28
|
+
feedback: PageFeedback,
|
|
29
|
+
): Promise<ActionResponse> {
|
|
30
|
+
feedback = pageFeedback.parse(feedback);
|
|
31
|
+
const { pathname } = new URL(feedback.url);
|
|
32
|
+
|
|
33
|
+
return createDiscussionThread(
|
|
34
|
+
pathname,
|
|
35
|
+
`[${feedback.opinion}] ${feedback.message}\n\n> Forwarded from user feedback.`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function onBlockFeedbackAction(
|
|
40
|
+
feedback: BlockFeedback,
|
|
41
|
+
): Promise<ActionResponse> {
|
|
42
|
+
feedback = blockFeedback.parse(feedback);
|
|
43
|
+
const url = new URL(feedback.url);
|
|
44
|
+
url.hash = feedback.blockId;
|
|
45
|
+
|
|
46
|
+
return createDiscussionThread(
|
|
47
|
+
url.pathname,
|
|
48
|
+
`> ${feedback.blockBody}\n\n${feedback.message}\n\n> [Forwarded from user feedback](${url.href}).`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function createDiscussionThread(
|
|
53
|
+
pageId: string,
|
|
54
|
+
body: string,
|
|
55
|
+
): Promise<ActionResponse> {
|
|
56
|
+
const target = targetOf(pageId);
|
|
57
|
+
if (!target) return { githubUrl: '' };
|
|
58
|
+
const github = createGitHubClient(target);
|
|
59
|
+
const auth = await githubAppToken(target.repo);
|
|
60
|
+
const title = `Feedback for ${pageId}`;
|
|
61
|
+
|
|
62
|
+
const discussion =
|
|
63
|
+
(await github.findDiscussion(title, auth)) ??
|
|
64
|
+
(await createDiscussion(
|
|
65
|
+
target,
|
|
66
|
+
title,
|
|
67
|
+
`${discussionsConfig().siteUrl}${pageId}`,
|
|
68
|
+
auth,
|
|
69
|
+
));
|
|
70
|
+
await github.addComment(discussion.id, body, auth);
|
|
71
|
+
|
|
72
|
+
return { githubUrl: discussion.url };
|
|
73
|
+
}
|
|
74
|
+
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"1 Reply(replies)": "1 Reply",
|
|
3
|
-
"Add emoji(emoji picker)(aria-label)": "Add emoji",
|
|
4
|
-
"Add link(comment editor)": "Add link",
|
|
5
|
-
"Attach your own image to the comment.(image upload)": "Attach your own image to the comment.",
|
|
6
|
-
"Close(dialog)(aria-label)": "Close",
|
|
7
|
-
"Comments(replies)": "Comments",
|
|
8
|
-
"Copied(code block)(aria-label)": "Copied",
|
|
9
|
-
"Copy(comment menu)": "Copy",
|
|
10
|
-
"Delete(comment menu)": "Delete",
|
|
11
|
-
"Dislike(comment actions)(aria-label)": "Dislike",
|
|
12
|
-
"Edit comment(comment menu)": "Edit comment",
|
|
13
|
-
"Edit message(edit comment form)": "Edit message",
|
|
14
|
-
"Edit the content of your comment.(comment menu)": "Edit the content of your comment.",
|
|
15
|
-
"Edit(comment menu)": "Edit",
|
|
16
|
-
"Image preview(image upload)(image alt text)": "Image preview",
|
|
17
|
-
"Insert code block(code block editor)": "Insert code block",
|
|
18
|
-
"Insert emoji(emoji picker)": "Insert emoji",
|
|
19
|
-
"Insert(link editor)": "Insert",
|
|
20
|
-
"Leave comment(comment form)": "Leave comment",
|
|
21
|
-
"Like(comment actions)(aria-label)": "Like",
|
|
22
|
-
"Link(link editor)": "Link",
|
|
23
|
-
"Load more(comment list)": "Load more",
|
|
24
|
-
"Loading(mention picker)": "Loading",
|
|
25
|
-
"Loading...(emoji picker)": "Loading...",
|
|
26
|
-
"My link (optional)(link editor)": "My link (optional)",
|
|
27
|
-
"Name(link editor)": "Name",
|
|
28
|
-
"No comments(comment list)": "No comments",
|
|
29
|
-
"No emoji found.(emoji picker)": "No emoji found.",
|
|
30
|
-
"No language found.(code block editor)": "No language found.",
|
|
31
|
-
"No results(mention picker)": "No results",
|
|
32
|
-
"Open menu(comment menu)(aria-label)": "Open menu",
|
|
33
|
-
"Reply to comment(reply form)": "Reply to comment",
|
|
34
|
-
"Reply(reply form)(aria-label)": "Reply",
|
|
35
|
-
"Replying to {name}(comment actions)": "Replying to {name}",
|
|
36
|
-
"Save edit(edit comment form)(aria-label)": "Save edit",
|
|
37
|
-
"Save(link editor)": "Save",
|
|
38
|
-
"Search emoji...(emoji picker)": "Search emoji...",
|
|
39
|
-
"Search language...(code block editor)": "Search language...",
|
|
40
|
-
"Send(comment form)(aria-label)": "Send",
|
|
41
|
-
"Sign In(auth button)": "Sign In",
|
|
42
|
-
"Today(timestamp)": "Today",
|
|
43
|
-
"Toggle bold(comment editor)(aria-label)": "Toggle bold",
|
|
44
|
-
"Toggle code block(code block editor)(aria-label)": "Toggle code block",
|
|
45
|
-
"Toggle inline code(comment editor)(aria-label)": "Toggle inline code",
|
|
46
|
-
"Toggle italic(comment editor)(aria-label)": "Toggle italic",
|
|
47
|
-
"Toggle link(comment editor)(aria-label)": "Toggle link",
|
|
48
|
-
"Toggle strikethrough(comment editor)(aria-label)": "Toggle strikethrough",
|
|
49
|
-
"Unset(link editor)": "Unset",
|
|
50
|
-
"Upload image(image upload)(aria-label)": "Upload image",
|
|
51
|
-
"Uploaded image(image upload)(image alt text)": "Uploaded image",
|
|
52
|
-
"Uploading(image upload)": "Uploading",
|
|
53
|
-
"{count} Replies(replies)": "{count} Replies"
|
|
54
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"1 Reply(replies)": "1 ответ",
|
|
3
|
-
"Add emoji(emoji picker)(aria-label)": "Добавить эмодзи",
|
|
4
|
-
"Add link(comment editor)": "Добавить ссылку",
|
|
5
|
-
"Attach your own image to the comment.(image upload)": "Прикрепите изображение к комментарию.",
|
|
6
|
-
"Close(dialog)(aria-label)": "Закрыть",
|
|
7
|
-
"Comments(replies)": "Комментарии",
|
|
8
|
-
"Copied(code block)(aria-label)": "Скопировано",
|
|
9
|
-
"Copy(comment menu)": "Копировать",
|
|
10
|
-
"Delete(comment menu)": "Удалить",
|
|
11
|
-
"Dislike(comment actions)(aria-label)": "Не нравится",
|
|
12
|
-
"Edit comment(comment menu)": "Редактировать комментарий",
|
|
13
|
-
"Edit message(edit comment form)": "Редактировать сообщение",
|
|
14
|
-
"Edit the content of your comment.(comment menu)": "Отредактируйте текст вашего комментария.",
|
|
15
|
-
"Edit(comment menu)": "Редактировать",
|
|
16
|
-
"Image preview(image upload)(image alt text)": "Просмотр изображения",
|
|
17
|
-
"Insert code block(code block editor)": "Вставить блок кода",
|
|
18
|
-
"Insert emoji(emoji picker)": "Вставить эмодзи",
|
|
19
|
-
"Insert(link editor)": "Вставить",
|
|
20
|
-
"Leave comment(comment form)": "Оставить комментарий",
|
|
21
|
-
"Like(comment actions)(aria-label)": "Нравится",
|
|
22
|
-
"Link(link editor)": "Ссылка",
|
|
23
|
-
"Load more(comment list)": "Загрузить ещё",
|
|
24
|
-
"Loading(mention picker)": "Загрузка",
|
|
25
|
-
"Loading...(emoji picker)": "Загрузка...",
|
|
26
|
-
"My link (optional)(link editor)": "Моя ссылка (необязательно)",
|
|
27
|
-
"Name(link editor)": "Имя",
|
|
28
|
-
"No comments(comment list)": "Нет комментариев",
|
|
29
|
-
"No emoji found.(emoji picker)": "Эмодзи не найдены.",
|
|
30
|
-
"No language found.(code block editor)": "Язык не найден.",
|
|
31
|
-
"No results(mention picker)": "Нет результатов",
|
|
32
|
-
"Open menu(comment menu)(aria-label)": "Открыть меню",
|
|
33
|
-
"Reply to comment(reply form)": "Ответить на комментарий",
|
|
34
|
-
"Reply(reply form)(aria-label)": "Ответить",
|
|
35
|
-
"Replying to {name}(comment actions)": "Ответ для {name}",
|
|
36
|
-
"Save edit(edit comment form)(aria-label)": "Сохранить правку",
|
|
37
|
-
"Save(link editor)": "Сохранить",
|
|
38
|
-
"Search emoji...(emoji picker)": "Поиск эмодзи...",
|
|
39
|
-
"Search language...(code block editor)": "Поиск языка...",
|
|
40
|
-
"Send(comment form)(aria-label)": "Отправить",
|
|
41
|
-
"Sign In(auth button)": "Войти",
|
|
42
|
-
"Today(timestamp)": "Сегодня",
|
|
43
|
-
"Toggle bold(comment editor)(aria-label)": "Жирный",
|
|
44
|
-
"Toggle code block(code block editor)(aria-label)": "Блок кода",
|
|
45
|
-
"Toggle inline code(comment editor)(aria-label)": "Строчный код",
|
|
46
|
-
"Toggle italic(comment editor)(aria-label)": "Курсив",
|
|
47
|
-
"Toggle link(comment editor)(aria-label)": "Ссылка",
|
|
48
|
-
"Toggle strikethrough(comment editor)(aria-label)": "Зачёркнутый",
|
|
49
|
-
"Unset(link editor)": "Сбросить",
|
|
50
|
-
"Upload image(image upload)(aria-label)": "Загрузить изображение",
|
|
51
|
-
"Uploaded image(image upload)(image alt text)": "Загруженное изображение",
|
|
52
|
-
"Uploading(image upload)": "Загружается",
|
|
53
|
-
"{count} Replies(replies)": "Ответов: {count}"
|
|
54
|
-
}
|