chore: test deploy snapshot
This commit is contained in:
@@ -2463,11 +2463,57 @@
|
|||||||
|
|
||||||
.app-chat-preview-card--prompt .app-chat-preview-card__actions {
|
.app-chat-preview-card--prompt .app-chat-preview-card__actions {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
min-width: fit-content;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.app-chat-preview-card--prompt .app-chat-preview-card__header {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-chat-preview-card--prompt .app-chat-preview-card__actions {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-chat-prompt-card__submitted {
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-chat-prompt-card__selection-pill {
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-chat-prompt-card__step-header {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-chat-prompt-card__preview-toolbar {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-chat-prompt-card__preview-title.ant-typography {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.app-chat-prompt-card__preview-modal.ant-modal {
|
.app-chat-prompt-card__preview-modal.ant-modal {
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(248, 250, 252, 0.98), rgba(226, 232, 240, 0.98)),
|
linear-gradient(180deg, rgba(248, 250, 252, 0.98), rgba(226, 232, 240, 0.98)),
|
||||||
|
|||||||
@@ -173,6 +173,9 @@ type ShareSearchResult = {
|
|||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
type ShareSearchIndexedResult = ShareSearchResult & {
|
||||||
|
searchText: string;
|
||||||
|
};
|
||||||
type ShareSearchPanelMode = 'all' | 'apps';
|
type ShareSearchPanelMode = 'all' | 'apps';
|
||||||
type ShareWorkServerVersionStatus = 'latest' | 'unknown' | 'update-available' | 'build-required';
|
type ShareWorkServerVersionStatus = 'latest' | 'unknown' | 'update-available' | 'build-required';
|
||||||
type ClientNotificationPermissionState = 'unsupported' | 'default' | 'granted' | 'denied';
|
type ClientNotificationPermissionState = 'unsupported' | 'default' | 'granted' | 'denied';
|
||||||
@@ -1923,6 +1926,10 @@ function matchesSearchKeyword(keyword: string, ...values: Array<string | null |
|
|||||||
return values.some((value) => normalizeSearchKeyword(value ?? '').includes(keyword));
|
return values.some((value) => normalizeSearchKeyword(value ?? '').includes(keyword));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSearchKeywordText(...values: Array<string | null | undefined>) {
|
||||||
|
return normalizeSearchKeyword(values.filter(Boolean).join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
function buildSharePromptSelectionKey(parentRequestId: string, sourceMessageId: number, promptIndex: number, promptTitle: string, promptSignature: string) {
|
function buildSharePromptSelectionKey(parentRequestId: string, sourceMessageId: number, promptIndex: number, promptTitle: string, promptSignature: string) {
|
||||||
return [parentRequestId.trim(), sourceMessageId, promptIndex, promptTitle.trim(), promptSignature.trim()].join('::');
|
return [parentRequestId.trim(), sourceMessageId, promptIndex, promptTitle.trim(), promptSignature.trim()].join('::');
|
||||||
}
|
}
|
||||||
@@ -8379,24 +8386,131 @@ export function ChatSharePage() {
|
|||||||
? renderEmbeddedSharePlayApp(programTarget.appId, closeProgramTarget, normalizedToken)
|
? renderEmbeddedSharePlayApp(programTarget.appId, closeProgramTarget, normalizedToken)
|
||||||
: null;
|
: null;
|
||||||
const isServerCommandDrawerOpen = programTarget?.appId === 'server-command';
|
const isServerCommandDrawerOpen = programTarget?.appId === 'server-command';
|
||||||
const searchResults = useMemo<ShareSearchResult[]>(() => {
|
const indexedContentSearchResults = useMemo<ShareSearchIndexedResult[]>(() => {
|
||||||
const keyword = normalizeSearchKeyword(searchKeyword);
|
const results: ShareSearchIndexedResult[] = [];
|
||||||
const results: ShareSearchResult[] = [];
|
|
||||||
|
|
||||||
if (searchPanelMode === 'apps') {
|
sortedRequests.forEach((request) => {
|
||||||
|
const requestText = buildShareVisibleText(request.userText);
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
key: `request:${request.requestId}`,
|
||||||
|
title: requestText || '질문',
|
||||||
|
description: `질문 · ${formatTimeLabel(request.createdAt)}`,
|
||||||
|
category: 'request',
|
||||||
|
requestId: request.requestId,
|
||||||
|
scrollTarget: {
|
||||||
|
type: 'request',
|
||||||
|
value: request.requestId,
|
||||||
|
},
|
||||||
|
searchText: buildSearchKeywordText(requestText, request.statusMessage, request.requestId),
|
||||||
|
});
|
||||||
|
|
||||||
|
buildSharePreviewItemsFromText(request.userText, normalizedToken).forEach((item) => {
|
||||||
|
results.push({
|
||||||
|
key: `request-resource:${request.requestId}:${item.id}`,
|
||||||
|
title: item.label,
|
||||||
|
description: `질문 리소스 · ${item.kind}`,
|
||||||
|
category: 'resource',
|
||||||
|
requestId: request.requestId,
|
||||||
|
resource: {
|
||||||
|
key: `request-resource:${request.requestId}:${item.id}`,
|
||||||
|
label: item.label,
|
||||||
|
url: item.url,
|
||||||
|
kind: item.kind,
|
||||||
|
meta: `${item.kind} resource`,
|
||||||
|
},
|
||||||
|
scrollTarget: {
|
||||||
|
type: 'request',
|
||||||
|
value: request.requestId,
|
||||||
|
},
|
||||||
|
searchText: buildSearchKeywordText(item.label, item.url, item.kind),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
sortedMessages.forEach((entry) => {
|
||||||
|
const payload = messageRenderPayloadById.get(entry.id);
|
||||||
|
const visibleText = buildVisibleMessageText(entry, payload);
|
||||||
|
const requestId = entry.clientRequestId?.trim() || snapshot?.rootRequestId?.trim() || '';
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
key: `response:${entry.id}`,
|
||||||
|
title: visibleText || '응답',
|
||||||
|
description: `${entry.author === 'user' ? '질문 메시지' : '응답'} · ${formatTimeLabel(entry.timestamp)}`,
|
||||||
|
category: 'response',
|
||||||
|
requestId: requestId || undefined,
|
||||||
|
scrollTarget: {
|
||||||
|
type: 'response',
|
||||||
|
value: String(entry.id),
|
||||||
|
},
|
||||||
|
searchText: buildSearchKeywordText(visibleText, entry.author, requestId),
|
||||||
|
});
|
||||||
|
|
||||||
|
(payload?.promptParts ?? []).forEach((prompt, promptIndex) => {
|
||||||
|
results.push({
|
||||||
|
key: `prompt:${entry.id}:${promptIndex}`,
|
||||||
|
title: prompt.title || 'prompt',
|
||||||
|
description: `prompt · ${formatTimeLabel(entry.timestamp)}`,
|
||||||
|
category: 'response',
|
||||||
|
requestId: requestId || undefined,
|
||||||
|
scrollTarget: {
|
||||||
|
type: 'prompt',
|
||||||
|
value: buildSharePromptAnchorKey(entry.id, promptIndex),
|
||||||
|
},
|
||||||
|
searchText: buildSearchKeywordText(buildSharePromptSearchText(prompt), requestId, String(entry.id)),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
(payload?.previewItems ?? []).forEach((item) => {
|
||||||
|
if (!requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
key: `response-resource:${entry.id}:${item.id}`,
|
||||||
|
title: item.label,
|
||||||
|
description: `응답 리소스 · ${item.kind}`,
|
||||||
|
category: 'resource',
|
||||||
|
requestId,
|
||||||
|
resource: {
|
||||||
|
key: `response-resource:${entry.id}:${item.id}`,
|
||||||
|
label: item.label,
|
||||||
|
url: resolveShareScopedResourceUrl(item.url, normalizedToken),
|
||||||
|
kind: item.kind,
|
||||||
|
meta: `${item.kind} resource`,
|
||||||
|
},
|
||||||
|
scrollTarget: {
|
||||||
|
type: 'response',
|
||||||
|
value: String(entry.id),
|
||||||
|
},
|
||||||
|
searchText: buildSearchKeywordText(item.label, item.url, item.kind),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
(snapshot?.activityLogs ?? []).forEach((activity, index) => {
|
||||||
|
const summary = summarizeActivityLogLines(activity.lines ?? []).join(' ');
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
key: `activity:${activity.requestId}:${index}`,
|
||||||
|
title: summary || `활동 로그 ${index + 1}`,
|
||||||
|
description: `활동 로그 · ${activity.requestId}`,
|
||||||
|
category: 'activity',
|
||||||
|
requestId: activity.requestId,
|
||||||
|
scrollTarget: {
|
||||||
|
type: 'activity',
|
||||||
|
value: 'chat-share-activity-panel',
|
||||||
|
},
|
||||||
|
searchText: buildSearchKeywordText(activity.requestId, summary),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return results.filter((item) => !item.scrollTarget || item.scrollTarget.value.trim());
|
||||||
|
}, [messageRenderPayloadById, normalizedToken, snapshot?.activityLogs, snapshot?.rootRequestId, sortedMessages, sortedRequests]);
|
||||||
|
const indexedAppSearchResults = useMemo<ShareSearchIndexedResult[]>(() => {
|
||||||
|
const results: ShareSearchIndexedResult[] = [];
|
||||||
const photoprismLauncher = buildPhotoPrismProgramTarget();
|
const photoprismLauncher = buildPhotoPrismProgramTarget();
|
||||||
|
|
||||||
if (
|
|
||||||
matchesSearchKeyword(
|
|
||||||
keyword,
|
|
||||||
currentShareChatTarget.label,
|
|
||||||
currentShareChatTarget.appId,
|
|
||||||
'공유채팅',
|
|
||||||
'현재 토큰',
|
|
||||||
'현재 공유토큰 열기',
|
|
||||||
...APPS_LAUNCHER_SEARCH_TERMS,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
results.push({
|
results.push({
|
||||||
key: `management-app:${SHARE_CURRENT_CHAT_APP_ID}`,
|
key: `management-app:${SHARE_CURRENT_CHAT_APP_ID}`,
|
||||||
title: currentShareChatTarget.label,
|
title: currentShareChatTarget.label,
|
||||||
@@ -8405,14 +8519,17 @@ export function ChatSharePage() {
|
|||||||
icon: <CommentOutlined />,
|
icon: <CommentOutlined />,
|
||||||
usageBadge: resolveShareAppUsageBadge(appLaunchUsage[SHARE_CURRENT_CHAT_APP_ID]),
|
usageBadge: resolveShareAppUsageBadge(appLaunchUsage[SHARE_CURRENT_CHAT_APP_ID]),
|
||||||
resource: currentShareChatTarget,
|
resource: currentShareChatTarget,
|
||||||
|
searchText: buildSearchKeywordText(
|
||||||
|
currentShareChatTarget.label,
|
||||||
|
currentShareChatTarget.appId,
|
||||||
|
'공유채팅',
|
||||||
|
'현재 토큰',
|
||||||
|
'현재 공유토큰 열기',
|
||||||
|
...APPS_LAUNCHER_SEARCH_TERMS,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
sortedAllowedManagementApps.forEach((item) => {
|
sortedAllowedManagementApps.forEach((item) => {
|
||||||
if (!matchesSearchKeyword(keyword, item.value, item.label, item.description, ...APPS_LAUNCHER_SEARCH_TERMS)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
key: `management-app:${item.value}`,
|
key: `management-app:${item.value}`,
|
||||||
title: item.label,
|
title: item.label,
|
||||||
@@ -8421,23 +8538,11 @@ export function ChatSharePage() {
|
|||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
usageBadge: resolveShareAppUsageBadge(appLaunchUsage[item.value]),
|
usageBadge: resolveShareAppUsageBadge(appLaunchUsage[item.value]),
|
||||||
resource: buildShareManagementProgramTarget(item.value, item.label),
|
resource: buildShareManagementProgramTarget(item.value, item.label),
|
||||||
|
searchText: buildSearchKeywordText(item.value, item.label, item.description, ...APPS_LAUNCHER_SEARCH_TERMS),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
sortedAllowedPlayAppEntries.forEach((entry) => {
|
sortedAllowedPlayAppEntries.forEach((entry) => {
|
||||||
if (
|
|
||||||
!matchesSearchKeyword(
|
|
||||||
keyword,
|
|
||||||
entry.id,
|
|
||||||
entry.name,
|
|
||||||
entry.searchDescription,
|
|
||||||
...APPS_LAUNCHER_SEARCH_TERMS,
|
|
||||||
...(entry.searchKeywords ?? []),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
key: `app:${entry.id}`,
|
key: `app:${entry.id}`,
|
||||||
title: entry.name,
|
title: entry.name,
|
||||||
@@ -8450,148 +8555,32 @@ export function ChatSharePage() {
|
|||||||
entry.id === 'photoprism'
|
entry.id === 'photoprism'
|
||||||
? photoprismLauncher
|
? photoprismLauncher
|
||||||
: buildPlayAppProgramTarget(entry.id, entry.name),
|
: buildPlayAppProgramTarget(entry.id, entry.name),
|
||||||
|
searchText: buildSearchKeywordText(
|
||||||
|
entry.id,
|
||||||
|
entry.name,
|
||||||
|
entry.searchDescription,
|
||||||
|
...APPS_LAUNCHER_SEARCH_TERMS,
|
||||||
|
...(entry.searchKeywords ?? []),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
}, [appLaunchUsage, currentShareChatTarget, selectedAppEnvironment, sortedAllowedManagementApps, sortedAllowedPlayAppEntries]);
|
||||||
|
const searchResults = useMemo<ShareSearchResult[]>(() => {
|
||||||
|
const keyword = normalizeSearchKeyword(searchKeyword);
|
||||||
|
const source = searchPanelMode === 'apps' ? indexedAppSearchResults : indexedContentSearchResults;
|
||||||
|
|
||||||
|
if (searchPanelMode !== 'apps' && !keyword) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keyword) {
|
const filtered = keyword
|
||||||
return results;
|
? source.filter((item) => item.searchText.includes(keyword))
|
||||||
}
|
: source;
|
||||||
|
|
||||||
sortedRequests.forEach((request) => {
|
return filtered.slice(0, 40).map(({ searchText: _searchText, ...result }) => result);
|
||||||
const requestText = buildShareVisibleText(request.userText);
|
}, [indexedAppSearchResults, indexedContentSearchResults, searchKeyword, searchPanelMode]);
|
||||||
|
|
||||||
if (matchesSearchKeyword(keyword, requestText, request.statusMessage, request.requestId)) {
|
|
||||||
results.push({
|
|
||||||
key: `request:${request.requestId}`,
|
|
||||||
title: requestText || '질문',
|
|
||||||
description: `질문 · ${formatTimeLabel(request.createdAt)}`,
|
|
||||||
category: 'request',
|
|
||||||
requestId: request.requestId,
|
|
||||||
scrollTarget: {
|
|
||||||
type: 'request',
|
|
||||||
value: request.requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildSharePreviewItemsFromText(request.userText, normalizedToken).forEach((item) => {
|
|
||||||
if (!matchesSearchKeyword(keyword, item.label, item.url, item.kind)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopedUrl = resolveShareScopedResourceUrl(item.url, normalizedToken);
|
|
||||||
results.push({
|
|
||||||
key: `request-resource:${request.requestId}:${item.id}`,
|
|
||||||
title: item.label,
|
|
||||||
description: `질문 리소스 · ${item.kind}`,
|
|
||||||
category: 'resource',
|
|
||||||
requestId: request.requestId,
|
|
||||||
resource: {
|
|
||||||
key: `request-resource:${request.requestId}:${item.id}`,
|
|
||||||
label: item.label,
|
|
||||||
url: scopedUrl,
|
|
||||||
kind: item.kind,
|
|
||||||
meta: `${item.kind} resource`,
|
|
||||||
},
|
|
||||||
scrollTarget: {
|
|
||||||
type: 'request',
|
|
||||||
value: request.requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
sortedMessages.forEach((entry) => {
|
|
||||||
const payload = messageRenderPayloadById.get(entry.id);
|
|
||||||
const visibleText = buildVisibleMessageText(entry, payload);
|
|
||||||
const requestId = entry.clientRequestId?.trim() || snapshot?.rootRequestId?.trim() || '';
|
|
||||||
|
|
||||||
if (matchesSearchKeyword(keyword, visibleText, entry.author, requestId)) {
|
|
||||||
results.push({
|
|
||||||
key: `response:${entry.id}`,
|
|
||||||
title: visibleText || '응답',
|
|
||||||
description: `${entry.author === 'user' ? '질문 메시지' : '응답'} · ${formatTimeLabel(entry.timestamp)}`,
|
|
||||||
category: 'response',
|
|
||||||
requestId: requestId || undefined,
|
|
||||||
scrollTarget: {
|
|
||||||
type: 'response',
|
|
||||||
value: String(entry.id),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
(payload?.promptParts ?? []).forEach((prompt, promptIndex) => {
|
|
||||||
if (!matchesSearchKeyword(keyword, buildSharePromptSearchText(prompt), requestId, entry.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
key: `prompt:${entry.id}:${promptIndex}`,
|
|
||||||
title: prompt.title || 'prompt',
|
|
||||||
description: `prompt · ${formatTimeLabel(entry.timestamp)}`,
|
|
||||||
category: 'response',
|
|
||||||
requestId: requestId || undefined,
|
|
||||||
scrollTarget: {
|
|
||||||
type: 'prompt',
|
|
||||||
value: buildSharePromptAnchorKey(entry.id, promptIndex),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
(payload?.previewItems ?? []).forEach((item) => {
|
|
||||||
if (!requestId || !matchesSearchKeyword(keyword, item.label, item.url, item.kind)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopedUrl = resolveShareScopedResourceUrl(item.url, normalizedToken);
|
|
||||||
results.push({
|
|
||||||
key: `response-resource:${entry.id}:${item.id}`,
|
|
||||||
title: item.label,
|
|
||||||
description: `응답 리소스 · ${item.kind}`,
|
|
||||||
category: 'resource',
|
|
||||||
requestId,
|
|
||||||
resource: {
|
|
||||||
key: `response-resource:${entry.id}:${item.id}`,
|
|
||||||
label: item.label,
|
|
||||||
url: scopedUrl,
|
|
||||||
kind: item.kind,
|
|
||||||
meta: `${item.kind} resource`,
|
|
||||||
},
|
|
||||||
scrollTarget: {
|
|
||||||
type: 'response',
|
|
||||||
value: String(entry.id),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
(snapshot?.activityLogs ?? []).forEach((activity, index) => {
|
|
||||||
const summary = summarizeActivityLogLines(activity.lines ?? []).join(' ');
|
|
||||||
|
|
||||||
if (!matchesSearchKeyword(keyword, activity.requestId, summary)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
key: `activity:${activity.requestId}:${index}`,
|
|
||||||
title: summary || `활동 로그 ${index + 1}`,
|
|
||||||
description: `활동 로그 · ${activity.requestId}`,
|
|
||||||
category: 'activity',
|
|
||||||
requestId: activity.requestId,
|
|
||||||
scrollTarget: {
|
|
||||||
type: 'activity',
|
|
||||||
value: 'chat-share-activity-panel',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return results
|
|
||||||
.filter((item) => !item.scrollTarget || item.scrollTarget.value.trim())
|
|
||||||
.slice(0, 40);
|
|
||||||
}, [appLaunchUsage, currentShareChatTarget, messageRenderPayloadById, normalizedToken, searchKeyword, searchPanelMode, selectedAppEnvironment, snapshot?.activityLogs, snapshot?.rootRequestId, sortedAllowedManagementApps, sortedAllowedPlayAppEntries, sortedMessages, sortedRequests]);
|
|
||||||
const selectedTokenUsageSetting = shareTokenSetting;
|
const selectedTokenUsageSetting = shareTokenSetting;
|
||||||
const tokenUsageSummaryByPeriod = useMemo(
|
const tokenUsageSummaryByPeriod = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user