mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
fix: revert document content field rename (#6955)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
d1bf10b1d9
commit
dea31012b5
@ -326,7 +326,7 @@ async function createDBPageWithAttachments (
|
|||||||
documentMetaMap?: Map<string, DocumentMetadata>
|
documentMetaMap?: Map<string, DocumentMetadata>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const pageId = docMeta.id as Ref<Document>
|
const pageId = docMeta.id as Ref<Document>
|
||||||
const collabId = makeCollaborativeDoc(pageId, 'description')
|
const collabId = makeCollaborativeDoc(pageId, 'content')
|
||||||
|
|
||||||
const parentId = parentMeta !== undefined ? (parentMeta.id as Ref<Document>) : document.ids.NoParent
|
const parentId = parentMeta !== undefined ? (parentMeta.id as Ref<Document>) : document.ids.NoParent
|
||||||
|
|
||||||
@ -335,7 +335,7 @@ async function createDBPageWithAttachments (
|
|||||||
|
|
||||||
const object: Data<Document> = {
|
const object: Data<Document> = {
|
||||||
title: docMeta.name,
|
title: docMeta.name,
|
||||||
description: collabId,
|
content: collabId,
|
||||||
parent: parentId,
|
parent: parentId,
|
||||||
attachments: 0,
|
attachments: 0,
|
||||||
embeddings: 0,
|
embeddings: 0,
|
||||||
@ -482,7 +482,7 @@ async function importPageDocument (
|
|||||||
|
|
||||||
const attachedData: Data<Document> = {
|
const attachedData: Data<Document> = {
|
||||||
title: docMeta.name,
|
title: docMeta.name,
|
||||||
description: collabId,
|
content: collabId,
|
||||||
parent,
|
parent,
|
||||||
attachments: 0,
|
attachments: 0,
|
||||||
embeddings: 0,
|
embeddings: 0,
|
||||||
|
@ -41,7 +41,7 @@ import {
|
|||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import attachment, { TAttachment } from '@hcengineering/model-attachment'
|
import attachment, { TAttachment } from '@hcengineering/model-attachment'
|
||||||
import chunter from '@hcengineering/model-chunter'
|
import chunter from '@hcengineering/model-chunter'
|
||||||
import core, { TCard, TTypedSpace } from '@hcengineering/model-core'
|
import core, { TDoc, TTypedSpace } from '@hcengineering/model-core'
|
||||||
import { createPublicLinkAction } from '@hcengineering/model-guest'
|
import { createPublicLinkAction } from '@hcengineering/model-guest'
|
||||||
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
|
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
|
||||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||||
@ -69,24 +69,24 @@ export class TDocumentEmbedding extends TAttachment implements DocumentEmbedding
|
|||||||
declare attachedToClass: Ref<Class<Document>>
|
declare attachedToClass: Ref<Class<Document>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(document.class.Document, core.class.Card, DOMAIN_DOCUMENT)
|
@Model(document.class.Document, core.class.Doc, DOMAIN_DOCUMENT)
|
||||||
@UX(document.string.Document, document.icon.Document, undefined, 'name', undefined, document.string.Documents)
|
@UX(document.string.Document, document.icon.Document, undefined, 'name', undefined, document.string.Documents)
|
||||||
export class TDocument extends TCard implements Document, Todoable {
|
export class TDocument extends TDoc implements Document, Todoable {
|
||||||
|
@Prop(TypeString(), document.string.Name)
|
||||||
|
@Index(IndexKind.FullText)
|
||||||
|
title!: string
|
||||||
|
|
||||||
|
@Prop(TypeCollaborativeDoc(), document.string.Document)
|
||||||
|
content!: CollaborativeDoc
|
||||||
|
|
||||||
@Prop(TypeRef(document.class.Document), document.string.ParentDocument)
|
@Prop(TypeRef(document.class.Document), document.string.ParentDocument)
|
||||||
declare parent: Ref<Document>
|
parent!: Ref<Document>
|
||||||
|
|
||||||
@Prop(TypeRef(core.class.Space), core.string.Space)
|
@Prop(TypeRef(core.class.Space), core.string.Space)
|
||||||
@Index(IndexKind.Indexed)
|
@Index(IndexKind.Indexed)
|
||||||
@Hidden()
|
@Hidden()
|
||||||
declare space: Ref<Teamspace>
|
declare space: Ref<Teamspace>
|
||||||
|
|
||||||
@Prop(TypeString(), document.string.Name)
|
|
||||||
@Index(IndexKind.FullText)
|
|
||||||
declare title: string
|
|
||||||
|
|
||||||
@Prop(TypeCollaborativeDoc(), document.string.Document)
|
|
||||||
declare description: CollaborativeDoc
|
|
||||||
|
|
||||||
@Prop(TypeRef(core.class.Account), document.string.LockedBy)
|
@Prop(TypeRef(core.class.Account), document.string.LockedBy)
|
||||||
@Hidden()
|
@Hidden()
|
||||||
lockedBy?: Ref<Account>
|
lockedBy?: Ref<Account>
|
||||||
@ -125,12 +125,9 @@ export class TDocument extends TCard implements Document, Todoable {
|
|||||||
rank!: Rank
|
rank!: Rank
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(document.class.DocumentSnapshot, core.class.Card, DOMAIN_DOCUMENT)
|
@Model(document.class.DocumentSnapshot, core.class.Doc, DOMAIN_DOCUMENT)
|
||||||
@UX(document.string.Version)
|
@UX(document.string.Version)
|
||||||
export class TDocumentSnapshot extends TCard implements DocumentSnapshot {
|
export class TDocumentSnapshot extends TDoc implements DocumentSnapshot {
|
||||||
@Prop(TypeRef(document.class.Document), document.string.ParentDocument)
|
|
||||||
declare parent: Ref<Document>
|
|
||||||
|
|
||||||
@Prop(TypeRef(core.class.Space), core.string.Space)
|
@Prop(TypeRef(core.class.Space), core.string.Space)
|
||||||
@Index(IndexKind.Indexed)
|
@Index(IndexKind.Indexed)
|
||||||
@Hidden()
|
@Hidden()
|
||||||
@ -138,11 +135,13 @@ export class TDocumentSnapshot extends TCard implements DocumentSnapshot {
|
|||||||
|
|
||||||
@Prop(TypeString(), document.string.Name)
|
@Prop(TypeString(), document.string.Name)
|
||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
declare title: string
|
title!: string
|
||||||
|
|
||||||
@Prop(TypeCollaborativeDocVersion(), document.string.Document)
|
@Prop(TypeCollaborativeDocVersion(), document.string.Document)
|
||||||
@Hidden()
|
content!: CollaborativeDoc
|
||||||
declare description: CollaborativeDoc
|
|
||||||
|
@Prop(TypeRef(document.class.Document), document.string.ParentDocument)
|
||||||
|
parent!: Ref<Document>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(document.class.SavedDocument, preference.class.Preference)
|
@Model(document.class.SavedDocument, preference.class.Preference)
|
||||||
@ -444,7 +443,7 @@ function defineDocument (builder: Builder): void {
|
|||||||
allowedForAuthor: false,
|
allowedForAuthor: false,
|
||||||
label: document.string.Document,
|
label: document.string.Document,
|
||||||
group: document.ids.DocumentNotificationGroup,
|
group: document.ids.DocumentNotificationGroup,
|
||||||
field: 'description',
|
field: 'content',
|
||||||
txClasses: [core.class.TxUpdateDoc],
|
txClasses: [core.class.TxUpdateDoc],
|
||||||
objectClass: document.class.Document,
|
objectClass: document.class.Document,
|
||||||
defaultEnabled: false,
|
defaultEnabled: false,
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { DOMAIN_TX, MeasureMetricsContext, SortingOrder } from '@hcengineering/core'
|
import { type CollaborativeDoc, DOMAIN_TX, MeasureMetricsContext, SortingOrder } from '@hcengineering/core'
|
||||||
import { type DocumentSnapshot, type Document, type Teamspace } from '@hcengineering/document'
|
import { type DocumentSnapshot, type Document, type Teamspace } from '@hcengineering/document'
|
||||||
import {
|
import {
|
||||||
tryMigrate,
|
tryMigrate,
|
||||||
@ -111,7 +111,7 @@ async function migrateContentField (client: MigrationClient): Promise<void> {
|
|||||||
|
|
||||||
for (const document of documents) {
|
for (const document of documents) {
|
||||||
try {
|
try {
|
||||||
const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.description, ctx)
|
const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.content, ctx)
|
||||||
if (ydoc === undefined) {
|
if (ydoc === undefined) {
|
||||||
ctx.error('document content not found', { document: document.title })
|
ctx.error('document content not found', { document: document.title })
|
||||||
continue
|
continue
|
||||||
@ -123,7 +123,7 @@ async function migrateContentField (client: MigrationClient): Promise<void> {
|
|||||||
|
|
||||||
yDocCopyXmlField(ydoc, '', 'content')
|
yDocCopyXmlField(ydoc, '', 'content')
|
||||||
|
|
||||||
await saveCollaborativeDoc(storage, client.workspaceId, document.description, ydoc, ctx)
|
await saveCollaborativeDoc(storage, client.workspaceId, document.content, ydoc, ctx)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.error('error document content migration', { error: err, document: document.title })
|
ctx.error('error document content migration', { error: err, document: document.title })
|
||||||
}
|
}
|
||||||
@ -202,6 +202,68 @@ async function renameFields (client: MigrationClient): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function renameFieldsRevert (client: MigrationClient): Promise<void> {
|
||||||
|
const ctx = new MeasureMetricsContext('renameFieldsRevert', {})
|
||||||
|
const storage = client.storageAdapter
|
||||||
|
|
||||||
|
type ExDocument = Document & {
|
||||||
|
description: CollaborativeDoc
|
||||||
|
}
|
||||||
|
|
||||||
|
const documents = await client.find<ExDocument>(DOMAIN_DOCUMENT, {
|
||||||
|
_class: document.class.Document,
|
||||||
|
description: { $exists: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const document of documents) {
|
||||||
|
await client.update(
|
||||||
|
DOMAIN_DOCUMENT,
|
||||||
|
{ _id: document._id },
|
||||||
|
{
|
||||||
|
$rename: {
|
||||||
|
description: 'content'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (document.description.includes('%description:')) {
|
||||||
|
try {
|
||||||
|
const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.description, ctx)
|
||||||
|
if (ydoc === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ydoc.share.has('description') || ydoc.share.has('content')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
yDocCopyXmlField(ydoc, 'description', 'content')
|
||||||
|
|
||||||
|
await saveCollaborativeDoc(storage, client.workspaceId, document.description, ydoc, ctx)
|
||||||
|
} catch (err) {
|
||||||
|
ctx.error('error document content migration', { error: err, document: document.title })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshots = await client.find<DocumentSnapshot>(DOMAIN_DOCUMENT, {
|
||||||
|
_class: document.class.DocumentSnapshot,
|
||||||
|
description: { $exists: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const snapshot of snapshots) {
|
||||||
|
await client.update(
|
||||||
|
DOMAIN_DOCUMENT,
|
||||||
|
{ _id: snapshot._id },
|
||||||
|
{
|
||||||
|
$rename: {
|
||||||
|
description: 'content'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const documentOperation: MigrateOperation = {
|
export const documentOperation: MigrateOperation = {
|
||||||
async migrate (client: MigrationClient): Promise<void> {
|
async migrate (client: MigrationClient): Promise<void> {
|
||||||
await tryMigrate(client, documentId, [
|
await tryMigrate(client, documentId, [
|
||||||
@ -234,6 +296,10 @@ export const documentOperation: MigrateOperation = {
|
|||||||
func: async (client: MigrationClient): Promise<void> => {
|
func: async (client: MigrationClient): Promise<void> => {
|
||||||
await client.update(DOMAIN_DOCUMENT, { '%hash%': { $exists: true } }, { $set: { '%hash%': null } })
|
await client.update(DOMAIN_DOCUMENT, { '%hash%': { $exists: true } }, { $set: { '%hash%': null } })
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: 'renameFieldsRevert',
|
||||||
|
func: renameFieldsRevert
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
import { type Builder } from '@hcengineering/model'
|
import { type Builder } from '@hcengineering/model'
|
||||||
|
|
||||||
import core from '@hcengineering/core'
|
import core, { type Class, type Doc } from '@hcengineering/core'
|
||||||
import document from '@hcengineering/document'
|
import document from '@hcengineering/document'
|
||||||
import serverCore from '@hcengineering/server-core'
|
import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core'
|
||||||
import serverDocument from '@hcengineering/server-document'
|
import serverDocument from '@hcengineering/server-document'
|
||||||
import serverNotification from '@hcengineering/server-notification'
|
import serverNotification from '@hcengineering/server-notification'
|
||||||
import serverView from '@hcengineering/server-view'
|
import serverView from '@hcengineering/server-view'
|
||||||
@ -36,4 +36,13 @@ export function createModel (builder: Builder): void {
|
|||||||
title: 'title'
|
title: 'title'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||||
|
document.class.Document,
|
||||||
|
core.class.Class,
|
||||||
|
serverCore.mixin.ObjectDDParticipant,
|
||||||
|
{
|
||||||
|
collectDocs: serverDocument.function.FindChildDocuments
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -56,12 +56,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CollaboratorEditor
|
<CollaboratorEditor
|
||||||
collaborativeDoc={object.description}
|
collaborativeDoc={object.content}
|
||||||
objectClass={object._class}
|
objectClass={object._class}
|
||||||
objectId={object._id}
|
objectId={object._id}
|
||||||
objectSpace={object.space}
|
objectSpace={object.space}
|
||||||
objectAttr="description"
|
objectAttr="content"
|
||||||
field="description"
|
field="content"
|
||||||
{user}
|
{user}
|
||||||
{userComponent}
|
{userComponent}
|
||||||
{focusIndex}
|
{focusIndex}
|
||||||
|
@ -79,7 +79,7 @@ export async function createEmptyDocument (
|
|||||||
|
|
||||||
const object: Data<Document> = {
|
const object: Data<Document> = {
|
||||||
title,
|
title,
|
||||||
description: makeCollaborativeDoc(id, 'description'),
|
content: makeCollaborativeDoc(id, 'content'),
|
||||||
attachments: 0,
|
attachments: 0,
|
||||||
embeddings: 0,
|
embeddings: 0,
|
||||||
labels: 0,
|
labels: 0,
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { Attachment } from '@hcengineering/attachment'
|
import { Attachment } from '@hcengineering/attachment'
|
||||||
import { Account, Card, Class, CollaborativeDoc, Rank, Ref, TypedSpace } from '@hcengineering/core'
|
import { Account, Class, CollaborativeDoc, Doc, Rank, Ref, TypedSpace } from '@hcengineering/core'
|
||||||
import { Preference } from '@hcengineering/preference'
|
import { Preference } from '@hcengineering/preference'
|
||||||
import { IconProps } from '@hcengineering/view'
|
import { IconProps } from '@hcengineering/view'
|
||||||
|
|
||||||
@ -22,11 +22,10 @@ import { IconProps } from '@hcengineering/view'
|
|||||||
export interface Teamspace extends TypedSpace, IconProps {}
|
export interface Teamspace extends TypedSpace, IconProps {}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface Document extends Card, IconProps {
|
export interface Document extends Doc, IconProps {
|
||||||
|
title: string
|
||||||
|
content: CollaborativeDoc
|
||||||
parent: Ref<Document>
|
parent: Ref<Document>
|
||||||
|
|
||||||
description: CollaborativeDoc
|
|
||||||
|
|
||||||
space: Ref<Teamspace>
|
space: Ref<Teamspace>
|
||||||
|
|
||||||
lockedBy?: Ref<Account> | null
|
lockedBy?: Ref<Account> | null
|
||||||
@ -42,10 +41,10 @@ export interface Document extends Card, IconProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export interface DocumentSnapshot extends Card {
|
export interface DocumentSnapshot extends Doc {
|
||||||
parent: Ref<Document>
|
|
||||||
title: string
|
title: string
|
||||||
description: CollaborativeDoc
|
content: CollaborativeDoc
|
||||||
|
parent: Ref<Document>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -365,15 +365,7 @@
|
|||||||
remoteProvider.awareness?.setLocalStateField('lastUpdate', Date.now())
|
remoteProvider.awareness?.setLocalStateField('lastUpdate', Date.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseField (collaborativeDoc: CollaborativeDoc): string | undefined {
|
|
||||||
if (collaborativeDoc === undefined) return undefined
|
|
||||||
const _id = collaborativeDoc.split(':')
|
|
||||||
if (_id === undefined) return undefined
|
|
||||||
return _id[0]?.split('%')?.[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const _field = parseField(collaborativeDoc) ?? field
|
|
||||||
await ph
|
await ph
|
||||||
|
|
||||||
editor = new Editor({
|
editor = new Editor({
|
||||||
@ -406,7 +398,7 @@
|
|||||||
Placeholder.configure({ placeholder: placeHolderStr }),
|
Placeholder.configure({ placeholder: placeHolderStr }),
|
||||||
Collaboration.configure({
|
Collaboration.configure({
|
||||||
document: ydoc,
|
document: ydoc,
|
||||||
field: _field
|
field
|
||||||
}),
|
}),
|
||||||
CollaborationCursor.configure({
|
CollaborationCursor.configure({
|
||||||
provider: remoteProvider,
|
provider: remoteProvider,
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Doc, concatLink } from '@hcengineering/core'
|
import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref, concatLink } from '@hcengineering/core'
|
||||||
import { Document, documentId } from '@hcengineering/document'
|
import document, { Document, documentId } from '@hcengineering/document'
|
||||||
import { getMetadata } from '@hcengineering/platform'
|
import { getMetadata } from '@hcengineering/platform'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
@ -38,11 +38,27 @@ export async function documentTextPresenter (doc: Doc): Promise<string> {
|
|||||||
return document.title
|
return document.title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function findChildDocuments (
|
||||||
|
doc: Doc,
|
||||||
|
hiearachy: Hierarchy,
|
||||||
|
findAll: <T extends Doc>(
|
||||||
|
clazz: Ref<Class<T>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
options?: FindOptions<T>
|
||||||
|
) => Promise<FindResult<T>>
|
||||||
|
): Promise<Doc[]> {
|
||||||
|
return await findAll(document.class.Document, { parent: doc._id as Ref<Document> })
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export default async () => ({
|
export default async () => ({
|
||||||
function: {
|
function: {
|
||||||
DocumentHTMLPresenter: documentHTMLPresenter,
|
DocumentHTMLPresenter: documentHTMLPresenter,
|
||||||
DocumentTextPresenter: documentTextPresenter,
|
DocumentTextPresenter: documentTextPresenter,
|
||||||
DocumentLinkIdProvider: documentLinkIdProvider
|
DocumentLinkIdProvider: documentLinkIdProvider,
|
||||||
|
FindChildDocuments: findChildDocuments
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import { Doc } from '@hcengineering/core'
|
import { Doc } from '@hcengineering/core'
|
||||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
|
import { ObjectDDParticipantFunc } from '@hcengineering/server-core'
|
||||||
import { Presenter } from '@hcengineering/server-notification'
|
import { Presenter } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,6 +21,7 @@ export default plugin(serverDocumentId, {
|
|||||||
function: {
|
function: {
|
||||||
DocumentHTMLPresenter: '' as Resource<Presenter>,
|
DocumentHTMLPresenter: '' as Resource<Presenter>,
|
||||||
DocumentTextPresenter: '' as Resource<Presenter>,
|
DocumentTextPresenter: '' as Resource<Presenter>,
|
||||||
DocumentLinkIdProvider: '' as Resource<(doc: Doc) => Promise<string>>
|
DocumentLinkIdProvider: '' as Resource<(doc: Doc) => Promise<string>>,
|
||||||
|
FindChildDocuments: '' as Resource<ObjectDDParticipantFunc>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user