feat: consolidate local-first mnote web runtime
This commit is contained in:
@@ -3,16 +3,19 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
copyWorkspaceSourceParams,
|
||||
currentDocumentId,
|
||||
currentRootUri,
|
||||
currentSourceKind,
|
||||
fileTreeIconKindForFileName,
|
||||
getNavigationInFlight,
|
||||
isCodeAttachmentFileName,
|
||||
isNonOfficeAttachmentName,
|
||||
isPdfAttachmentFileName,
|
||||
openCodeEditorAttachment,
|
||||
resolveWorkspaceId,
|
||||
setNavigationInFlight,
|
||||
shouldOpenLocalResourceInNewWindow,
|
||||
uploadedFileSize,
|
||||
} = dependencies;
|
||||
const currentSourceKindFn = typeof currentSourceKind === 'function' ? currentSourceKind : function() { return ''; };
|
||||
|
||||
function inferOnlyOfficeFileType(fileName, mimeType) {
|
||||
var name = String(fileName || '').trim().toLowerCase();
|
||||
@@ -29,6 +32,9 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
}
|
||||
|
||||
function buildOnlyOfficeOpenUrl(input) {
|
||||
if (shouldUseOfficePreview(input.fileType, input.mode)) {
|
||||
return buildOfficePreviewOpenUrl(input);
|
||||
}
|
||||
var target = new URL('/onlyoffice', window.location.origin);
|
||||
var fileUrl = String(input.fileUrl || '').trim();
|
||||
if (fileUrl) {
|
||||
@@ -46,11 +52,49 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
return target.toString();
|
||||
}
|
||||
|
||||
function shouldUseOfficePreview(fileType, mode) {
|
||||
var normalizedMode = String(mode || 'view').trim().toLowerCase();
|
||||
if (normalizedMode === 'edit') return false;
|
||||
var ext = String(fileType || '').trim().toLowerCase();
|
||||
return ['docx', 'xlsx', 'xls', 'csv', 'pptx'].indexOf(ext) >= 0;
|
||||
}
|
||||
|
||||
function buildOfficePreviewOpenUrl(input) {
|
||||
var _rto_ = window.__mnoteResourceOpenRuntime;
|
||||
if (_rto_ && typeof _rto_.buildOfficePreviewOpenUrl === 'function') {
|
||||
return _rto_.buildOfficePreviewOpenUrl(input);
|
||||
}
|
||||
var target = new URL('/office-preview', window.location.origin);
|
||||
var workspacePath = input && input.workspacePath && typeof input.workspacePath === 'object' ? input.workspacePath : {};
|
||||
var fileUrl = String(input.fileUrl || '').trim();
|
||||
if (fileUrl) {
|
||||
try {
|
||||
fileUrl = new URL(fileUrl, window.location.origin).toString();
|
||||
} catch (_error) {}
|
||||
}
|
||||
target.searchParams.set('fileUrl', fileUrl);
|
||||
target.searchParams.set('fileName', input.fileName || '未命名资源');
|
||||
target.searchParams.set('fileType', input.fileType || 'docx');
|
||||
if (input.assetId) target.searchParams.set('assetId', input.assetId);
|
||||
if (input.documentId) target.searchParams.set('documentId', input.documentId);
|
||||
if (input.userId) target.searchParams.set('userId', input.userId);
|
||||
var workspaceId = String(input.workspaceId || workspacePath.workspaceId || '').trim();
|
||||
var sourceKind = String(input.sourceKind || workspacePath.sourceKind || currentSourceKindFn() || '').trim();
|
||||
var rootUri = String(input.rootUri || workspacePath.rootUri || currentRootUri() || '').trim();
|
||||
if (workspaceId) target.searchParams.set('workspaceId', workspaceId);
|
||||
if (sourceKind) target.searchParams.set('sourceKind', sourceKind);
|
||||
if (rootUri) target.searchParams.set('rootUri', rootUri);
|
||||
return target.toString();
|
||||
}
|
||||
|
||||
function buildOnlyOfficeOpenPath(input) {
|
||||
var _rto_ = window.__mnoteResourceOpenRuntime;
|
||||
if (_rto_ && typeof _rto_.buildOnlyOfficeOpenPath === 'function') {
|
||||
return _rto_.buildOnlyOfficeOpenPath(input);
|
||||
}
|
||||
if (shouldUseOfficePreview(input.fileType, input.mode)) {
|
||||
return buildOfficePreviewOpenUrl(input);
|
||||
}
|
||||
var params = new URLSearchParams();
|
||||
params.set('fileUrl', input.fileUrl || '');
|
||||
params.set('fileName', input.fileName || '未命名资源');
|
||||
@@ -77,12 +121,17 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
assetId: assetId || ('local-file:' + relativePath),
|
||||
documentId: documentId || currentDocumentId() || '',
|
||||
userId: '',
|
||||
mode: mode || 'view'
|
||||
mode: mode || 'view',
|
||||
workspaceId: resolveWorkspaceId(document.body) || '',
|
||||
sourceKind: currentSourceKindFn() || 'local_folder',
|
||||
rootUri: currentRootUri() || ''
|
||||
});
|
||||
}
|
||||
|
||||
async function openLocalOfficeFileInActiveTab(detail, mode) {
|
||||
var localFilePath = localFilePathFromAssetId(detail.assetId);
|
||||
var workspacePath = workspacePathFromDetail(detail);
|
||||
if (!localFilePath && workspacePath) localFilePath = String(workspacePath.relativePath || workspacePath.localRelativePath || '').trim();
|
||||
if (!localFilePath) return false;
|
||||
var localFileName = localFilePath.split('/').pop() || localFilePath;
|
||||
var localOfficeUrl = buildLocalOnlyOfficeOpenUrl(localFilePath, localFileName, String(detail.documentId || currentDocumentId() || '').trim(), detail.assetId, mode || 'view');
|
||||
@@ -96,6 +145,7 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
workspaceId: detail.workspaceId || resolveWorkspaceId(document.body) || '',
|
||||
href: buildLocalFileOpenUrl(localFilePath, false),
|
||||
officeUrl: localOfficeUrl,
|
||||
workspacePath: workspacePath,
|
||||
paneRole: detail.paneRole || 'primary'
|
||||
});
|
||||
if (!opened) window.open(localOfficeUrl, '_blank', 'noopener,noreferrer');
|
||||
@@ -168,6 +218,36 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
return value.indexOf('local-file:') === 0 ? value.slice('local-file:'.length) : value.indexOf('local:asset:') === 0 ? value.slice('local:asset:'.length) : '';
|
||||
}
|
||||
|
||||
function workspacePathFromDetail(detail) {
|
||||
var workspacePath = detail && detail.workspacePath && typeof detail.workspacePath === 'object' ? detail.workspacePath : null;
|
||||
return workspacePath && workspacePath.schema === 'mnote.workspace_path.v1' ? workspacePath : null;
|
||||
}
|
||||
|
||||
function workspacePathObjectIdentity(workspacePath) {
|
||||
var objectIdentity = workspacePath && workspacePath.objectIdentity && typeof workspacePath.objectIdentity === 'object'
|
||||
? workspacePath.objectIdentity
|
||||
: null;
|
||||
return objectIdentity || {};
|
||||
}
|
||||
|
||||
function resourceObjectIdentityFromWorkspacePath(input) {
|
||||
input = input || {};
|
||||
var workspacePath = input.workspacePath && typeof input.workspacePath === 'object' ? input.workspacePath : null;
|
||||
var structuredIdentity = workspacePathObjectIdentity(workspacePath);
|
||||
var objectKind = String(input.objectKind || workspacePath && (workspacePath.objectKind || workspacePath.resourceKind) || structuredIdentity.objectKind || '').trim();
|
||||
var documentId = String(input.documentId || workspacePath && workspacePath.documentId || structuredIdentity.documentId || currentDocumentId() || '').trim();
|
||||
var assetId = String(input.assetId || workspacePath && workspacePath.assetId || structuredIdentity.assetId || '').trim();
|
||||
var relativePath = String(input.path || workspacePath && (workspacePath.relativePath || workspacePath.localRelativePath) || '').trim();
|
||||
var rootUri = String(input.rootUri || workspacePath && workspacePath.rootUri || currentRootUri() || '').trim();
|
||||
var fallback = String(input.fallback || '').trim();
|
||||
if (objectKind === 'mindmap' && assetId) return 'resource:mindmap:' + documentId + ':' + assetId;
|
||||
if ((objectKind === 'only_office' || objectKind === 'office') && assetId) return 'resource:onlyoffice:' + documentId + ':' + assetId;
|
||||
if (objectKind === 'pdf' && assetId) return 'resource:pdf:' + documentId + ':' + assetId;
|
||||
if (relativePath && rootUri) return 'resource:file:' + rootUri + ':' + relativePath;
|
||||
if (assetId && documentId) return 'resource:' + (objectKind || 'attachment') + ':' + documentId + ':' + assetId;
|
||||
return fallback || assetId || '';
|
||||
}
|
||||
|
||||
function buildLocalFileOpenUrl(relativePath, download) {
|
||||
var _rto_ = window.__mnoteResourceOpenRuntime;
|
||||
if (_rto_ && typeof _rto_.buildLocalFileOpenUrl === 'function') {
|
||||
@@ -182,6 +262,19 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function buildPdfPreviewOpenUrl(fileUrl, fileName) {
|
||||
var _rto_ = window.__mnoteResourceOpenRuntime;
|
||||
if (_rto_ && typeof _rto_.buildPdfPreviewOpenUrl === 'function') {
|
||||
return _rto_.buildPdfPreviewOpenUrl(fileUrl, fileName);
|
||||
}
|
||||
var rawUrl = String(fileUrl || '').trim();
|
||||
if (!rawUrl) return '';
|
||||
var url = new URL('/pdf-preview', window.location.origin);
|
||||
url.searchParams.set('fileUrl', rawUrl);
|
||||
if (fileName) url.searchParams.set('fileName', fileName);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async function openLocalResourceInActiveTab(input) {
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab !== 'function') return false;
|
||||
var relativePath = String(input && input.path || '').trim();
|
||||
@@ -189,7 +282,15 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
if (!relativePath || !rootUri) return false;
|
||||
var title = String(input && input.title || '').trim() || relativePath.split('/').pop() || relativePath;
|
||||
var kind = String(input && input.kind || fileTreeIconKindForFileName(title) || '').trim();
|
||||
var objectIdentity = 'resource:file:' + rootUri + ':' + relativePath;
|
||||
var workspacePath = input && input.workspacePath && typeof input.workspacePath === 'object' ? input.workspacePath : null;
|
||||
var objectIdentity = resourceObjectIdentityFromWorkspacePath({
|
||||
workspacePath: workspacePath,
|
||||
objectKind: kind,
|
||||
documentId: input && input.documentId,
|
||||
assetId: input && input.assetId,
|
||||
path: relativePath,
|
||||
rootUri: rootUri
|
||||
});
|
||||
return await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: objectIdentity,
|
||||
assetId: String(input && input.assetId || '').trim() || 'local-file:' + relativePath,
|
||||
@@ -202,6 +303,7 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
officeUrl: String(input && input.officeUrl || '').trim(),
|
||||
documentId: String(input && input.documentId || currentDocumentId() || '').trim(),
|
||||
workspaceId: String(input && input.workspaceId || resolveWorkspaceId(document.body) || '').trim(),
|
||||
workspacePath: workspacePath,
|
||||
paneRole: String(input && input.paneRole || 'primary').trim() || 'primary'
|
||||
});
|
||||
}
|
||||
@@ -240,15 +342,26 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
var forceEditMode = openTarget === 'edit-mode';
|
||||
if (openTarget === 'side') {
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceAsSideTarget === 'function') {
|
||||
var sideWorkspacePath = workspacePathFromDetail(detail);
|
||||
var sideLocalFilePath = localFilePathFromAssetId(assetId);
|
||||
if (!sideLocalFilePath && sideWorkspacePath) sideLocalFilePath = String(sideWorkspacePath.relativePath || sideWorkspacePath.localRelativePath || '').trim();
|
||||
var sideRootUri = String(detail.rootUri || currentRootUri() || '').trim();
|
||||
var sideFileName = String(detail.title || detail.fileName || (sideLocalFilePath ? sideLocalFilePath.split('/').pop() : '') || assetId || '').trim();
|
||||
var sideKind = String(detail.iconKind || detail.assetType || fileTreeIconKindForFileName(sideFileName) || 'file').trim();
|
||||
var sideHref = sideLocalFilePath ? buildLocalFileOpenUrl(sideLocalFilePath, false) : '';
|
||||
var sideOfficeUrl = sideLocalFilePath ? buildLocalOnlyOfficeOpenUrl(sideLocalFilePath, sideFileName, String(detail.documentId || currentDocumentId() || '').trim(), assetId, 'view') : '';
|
||||
if (sideOfficeUrl) sideKind = 'office';
|
||||
if (!sideOfficeUrl && isPdfAttachmentFileName(sideFileName)) sideHref = buildPdfPreviewOpenUrl(sideHref, sideFileName);
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceAsSideTarget({
|
||||
objectIdentity: sideLocalFilePath && sideRootUri ? 'resource:file:' + sideRootUri + ':' + sideLocalFilePath : String(detail.objectIdentity || detail.assetId || ''),
|
||||
objectIdentity: resourceObjectIdentityFromWorkspacePath({
|
||||
workspacePath: sideWorkspacePath,
|
||||
objectKind: sideKind,
|
||||
documentId: detail.documentId,
|
||||
assetId: assetId,
|
||||
path: sideLocalFilePath,
|
||||
rootUri: sideRootUri,
|
||||
fallback: String(detail.objectIdentity || detail.assetId || '')
|
||||
}),
|
||||
assetId: assetId,
|
||||
title: sideFileName,
|
||||
fileName: sideFileName,
|
||||
@@ -258,12 +371,15 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
href: sideHref,
|
||||
officeUrl: sideOfficeUrl,
|
||||
documentId: String(detail.documentId || currentDocumentId() || ''),
|
||||
workspaceId: String(detail.workspaceId || resolveWorkspaceId(document.body) || '')
|
||||
workspaceId: String(detail.workspaceId || resolveWorkspaceId(document.body) || ''),
|
||||
workspacePath: sideWorkspacePath
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
var detailWorkspacePath = workspacePathFromDetail(detail);
|
||||
var localFilePath = localFilePathFromAssetId(assetId);
|
||||
if (!localFilePath && detailWorkspacePath) localFilePath = String(detailWorkspacePath.relativePath || detailWorkspacePath.localRelativePath || '').trim();
|
||||
if (localFilePath) {
|
||||
var localFileName = localFilePath.split('/').pop() || localFilePath;
|
||||
if (isMindmapAssetDetail(detail) || String(detail && detail.assetType || '').trim() === 'mindmap') {
|
||||
@@ -271,14 +387,21 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab');
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:mindmap:' + String(detail && detail.documentId || currentDocumentId() || '').trim() + ':' + assetId,
|
||||
objectIdentity: resourceObjectIdentityFromWorkspacePath({
|
||||
workspacePath: detailWorkspacePath,
|
||||
objectKind: 'mindmap',
|
||||
documentId: detail && detail.documentId,
|
||||
assetId: assetId,
|
||||
path: localFilePath
|
||||
}),
|
||||
assetId: assetId,
|
||||
mindmapId: assetId,
|
||||
title: String(detail && detail.title || localFileName || '思维导图').trim(),
|
||||
fileName: localFileName,
|
||||
kind: 'mindmap',
|
||||
documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(),
|
||||
workspaceId: String(detail.workspaceId || '').trim()
|
||||
workspaceId: String(detail.workspaceId || '').trim(),
|
||||
workspacePath: detailWorkspacePath
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -297,13 +420,15 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
assetId: assetId,
|
||||
documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(),
|
||||
workspaceId: String(detail.workspaceId || '').trim(),
|
||||
officeUrl: localOfficeUrl
|
||||
officeUrl: localOfficeUrl,
|
||||
workspacePath: detailWorkspacePath
|
||||
})) return;
|
||||
window.open(localOfficeUrl, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
var localFileUrl = buildLocalFileOpenUrl(localFilePath, false);
|
||||
if (localFileUrl) {
|
||||
var localOpenUrl = isPdfAttachmentFileName(localFileName) ? buildPdfPreviewOpenUrl(localFileUrl, localFileName) : localFileUrl;
|
||||
var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName);
|
||||
if (!shouldOpenInNewWindow && await openLocalResourceInActiveTab({
|
||||
path: localFilePath,
|
||||
@@ -312,9 +437,10 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
assetId: assetId,
|
||||
documentId: String(detail && detail.documentId || currentDocumentId() || '').trim(),
|
||||
workspaceId: String(detail.workspaceId || '').trim(),
|
||||
href: localFileUrl
|
||||
href: localOpenUrl,
|
||||
workspacePath: detailWorkspacePath
|
||||
})) return;
|
||||
window.open(localFileUrl, '_blank', 'noopener,noreferrer');
|
||||
window.open(localOpenUrl, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -324,14 +450,20 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-resource-tab');
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-id', assetId);
|
||||
void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:mindmap:' + documentId + ':' + assetId,
|
||||
objectIdentity: resourceObjectIdentityFromWorkspacePath({
|
||||
workspacePath: detailWorkspacePath,
|
||||
objectKind: 'mindmap',
|
||||
documentId: documentId,
|
||||
assetId: assetId
|
||||
}),
|
||||
assetId: assetId,
|
||||
mindmapId: assetId,
|
||||
title: String(detail.title || '思维导图').trim(),
|
||||
fileName: String(detail.title || '思维导图').trim(),
|
||||
kind: 'mindmap',
|
||||
documentId: documentId,
|
||||
workspaceId: String(detail.workspaceId || '').trim()
|
||||
workspaceId: String(detail.workspaceId || '').trim(),
|
||||
workspacePath: detailWorkspacePath
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -367,20 +499,50 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
});
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
||||
var didOpen = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: 'resource:onlyoffice:' + String(asset.document_id || detail.documentId || '').trim() + ':' + assetId,
|
||||
objectIdentity: resourceObjectIdentityFromWorkspacePath({
|
||||
workspacePath: detailWorkspacePath,
|
||||
objectKind: 'only_office',
|
||||
documentId: String(asset.document_id || detail.documentId || '').trim(),
|
||||
assetId: assetId
|
||||
}),
|
||||
assetId: assetId,
|
||||
title: fileName,
|
||||
fileName: fileName,
|
||||
kind: 'office',
|
||||
officeUrl: officeUrl,
|
||||
documentId: String(asset.document_id || detail.documentId || '').trim(),
|
||||
workspaceId: String(detail.workspaceId || '').trim()
|
||||
workspaceId: String(detail.workspaceId || '').trim(),
|
||||
workspacePath: detailWorkspacePath
|
||||
});
|
||||
if (didOpen) return;
|
||||
}
|
||||
window.open(officeUrl, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
if (isPdfAttachmentFileName(fileName)) {
|
||||
var pdfPreviewUrl = buildPdfPreviewOpenUrl(fileUrl, fileName);
|
||||
if (typeof window.__mnoteDocumentPaneRuntime?.openResourceInActiveTab === 'function') {
|
||||
var didOpenPdf = await window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({
|
||||
objectIdentity: resourceObjectIdentityFromWorkspacePath({
|
||||
workspacePath: detailWorkspacePath,
|
||||
objectKind: 'pdf',
|
||||
documentId: String(asset.document_id || detail.documentId || '').trim(),
|
||||
assetId: assetId
|
||||
}),
|
||||
assetId: assetId,
|
||||
title: fileName,
|
||||
fileName: fileName,
|
||||
kind: 'pdf',
|
||||
href: pdfPreviewUrl,
|
||||
documentId: String(asset.document_id || detail.documentId || '').trim(),
|
||||
workspaceId: String(detail.workspaceId || '').trim(),
|
||||
workspacePath: detailWorkspacePath
|
||||
});
|
||||
if (didOpenPdf) return;
|
||||
}
|
||||
window.open(pdfPreviewUrl || fileUrl, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
if (isCodeAttachmentFileName(fileName) && String(asset.document_id || detail.documentId || '').trim() === currentDocumentId()) {
|
||||
await openCodeEditorAttachment({
|
||||
href: fileUrl,
|
||||
@@ -407,8 +569,10 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
buildLocalFileOpenUrl,
|
||||
buildLocalOnlyOfficeOpenUrl,
|
||||
buildMindmapOpenPath,
|
||||
buildOfficePreviewOpenUrl,
|
||||
buildOnlyOfficeOpenPath,
|
||||
buildOnlyOfficeOpenUrl,
|
||||
buildPdfPreviewOpenUrl,
|
||||
fetchCurrentOnlyOfficeUserId,
|
||||
inferOnlyOfficeFileType,
|
||||
isMindmapAssetDetail,
|
||||
@@ -418,5 +582,7 @@ export const createSidebarFileTreeOpenRuntime = (dependencies = {}) => {
|
||||
openLocalOfficeFileInActiveTab,
|
||||
openLocalResourceInActiveTab,
|
||||
readFileTreeObjectIdentity,
|
||||
resourceObjectIdentityFromWorkspacePath,
|
||||
workspacePathFromDetail,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user