diff --git a/dev/doc-import-tool/src/extract/sections.ts b/dev/doc-import-tool/src/extract/sections.ts index f18c231edb..019323e9f9 100644 --- a/dev/doc-import-tool/src/extract/sections.ts +++ b/dev/doc-import-tool/src/extract/sections.ts @@ -1,6 +1,5 @@ import { Document } from 'domhandler' -import { Markup, Ref, generateId } from '@hcengineering/core' -import { DocumentSection } from '@hcengineering/controlled-documents' +import { Markup } from '@hcengineering/core' import { GenericNodeSpec, NodeType, SectionSpec, SectionType, TocSectionSpec } from './types' import { AnyContainer, createNodeExtractor } from './nodes' @@ -11,7 +10,6 @@ import { AnyContainer, createNodeExtractor } from './nodes' * can be found in a document (TOC, history, etc.) */ export interface ExtractedSection { - id: Ref type: SectionType title: string content: Markup @@ -25,7 +23,6 @@ export function extractSections (doc: Document, sectionSpecs: SectionSpec[]): Ex try { const extractedSection = sectionExtractor.extract(doc) sections.push({ - id: generateId(), type: section.type, title: extractedSection.getTitle(), content: extractedSection.getContent() diff --git a/dev/doc-import-tool/src/import.ts b/dev/doc-import-tool/src/import.ts index b23e61b7e4..0d2569cd7f 100644 --- a/dev/doc-import-tool/src/import.ts +++ b/dev/doc-import-tool/src/import.ts @@ -2,15 +2,12 @@ import attachment, { Attachment } from '@hcengineering/attachment' import { getClient as getCollaboratorClient } from '@hcengineering/collaborator-client' import documents, { ChangeControl, - CollaborativeDocumentSection, ControlledDocument, DEFAULT_PERIODIC_REVIEW_INTERVAL, - DEFAULT_SECTION_TITLE, Document, DocumentCategory, DocumentState, DocumentTemplate, - calcRank, createChangeControl, createControlledDocFromTemplate, createDocumentTemplate @@ -36,7 +33,6 @@ import { parseDocument } from 'htmlparser2' import { Config } from './config' import { ExtractedFile } from './extract/extract' import { ExtractedSection } from './extract/sections' -import { compareStrExact } from './helpers' export default async function importExtractedFile ( ctx: MeasureContext, @@ -56,6 +52,10 @@ export default async function importExtractedFile ( try { const docId = await createDocument(txops, extractedFile, config) const createdDoc = await txops.findOne(documents.class.Document, { _id: docId }) + if (createdDoc == null) { + throw new Error(`Failed to obtain created document: ${docId}`) + } + await createSections(ctx, txops, extractedFile, config, createdDoc) } finally { await txops.close() @@ -87,7 +87,6 @@ async function createDocument ( major: 0, minor: 1, commentSequence: 0, - sections: 0, template: templateId, state: DocumentState.Draft, requests: 0, @@ -185,10 +184,7 @@ async function createTemplateIfNotExist ( prefix, data, category, - owner, - { - title: DEFAULT_SECTION_TITLE - } + owner ) if (!success) { throw new Error('Failed to create document template') @@ -206,95 +202,34 @@ async function createSections ( txops: TxOperations, extractedFile: ExtractedFile, config: Config, - doc?: Document + doc: Document ): Promise { if (doc?.template == null) { throw new Error(`Invalid document: ${JSON.stringify(doc)}`) } - const h = txops.getHierarchy() - - const { space, collaboratorApiURL, token, workspaceId } = config + const { collaboratorApiURL, token, workspaceId } = config const collaborator = getCollaboratorClient(txops.getHierarchy(), workspaceId, token, collaboratorApiURL) - console.log('Creating document sections') + console.log('Creating document content') const collabId = doc.content console.log(`Collab doc ID: ${collabId}`) - const docSections = await txops.findAll(documents.class.DocumentSection, { attachedTo: doc._id }) - const shouldMergeSections = docSections.some((s) => s.title !== DEFAULT_SECTION_TITLE) - try { - let prevSection: { rank: string } | undefined + let content: string = '' for (const section of extractedFile.sections) { if (section.type !== 'generic') { continue } - const existingSection = shouldMergeSections - ? docSections.find((s) => s.title !== DEFAULT_SECTION_TITLE && compareStrExact(s.title, section.title)) - : undefined + await processImages(ctx, txops, section, config, doc) - // skipping sections that are not present in the document/template - if (shouldMergeSections && existingSection == null) { - continue - } - - if (existingSection == null) { - const sectionData: AttachedData = { - title: section.title, - rank: calcRank(prevSection, undefined), - key: section.id, - collaboratorSectionId: section.id - } - - console.log(`Creating section data: ${JSON.stringify(sectionData)}`) - - await txops.addCollection( - documents.class.CollaborativeDocumentSection, - space, - doc._id, - doc._class, - 'sections', - sectionData, - section.id - ) - - prevSection = sectionData - } else { - prevSection = existingSection - } - - await processImages(ctx, txops, section, config) - - const collabSectionId = - existingSection != null && h.isDerived(existingSection._class, documents.class.CollaborativeDocumentSection) - ? (existingSection as CollaborativeDocumentSection).collaboratorSectionId - : section.id - - await collaborator.updateContent(collabId, collabSectionId, section.content) + content += `

${section.title}

${section.content}` } - // deleting the default section if it was the only one and there were other sections added from the extracted doc - // doing it after import, so that the trigger doesn't re-create a new empty section - if ( - docSections.length === 1 && - docSections[0].title === DEFAULT_SECTION_TITLE && - extractedFile.sections.some((es) => es.type === 'generic') - ) { - const defaultSection = docSections[0] - - await txops.removeCollection( - defaultSection._class, - defaultSection.space, - defaultSection._id, - doc._id, - doc._class, - 'sections' - ) - } + await collaborator.updateContent(collabId, 'content', content) } finally { // do nothing } @@ -304,12 +239,13 @@ export async function processImages ( ctx: MeasureContext, txops: TxOperations, section: ExtractedSection, - config: Config + config: Config, + doc: Document ): Promise { const dom = parseDocument(section.content) const imageNodes = findAll((n) => n.tagName === 'img', dom.children) - const { storageAdapter, workspaceId, uploadURL, space } = config + const { storageAdapter, workspaceId, uploadURL } = config const imageUploads = imageNodes.map(async (img) => { const src = img.attribs.src @@ -335,9 +271,9 @@ export async function processImages ( const attachmentId: Ref = generateId() await txops.addCollection( attachment.class.Attachment, - space, - section.id, - documents.class.CollaborativeDocumentSection, + doc.space, + doc._id, + doc._class, 'attachments', { file: uuid as Ref, diff --git a/models/controlled-documents/package.json b/models/controlled-documents/package.json index c9f2f59374..d3e6c7660a 100644 --- a/models/controlled-documents/package.json +++ b/models/controlled-documents/package.json @@ -56,6 +56,7 @@ "@hcengineering/notification": "^0.6.23", "@hcengineering/model-notification": "^0.6.0", "@hcengineering/chunter": "^0.6.20", - "@hcengineering/text-editor": "^0.6.0" + "@hcengineering/text-editor": "^0.6.0", + "@hcengineering/collaboration": "^0.6.0" } } diff --git a/models/controlled-documents/src/index.ts b/models/controlled-documents/src/index.ts index 44e918c080..ab3cfe6b0c 100644 --- a/models/controlled-documents/src/index.ts +++ b/models/controlled-documents/src/index.ts @@ -57,17 +57,10 @@ import { TDocumentCategory, TControlledDocument, TChangeControl, - TDocumentSection, - TCollaborativeDocumentSection, - TAttachmentsDocumentSection, TSequence, TDocumentRequest, TDocumentReviewRequest, TDocumentApprovalRequest, - TDocumentSectionEditor, - TDocumentSectionPresenter, - TDocumentSectionCreator, - TDocumentTemplateSection, TTypeDocumentState, TTypeControlledDocumentState, TDocumentComment @@ -97,20 +90,11 @@ export function createModel (builder: Builder): void { TDocumentCategory, TControlledDocument, TChangeControl, - - TDocumentSection, - TCollaborativeDocumentSection, - TAttachmentsDocumentSection, - TSequence, TDocumentRequest, TDocumentReviewRequest, TDocumentApprovalRequest, - TDocumentSectionEditor, - TDocumentSectionPresenter, - TDocumentSectionCreator, - TDocumentTemplateSection, TTypeDocumentState, TTypeControlledDocumentState, @@ -413,39 +397,6 @@ export function createModel (builder: Builder): void { encode: documents.function.GetDocumentMetaLinkFragment }) - builder.mixin(documents.class.CollaborativeDocumentSection, core.class.Class, view.mixin.ObjectPresenter, { - presenter: documents.component.CollaborativeSectionPresenter - }) - - builder.mixin(documents.class.CollaborativeDocumentSection, core.class.Class, documents.mixin.DocumentSectionEditor, { - editor: documents.component.CollaborativeSectionEditor - }) - - builder.mixin( - documents.class.CollaborativeDocumentSection, - core.class.Class, - documents.mixin.DocumentSectionCreator, - { - creator: documents.function.CollaborativeSectionCreator - } - ) - - builder.mixin(documents.class.AttachmentsDocumentSection, core.class.Class, view.mixin.ObjectPresenter, { - presenter: documents.component.AttachmentsSectionPresenter - }) - - builder.mixin(documents.class.AttachmentsDocumentSection, core.class.Class, documents.mixin.DocumentSectionEditor, { - editor: documents.component.AttachmentsSectionEditor - }) - - builder.mixin(documents.class.AttachmentsDocumentSection, core.class.Class, documents.mixin.DocumentSectionCreator, { - creator: documents.function.AttachmentsSectionCreator - }) - - builder.mixin(documents.class.DocumentSection, core.class.Class, view.mixin.IgnoreActions, { - actions: [view.action.Open, print.action.Print, tracker.action.NewRelatedIssue] - }) - builder.mixin(documents.class.Document, core.class.Class, view.mixin.ObjectPresenter, { presenter: documents.component.DocumentPresenter }) @@ -692,87 +643,6 @@ export function createModel (builder: Builder): void { } ) - createAction(builder, { - action: documents.actionImpl.AddCollaborativeSectionAbove, - label: documents.string.AddSectionAbove, - icon: documents.icon.ArrowUp, - input: 'any', - category: view.category.General, - target: documents.class.CollaborativeDocumentSection, - context: { - mode: ['context', 'browser'], - group: 'create' - } - }) - - createAction(builder, { - action: documents.actionImpl.AddCollaborativeSectionBelow, - label: documents.string.AddSectionBelow, - icon: documents.icon.ArrowDown, - input: 'any', - category: view.category.General, - target: documents.class.CollaborativeDocumentSection, - context: { - mode: ['context', 'browser'], - group: 'create' - } - }) - - createAction(builder, { - action: documents.actionImpl.Duplicate, - label: documents.string.Duplicate, - icon: documents.icon.Duplicate, - input: 'any', - category: view.category.General, - target: documents.class.DocumentSection, - context: { - mode: ['context', 'browser'], - group: 'edit' - } - }) - - createAction(builder, { - action: documents.actionImpl.DeleteCollaborativeSection, - label: view.string.Delete, - icon: view.icon.Delete, - keyBinding: ['Meta + Backspace'], - category: view.category.General, - input: 'any', - target: documents.class.DocumentSection, - context: { mode: ['context', 'browser'], group: 'edit' }, - override: [view.action.Delete] - }) - - createAction(builder, { - action: documents.actionImpl.EditDescription, - label: documents.string.EditDescription, - icon: documents.icon.EditDescription, - input: 'any', - category: view.category.General, - target: documents.mixin.DocumentTemplateSection, - context: { - mode: ['context', 'browser'], - group: 'tools' - } - }) - - createAction(builder, { - action: documents.actionImpl.EditGuidance, - label: documents.string.EditGuidance, - icon: documents.icon.EditGuidance, - input: 'any', - category: view.category.General, - target: documents.mixin.DocumentTemplateSection, - context: { - mode: ['context', 'browser'], - group: 'tools' - } - }) - - builder.mixin(documents.class.CollaborativeDocumentSection, core.class.Class, view.mixin.IgnoreActions, { - actions: [view.action.Delete] - }) - builder.mixin(documents.class.DocumentSpace, core.class.Class, view.mixin.IgnoreActions, { actions: [tracker.action.EditRelatedTargets] }) @@ -980,7 +850,6 @@ export function defineNotifications (builder: Builder): void { 'author', 'content', 'labels', - 'sections', 'abstract', 'snapshots', 'requests', diff --git a/models/controlled-documents/src/migration.ts b/models/controlled-documents/src/migration.ts index 6bd6f27906..861ad75aaf 100644 --- a/models/controlled-documents/src/migration.ts +++ b/models/controlled-documents/src/migration.ts @@ -2,10 +2,23 @@ // Copyright @ 2022-2023 Hardcore Engineering Inc. // -import { type Data, type Ref, TxOperations, generateId, DOMAIN_TX, getCollaborativeDoc } from '@hcengineering/core' +import { + type Data, + type Ref, + TxOperations, + generateId, + DOMAIN_TX, + getCollaborativeDoc, + MeasureMetricsContext, + type Class, + type Doc, + SortingOrder +} from '@hcengineering/core' import { createDefaultSpace, createOrUpdate, + type MigrateUpdate, + type MigrationDocumentQuery, tryMigrate, tryUpgrade, type MigrateOperation, @@ -23,8 +36,18 @@ import { type ControlledDocument, createChangeControl } from '@hcengineering/controlled-documents' +import { + loadCollaborativeDoc, + saveCollaborativeDoc, + YXmlElement, + YXmlText, + YAbstractType, + clone +} from '@hcengineering/collaboration' +import attachment, { type Attachment } from '@hcengineering/attachment' +import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment' -import documents from './index' +import documents, { DOMAIN_DOCUMENTS } from './index' async function createTemplatesSpace (tx: TxOperations): Promise { const existingSpace = await tx.findOne(documents.class.DocumentSpace, { @@ -73,39 +96,6 @@ async function createQualityDocumentsSpace (tx: TxOperations): Promise { } } -async function fixChangeControlsForDocs (tx: TxOperations): Promise { - const defaultCCSpec: Data = { - description: '', - reason: '', - impact: '', - impactedDocuments: [] - } - const controlledDocuments = await tx.findAll( - documents.class.ControlledDocument, - {}, - { lookup: { changeControl: documents.class.ChangeControl } } - ) - - for (const cdoc of controlledDocuments) { - const existingCC = await tx.findOne(documents.class.ChangeControl, { _id: cdoc.changeControl }) - - if (existingCC !== undefined) { - continue - } - - const newCc = await tx.createDoc( - documents.class.ChangeControl, - cdoc.space, - defaultCCSpec, - cdoc.changeControl?.length > 0 ? cdoc.changeControl : undefined - ) - - if (cdoc.changeControl === undefined) { - await tx.update(cdoc, { changeControl: newCc }) - } - } -} - async function createProductChangeControlTemplate (tx: TxOperations): Promise { const ccCategory = 'documents:category:DOC - CC' as Ref const productChangeControlTemplate = await tx.findOne(documents.mixin.DocumentTemplate, { @@ -152,15 +142,10 @@ async function createProductChangeControlTemplate (tx: TxOperations): Promise { ) } +async function migrateDocSections (client: MigrationClient): Promise { + const ctx = new MeasureMetricsContext('migrate_doc_sections', {}) + const storage = client.storageAdapter + + const targetDocuments = await client.find(DOMAIN_DOCUMENTS, { + _class: documents.class.ControlledDocument + }) + const attachmentsOps: { filter: MigrationDocumentQuery, update: MigrateUpdate }[] = [] + + for (const document of targetDocuments) { + const targetSections: any = await client.find( + DOMAIN_DOCUMENTS, + { + _class: 'documents:class:CollaborativeDocumentSection' as Ref>, + attachedTo: document._id + }, + { + sort: { rank: SortingOrder.Ascending } + } + ) + + // Migrate sections headers + content + try { + const ydoc = await loadCollaborativeDoc(storage, client.workspaceId, document.content, ctx) + if (ydoc === undefined) { + ctx.error('collaborative document content not found', { document: document.title }) + continue + } + + if (ydoc.share.has('content')) { + // Already migrated? + continue + } + + const content = ydoc.getXmlFragment('content') + + ydoc.transact((tr) => { + for (const section of targetSections) { + const sectionTemplate = section['documents:mixin:DocumentTemplateSection'] + const sectionNote = sectionTemplate?.description ?? sectionTemplate?.guidance + const titleXml = new YXmlText() + titleXml.insert( + 0, + section.title, + sectionNote !== undefined && sectionNote !== '' + ? { note: { kind: 'neutral', title: sectionNote } } + : undefined + ) + + const sectionContent = ydoc.getXmlFragment(section.collaboratorSectionId) + const sectionTitle = new YXmlElement('heading') + sectionTitle.setAttribute('level', 1 as any) + sectionTitle.insert(0, [titleXml]) + + content.push([ + sectionTitle, + ...(sectionContent + .toArray() + .map((item) => + item instanceof YAbstractType ? (item instanceof YXmlElement ? clone(item) : item.clone()) : item + ) as any) + ]) + } + }) + + await saveCollaborativeDoc(storage, client.workspaceId, document.content, ydoc, ctx) + } catch (err) { + ctx.error('error collaborative document content migration', { error: err, document: document.title }) + } + + attachmentsOps.push({ + filter: { + _class: attachment.class.Attachment, + attachedTo: { $in: targetSections.map((s: any) => s._id) } + }, + update: { + attachedTo: document._id, + attachedToClass: document._class + } + }) + } + + if (attachmentsOps.length > 0) { + await client.bulk(DOMAIN_ATTACHMENT, attachmentsOps) + } +} + export const documentsOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await tryMigrate(client, documentsId, [ { state: 'migrateSpaceTypes', func: migrateSpaceTypes + }, + { + state: 'migrateDocSections', + func: migrateDocSections } ]) }, @@ -305,7 +381,6 @@ export const documentsOperation: MigrateOperation = { await createTemplateSequence(tx) await createTagCategories(tx) await createDocumentCategories(tx) - await fixChangeControlsForDocs(tx) await createProductChangeControlTemplate(tx) } } diff --git a/models/controlled-documents/src/plugin.ts b/models/controlled-documents/src/plugin.ts index 08344d66ad..8b709ed2bf 100644 --- a/models/controlled-documents/src/plugin.ts +++ b/models/controlled-documents/src/plugin.ts @@ -26,20 +26,14 @@ import { type TextActionVisibleFunction, type TextActionFunction } from '@hcengi export default mergeIds(documentsId, documents, { component: { - DocumentTemplateSectionPresenter: '' as AnyComponent, - ContentSectionPresenter: '' as AnyComponent, - AttachmentSectionPresenter: '' as AnyComponent, DocumentVersions: '' as AnyComponent, EditDocumentContent: '' as AnyComponent, EditDocumentAttachment: '' as AnyComponent, - TemplateSectionPresenter: '' as AnyComponent, // new model components CategoryPresenter: '' as AnyComponent, Categories: '' as AnyComponent, DocumentTemplates: '' as AnyComponent, - CollaborativeSectionPresenter: '' as AnyComponent, - AttachmentsSectionPresenter: '' as AnyComponent, StateFilterValuePresenter: '' as AnyComponent, ControlledStateFilterValuePresenter: '' as AnyComponent, @@ -63,12 +57,6 @@ export default mergeIds(documentsId, documents, { IsCommentVisible: '' as Resource }, actionImpl: { - AddCollaborativeSectionAbove: '' as ViewAction, - AddCollaborativeSectionBelow: '' as ViewAction, - DeleteCollaborativeSection: '' as ViewAction, - Duplicate: '' as ViewAction, - EditDescription: '' as ViewAction, - EditGuidance: '' as ViewAction, CreateChildDocument: '' as ViewAction, CreateChildTemplate: '' as ViewAction, CreateDocument: '' as ViewAction, diff --git a/models/controlled-documents/src/types.ts b/models/controlled-documents/src/types.ts index a40f13b757..6fbe902438 100644 --- a/models/controlled-documents/src/types.ts +++ b/models/controlled-documents/src/types.ts @@ -15,9 +15,7 @@ import request from '@hcengineering/request' import { - type AttachmentsDocumentSection, type ChangeControl, - type CollaborativeDocumentSection, type ControlledDocument, type ControlledDocumentState, type Document, @@ -26,17 +24,12 @@ import { type DocumentCategory, type DocumentRequest, type DocumentReviewRequest, - type DocumentSection, type DocumentComment, - type DocumentSectionCreator, - type DocumentSectionEditor, - type DocumentSectionPresenter, type DocumentSpace, type DocumentSpaceType, type DocumentSpaceTypeDescriptor, type DocumentState, type DocumentTemplate, - type DocumentTemplateSection, type Sequence, type DocumentMeta, type ExternalSpace, @@ -54,10 +47,8 @@ import contact, { type Employee } from '@hcengineering/contact' import { DateRangeMode, IndexKind, - type AttachedData, type Class, type Doc, - type Markup, type Ref, type Timestamp, type Type, @@ -91,16 +82,14 @@ import attachment from '@hcengineering/model-attachment' import chunter, { TChatMessage } from '@hcengineering/model-chunter' import core, { TAttachedDoc, - TClass, TDoc, TTypedSpace, TType, TSpaceTypeDescriptor, TSpaceType } from '@hcengineering/model-core' -import { getEmbeddedLabel, type Resource } from '@hcengineering/platform' +import { getEmbeddedLabel } from '@hcengineering/platform' import tags, { type TagReference } from '@hcengineering/tags' -import { type AnyComponent } from '@hcengineering/ui' import training, { type Training, type TrainingRequest } from '@hcengineering/training' import documents from './plugin' @@ -275,9 +264,6 @@ export class TDocument extends TDoc implements Document { @Prop(Collection(tags.class.TagReference), documents.string.Labels) labels?: CollectionSize - @Prop(Collection(documents.class.DocumentSection), documents.string.Sections) - sections!: CollectionSize - @Prop(TypeString(), documents.string.MetaAbstract) @Index(IndexKind.FullText) abstract?: string @@ -291,6 +277,9 @@ export class TDocument extends TDoc implements Document { @Prop(Collection(documents.class.DocumentSnapshot), documents.string.Snapshots) snapshots?: CollectionSize + + @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) + attachments?: CollectionSize } @Model(documents.class.HierarchyDocument, documents.class.Document) @@ -428,25 +417,6 @@ export class TChangeControl extends TDoc implements ChangeControl { impactedDocuments!: Ref[] } -@Model(documents.class.DocumentSection, core.class.AttachedDoc, DOMAIN_DOCUMENTS) -@UX(documents.string.Section) -export class TDocumentSection extends TAttachedDoc implements DocumentSection { - @Prop(TypeString(), documents.string.Title) - @Index(IndexKind.FullText) - title!: string - - @Prop(TypeString(), documents.string.Rank) - @Hidden() - rank!: string - - @Prop(TypeString(), documents.string.Key) - @Hidden() - key!: string - - @Prop(TypeRef(documents.mixin.DocumentTemplateSection), documents.string.SectionTemplate) - templateSectionId?: Ref -} - @Model(documents.class.DocumentSnapshot, core.class.AttachedDoc, DOMAIN_DOCUMENTS) @UX(documents.string.Snapshot) export class TDocumentSnapshot extends TAttachedDoc implements DocumentSnapshot { @@ -459,9 +429,6 @@ export class TDocumentSnapshot extends TAttachedDoc implements DocumentSnapshot @Prop(TypeDocumentState(), documents.string.Status) state?: DocumentState - - @Prop(Collection(documents.class.DocumentSection), documents.string.Sections) - sections!: CollectionSize } @Model(documents.class.ControlledDocumentSnapshot, documents.class.DocumentSnapshot) @@ -473,9 +440,6 @@ export class TControlledDocumentSnapshot extends TDocumentSnapshot implements Co @Model(documents.class.DocumentComment, chunter.class.ChatMessage) export class TDocumentComment extends TChatMessage implements DocumentComment { - @Prop(TypeString(), documents.string.SectionKey) - sectionKey?: string - @Prop(TypeString(), documents.string.ID) nodeId?: string @@ -486,42 +450,6 @@ export class TDocumentComment extends TChatMessage implements DocumentComment { index?: number } -@Mixin(documents.mixin.DocumentTemplateSection, documents.class.DocumentSection) -@UX(documents.string.SectionTemplate) -export class TDocumentTemplateSection extends TDocumentSection implements DocumentTemplateSection { - @Prop(TypeBoolean(), documents.string.Required) - mandatory?: boolean - - @Prop(TypeString(), documents.string.Description) - @Index(IndexKind.FullText) - description?: string - - @Prop(TypeMarkup(), documents.string.Guidance) - @Index(IndexKind.FullText) - guidance?: Markup -} - -@Model(documents.class.CollaborativeDocumentSection, documents.class.DocumentSection) -@UX(documents.string.CollaborativeSection) -export class TCollaborativeDocumentSection extends TDocumentSection implements CollaborativeDocumentSection { - @Prop(TypeString(), documents.string.CollaboratorSectionId) - @Hidden() - collaboratorSectionId!: string - - @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) - attachments?: CollectionSize -} - -@Model(documents.class.AttachmentsDocumentSection, documents.class.DocumentSection) -@UX(documents.string.AttachmentsSection) -export class TAttachmentsDocumentSection extends TDocumentSection implements AttachmentsDocumentSection { - @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) - attachments?: CollectionSize - - @Prop(TypeNumber(), documents.string.AttachmentsMax) - maximum?: number -} - @Model(documents.class.Sequence, core.class.Doc, DOMAIN_DOCUMENTS) export class TSequence extends TDoc implements Sequence { attachedTo!: Ref> @@ -540,27 +468,6 @@ export class TDocumentReviewRequest extends TDocumentRequest implements Document @UX(documents.string.DocumentApprovalRequest) export class TDocumentApprovalRequest extends TDocumentRequest implements DocumentApprovalRequest {} -@Mixin(documents.mixin.DocumentSectionEditor, core.class.Class) -export class TDocumentSectionEditor extends TClass implements DocumentSectionEditor { - editor!: AnyComponent -} - -@Mixin(documents.mixin.DocumentSectionPresenter, core.class.Class) -export class TDocumentSectionPresenter extends TClass implements DocumentSectionPresenter { - presenter!: AnyComponent -} - -@Mixin(documents.mixin.DocumentSectionCreator, core.class.Class) -export class TDocumentSectionCreator extends TClass implements DocumentSectionCreator { - creator!: Resource< - ( - document: Document, - section: AttachedData, - copyFrom?: DocumentSection - ) => AttachedData - > -} - @Mixin(documents.mixin.DocumentSpaceTypeData, documents.class.DocumentSpace) @UX(getEmbeddedLabel('Default Documents'), documents.icon.Document) export class TDocumentSpaceTypeData extends TDocumentSpace implements RolesAssignment { diff --git a/models/server-controlled-documents/src/index.ts b/models/server-controlled-documents/src/index.ts index 5c2887569f..643d2b069f 100644 --- a/models/server-controlled-documents/src/index.ts +++ b/models/server-controlled-documents/src/index.ts @@ -13,15 +13,6 @@ import serverNotification from '@hcengineering/server-notification' export { serverDocumentsId } from '@hcengineering/server-controlled-documents/src/index' export function createModel (builder: Builder): void { - builder.createDoc(serverCore.class.Trigger, core.space.Model, { - trigger: serverDocuments.trigger.OnCollaborativeSectionDeleted, - txMatch: { - _class: core.class.TxCollectionCUD, - 'tx.objectClass': documents.class.CollaborativeDocumentSection, - 'tx._class': core.class.TxRemoveDoc - } - }) - builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverDocuments.trigger.OnDocDeleted, txMatch: { diff --git a/models/text-editor/src/index.ts b/models/text-editor/src/index.ts index fcfdf16fe4..ac7802ced6 100644 --- a/models/text-editor/src/index.ts +++ b/models/text-editor/src/index.ts @@ -340,4 +340,16 @@ export function createModel (builder: Builder): void { category: 100, index: 5 }) + + builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, { + action: textEditor.function.ConfigureNote, + icon: textEditor.icon.Note, + visibilityTester: textEditor.function.IsEditableNote, + isActive: { + name: 'note' + }, + label: textEditor.string.Note, + category: 110, + index: 5 + }) } diff --git a/models/text-editor/src/plugin.ts b/models/text-editor/src/plugin.ts index f6629213b7..19439fe536 100644 --- a/models/text-editor/src/plugin.ts +++ b/models/text-editor/src/plugin.ts @@ -28,8 +28,10 @@ export default mergeIds(textEditorId, textEditor, { OpenImage: '' as Resource, ExpandImage: '' as Resource, MoreImageActions: '' as Resource, + ConfigureNote: '' as Resource, IsEditableTableActive: '' as Resource, + IsEditableNote: '' as Resource, IsEditable: '' as Resource, IsHeadingVisible: '' as Resource } diff --git a/packages/presentation/src/components/Card.svelte b/packages/presentation/src/components/Card.svelte index bdeab3d2bb..bce2ae32bb 100644 --- a/packages/presentation/src/components/Card.svelte +++ b/packages/presentation/src/components/Card.svelte @@ -66,7 +66,7 @@ handleOkClick() } else if (event.key === 'Enter') { // ignore customized editable divs to not interrupt multiline behavior - if (!target.isContentEditable) { + if (!target.isContentEditable && target.nodeName !== 'TEXTAREA') { event.preventDefault() focusManager?.next(1) } diff --git a/packages/text/src/index.ts b/packages/text/src/index.ts index 667e725b4c..7f859d5017 100644 --- a/packages/text/src/index.ts +++ b/packages/text/src/index.ts @@ -21,6 +21,7 @@ export * from './markup/utils' export * from './nodes' export * from './ydoc' export * from './marks/code' +export * from './marks/noteBase' export * from './markdown' export * from './markdown/serializer' export * from './markdown/parser' diff --git a/packages/text/src/kits/server-kit.ts b/packages/text/src/kits/server-kit.ts index 86271f5195..0a29d71ef8 100644 --- a/packages/text/src/kits/server-kit.ts +++ b/packages/text/src/kits/server-kit.ts @@ -31,6 +31,7 @@ import { TodoItemNode, TodoListNode } from '../nodes/todo' import { CodeBlockExtension, codeBlockOptions } from '../nodes' import { DefaultKit, DefaultKitOptions } from './default-kit' import { CodeExtension, codeOptions } from '../marks/code' +import { NoteBaseExtension } from '../marks/noteBase' const headingLevels: Level[] = [1, 2, 3, 4, 5, 6] @@ -87,7 +88,8 @@ export const ServerKit = Extension.create({ TodoItemNode, TodoListNode, ReferenceNode, - NodeUuid + NodeUuid, + NoteBaseExtension ] } }) diff --git a/packages/text/src/marks/noteBase.ts b/packages/text/src/marks/noteBase.ts new file mode 100644 index 0000000000..1114d061ce --- /dev/null +++ b/packages/text/src/marks/noteBase.ts @@ -0,0 +1,53 @@ +import { Mark } from '@tiptap/core' +import { getDataAttribute } from '../nodes' + +export const name = 'note' +export enum NoteKind { + Neutral = 'neutral', + Dangerous = 'dangerous', + DangerousLight = 'dangerous-light', + Warning = 'warning', + WarningLight = 'warning-light', + Positive = 'positive', + PositiveLight = 'positive-light', + Primary = 'primary', + PrimaryLight = 'primary-light' +} + +declare module '@tiptap/core' { + export interface Commands { + [name]: { + setNote: (text: string, kind: NoteKind) => ReturnType + unsetNote: () => ReturnType + } + } +} + +export const NoteBaseExtension = Mark.create({ + name, + + parseHTML () { + return [ + { + tag: `span[data-mark="${name}"]` + } + ] + }, + + renderHTML ({ HTMLAttributes, mark }) { + return [ + 'span', + { ...HTMLAttributes, 'data-mark': this.name, class: `theme-text-editor-note-anchor ${mark.attrs.kind}` }, + 0 + ] + }, + + addAttributes () { + return { + title: { + default: null + }, + kind: getDataAttribute('kind', { default: NoteKind.Neutral }) + } + } +}) diff --git a/packages/text/src/markup/utils.ts b/packages/text/src/markup/utils.ts index 8ba4813746..8edcf75c20 100644 --- a/packages/text/src/markup/utils.ts +++ b/packages/text/src/markup/utils.ts @@ -27,8 +27,8 @@ import { MarkupMark, MarkupNode, MarkupNodeType, emptyMarkupNode } from './model export const EmptyMarkup: Markup = jsonToMarkup(emptyMarkupNode()) /** @public */ -export function getMarkup (editor: Editor): Markup { - return jsonToMarkup(editor.getJSON() as MarkupNode) +export function getMarkup (editor?: Editor): Markup { + return jsonToMarkup(editor?.getJSON() as MarkupNode) } /** @public */ diff --git a/packages/text/src/nodes/utils.ts b/packages/text/src/nodes/utils.ts index c87c33a9a4..0e051f1520 100644 --- a/packages/text/src/nodes/utils.ts +++ b/packages/text/src/nodes/utils.ts @@ -20,7 +20,7 @@ import { Attribute } from '@tiptap/core' */ export function getDataAttribute ( name: string, - options?: Omit + options?: Partial> ): Partial { const dataName = `data-${name}` diff --git a/packages/theme/styles/_colors.scss b/packages/theme/styles/_colors.scss index 6d5c260975..02ba32a4dd 100644 --- a/packages/theme/styles/_colors.scss +++ b/packages/theme/styles/_colors.scss @@ -238,6 +238,16 @@ --theme-won-color: #34DB80; --theme-caret-color: #fff; + --theme-text-editor-note-anchor-bg-neutral: #2C2C2C; /* Gray, no saturation change needed */ + --theme-text-editor-note-anchor-bg-dangerous: #8F4040; + --theme-text-editor-note-anchor-bg-dangerous-light: #8E6464; + --theme-text-editor-note-anchor-bg-warning: #A88D4E; + --theme-text-editor-note-anchor-bg-warning-light: #8A8666; + --theme-text-editor-note-anchor-bg-positive: #596941; + --theme-text-editor-note-anchor-bg-positive-light: #7B9589; + --theme-text-editor-note-anchor-bg-primary: #688797; + --theme-text-editor-note-anchor-bg-primary-light: #747C81; + --accent-bg-color: #27282b; --accent-shadow: rgb(0 0 0 / 10%) 0px 2px 4px; @@ -487,6 +497,16 @@ --theme-won-color: #34DB80; // Dark --theme-caret-color: #669AFF; + --theme-text-editor-note-anchor-bg-neutral: #F3F3F3; + --theme-text-editor-note-anchor-bg-dangerous: #DF8D8B; + --theme-text-editor-note-anchor-bg-dangerous-light: #EECECE; + --theme-text-editor-note-anchor-bg-warning: #FDE5A4; + --theme-text-editor-note-anchor-bg-warning-light: #FEF4D1; + --theme-text-editor-note-anchor-bg-positive: #BED6AF; + --theme-text-editor-note-anchor-bg-positive-light: #DEE9D9; + --theme-text-editor-note-anchor-bg-primary: #AAC5E9; + --theme-text-editor-note-anchor-bg-primary-light: #D5E5F5; + --accent-bg-color: #eff0f2; // HZ --accent-shadow: rgb(0 0 0 / 10%) 0px 2px 4px; // Dark diff --git a/packages/theme/styles/_text-editor.scss b/packages/theme/styles/_text-editor.scss index d7e4b8edcb..f9ac6e4f6e 100644 --- a/packages/theme/styles/_text-editor.scss +++ b/packages/theme/styles/_text-editor.scss @@ -317,6 +317,11 @@ &.text-editor-highlighted-node-selected, &:hover { background-color: var(--text-editor-highlighted-node-warning-active-background-color); } + + @media print { + background-color: inherit !important; + border-bottom: none; + } } .text-editor-highlighted-node-delete { @@ -330,6 +335,65 @@ color: var(--text-editor-highlighted-node-add-font-color) } +.text-editor-note-marker { + padding-left: 0.25rem; + color: transparent; + background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20256%20256%22%20id%3D%22note%22%3E%3Crect%20width%3D%22256%22%20height%3D%22256%22%20fill%3D%22none%22%3E%3C%2Frect%3E%3Cline%20x1%3D%2296%22%20x2%3D%22160%22%20y1%3D%2296%22%20y2%3D%2296%22%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cline%20x1%3D%2296%22%20x2%3D%22160%22%20y1%3D%22128%22%20y2%3D%22128%22%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cline%20x1%3D%2296%22%20x2%3D%22128%22%20y1%3D%22160%22%20y2%3D%22160%22%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%20d%3D%22M156.68629%2C216H48a8%2C8%2C0%2C0%2C1-8-8V48a8%2C8%2C0%2C0%2C1%2C8-8H208a8%2C8%2C0%2C0%2C1%2C8%2C8V156.68629a8%2C8%2C0%2C0%2C1-2.34315%2C5.65686l-51.3137%2C51.3137A8%2C8%2C0%2C0%2C1%2C156.68629%2C216Z%22%3E%3C%2Fpath%3E%3Cpolyline%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%20points%3D%22215.277%20159.992%20160%20159.992%20160%20215.272%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E'); + background-repeat: no-repeat; + user-select: none; + cursor: pointer; + + @media print { + display: none; + } +} + +.theme-dark .text-editor-note-marker { + background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20256%20256%22%20id%3D%22note%22%3E%3Crect%20width%3D%22256%22%20height%3D%22256%22%20fill%3D%22none%22%3E%3C%2Frect%3E%3Cline%20x1%3D%2296%22%20x2%3D%22160%22%20y1%3D%2296%22%20y2%3D%2296%22%20fill%3D%22none%22%20stroke%3D%22%23FDFDF7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cline%20x1%3D%2296%22%20x2%3D%22160%22%20y1%3D%22128%22%20y2%3D%22128%22%20fill%3D%22none%22%20stroke%3D%22%23FDFDF7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cline%20x1%3D%2296%22%20x2%3D%22128%22%20y1%3D%22160%22%20y2%3D%22160%22%20fill%3D%22none%22%20stroke%3D%22%23FDFDF7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23FDFDF7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%20d%3D%22M156.68629%2C216H48a8%2C8%2C0%2C0%2C1-8-8V48a8%2C8%2C0%2C0%2C1%2C8-8H208a8%2C8%2C0%2C0%2C1%2C8%2C8V156.68629a8%2C8%2C0%2C0%2C1-2.34315%2C5.65686l-51.3137%2C51.3137A8%2C8%2C0%2C0%2C1%2C156.68629%2C216Z%22%3E%3C%2Fpath%3E%3Cpolyline%20fill%3D%22none%22%20stroke%3D%22%23FDFDF7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%20points%3D%22215.277%20159.992%20160%20159.992%20160%20215.272%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E'); +} + +.theme-text-editor-note-anchor { + &.neutral { + background-color: var(--theme-text-editor-note-anchor-bg-neutral); + } + + &.dangerous { + background-color: var(--theme-text-editor-note-anchor-bg-dangerous); + } + + &.dangerous-light { + background-color: var(--theme-text-editor-note-anchor-bg-dangerous-light); + } + + &.warning { + background-color: var(--theme-text-editor-note-anchor-bg-warning); + } + + &.warning-light { + background-color: var(--theme-text-editor-note-anchor-bg-warning-light); + } + + &.positive { + background-color: var(--theme-text-editor-note-anchor-bg-positive); + } + + &.positive-light { + background-color: var(--theme-text-editor-note-anchor-bg-positive-light); + } + + &.primary { + background-color: var(--theme-text-editor-note-anchor-bg-primary); + } + + &.primary-light { + background-color: var(--theme-text-editor-note-anchor-bg-primary-light); + } + + @media print { + background-color: inherit !important; + } +} + .text-editor-popup { background-color: var(--theme-comp-header-color); border-radius: 0.5rem; diff --git a/packages/theme/styles/panel.scss b/packages/theme/styles/panel.scss index f7c73a4e0c..c9280ea933 100644 --- a/packages/theme/styles/panel.scss +++ b/packages/theme/styles/panel.scss @@ -369,6 +369,10 @@ flex-direction: column; min-width: 0; min-height: 0; + + @media print { + overflow: visible; + } } &__main, &__aside { height: 100%; @@ -538,6 +542,7 @@ @media print { border: none; + overflow: visible; } // &.asideShown .popupPanel-body__main { diff --git a/plugins/controlled-documents-assets/assets/icons.svg b/plugins/controlled-documents-assets/assets/icons.svg index 216f9718ef..cc6dd2356b 100644 --- a/plugins/controlled-documents-assets/assets/icons.svg +++ b/plugins/controlled-documents-assets/assets/icons.svg @@ -58,21 +58,6 @@ - - - - - - - - - - - - - - - diff --git a/plugins/controlled-documents-assets/lang/en.json b/plugins/controlled-documents-assets/lang/en.json index 5144a9ef41..e0d74de405 100644 --- a/plugins/controlled-documents-assets/lang/en.json +++ b/plugins/controlled-documents-assets/lang/en.json @@ -9,11 +9,7 @@ "Requests": "Requests", "EffectiveDate": "Effective date", "PlannedEffectiveDate": "Planned effective date", - "Section": "Section", "Rank": "Rang", - "CollaborativeSection": "Collaborative section", - "CollaboratorSectionId": "Collaborator section ID", - "AttachmentsSection": "Attachments section", "DocumentRequest": "Request", "DocumentReviewRequest": "Document review request", "DocumentApprovalRequest": "Document approval request", @@ -22,7 +18,6 @@ "Guidance": "Guidance", "Required": "Required", "Description": "Description", - "SectionTemplate": "Section template", "Major": "Major", "Minor": "Minor", "Patch": "Patch", @@ -104,8 +99,6 @@ "DocumentCodePlaceholder": "DOC-1", "DocumentPrefixPlaceholder": "DOC", "DocumentPrefix": "Documents prefix", - "Sections": "Sections", - "TemplateSectionTitle": "Title", "DocumentTemplateCreateLabel": "Template", "DocumentCategoryCreateLabel": "Category", "CreateDocumentCategory": "Create a category", @@ -114,6 +107,7 @@ "AttachmentsMax": "Maximum allowed attachments", "Resolve": "Resolve", "Unresolve": "Unresolve", + "Pending": "Pending", "Resolved": "Resolved", "ShowResolved": "Show resolved comments", "Ordering": "Ordering", @@ -152,12 +146,6 @@ "ChangeControl": "Change Control", "ReviewInterval": "Review interval", - "DocTemplateDeleteSectionTitle": "Confirm deletion of section", - "DocTemplateDeleteSectionConfirm": "Are you sure that you want to permanently remove section \"{section}\" from the template?", - - "AddSectionAbove": "Add new section above", - "AddSectionBelow": "Add new section below", - "SelectReviewers": "Select reviewers", "SelectApprovers": "Select approvers", "RequestsToReviewTheDoc": "requests you to review the document", @@ -179,8 +167,6 @@ "InfoStepTitle": "Info", "TeamStepTitle": "Team", - "Duplicate": "Duplicate", - "TitleAndDescr": "Title and description", "Reason": "Reason", "AbstractPlaceholder": "What is this document about? Who will need it and when? ...", @@ -193,7 +179,6 @@ "EditDocument": "Edit document", "Key": "Key", - "SectionKey": "Section key", "CommentsSequence": "Comments sequence", "Email": "Email", diff --git a/plugins/controlled-documents-assets/lang/fr.json b/plugins/controlled-documents-assets/lang/fr.json index 8b0b8c7c2f..95a0a7de04 100644 --- a/plugins/controlled-documents-assets/lang/fr.json +++ b/plugins/controlled-documents-assets/lang/fr.json @@ -9,11 +9,7 @@ "Requests": "Demandes", "EffectiveDate": "Date d'effet", "PlannedEffectiveDate": "Date d'effet prévue", - "Section": "Section", "Rank": "Rang", - "CollaborativeSection": "Section collaborative", - "CollaboratorSectionId": "ID de section du collaborateur", - "AttachmentsSection": "Section des pièces jointes", "DocumentRequest": "Demande", "DocumentReviewRequest": "Demande de révision de document", "DocumentApprovalRequest": "Demande d'approbation de document", @@ -22,7 +18,6 @@ "Guidance": "Orientation", "Required": "Requis", "Description": "Description", - "SectionTemplate": "Modèle de section", "Major": "Majeur", "Minor": "Mineur", "Patch": "Correctif", @@ -95,8 +90,6 @@ "DocumentCodePlaceholder": "DOC-1", "DocumentPrefixPlaceholder": "DOC", "DocumentPrefix": "Préfixe des documents", - "Sections": "Sections", - "TemplateSectionTitle": "Titre", "DocumentTemplateCreateLabel": "Modèle", "DocumentCategoryCreateLabel": "Catégorie", "CreateDocumentCategory": "Créer une catégorie", @@ -105,6 +98,7 @@ "AttachmentsMax": "Nombre maximum de pièces jointes autorisé", "Resolve": "Résoudre", "Unresolve": "Non résolu", + "Pending": "En attente", "Resolved": "Résolu", "ShowResolved": "Afficher les commentaires résolus", "Ordering": "Ordonnancement", @@ -137,10 +131,6 @@ "CreateDraftQmsTemplates": "Créer des modèles QMS brouillons", "ChangeControl": "Contrôle des modifications", "ReviewInterval": "Intervalle de révision", - "DocTemplateDeleteSectionTitle": "Confirmer la suppression de la section", - "DocTemplateDeleteSectionConfirm": "Êtes-vous sûr de vouloir supprimer définitivement la section \"{section}\" du modèle ?", - "AddSectionAbove": "Ajouter une nouvelle section au-dessus", - "AddSectionBelow": "Ajouter une nouvelle section en dessous", "SelectReviewers": "Sélectionner les réviseurs", "SelectApprovers": "Sélectionner les approuveurs", "RequestsToReviewTheDoc": "vous demande de réviser le document", @@ -158,7 +148,6 @@ "TemplateStepTitle": "Modèle", "InfoStepTitle": "Info", "TeamStepTitle": "Équipe", - "Duplicate": "Dupliquer", "TitleAndDescr": "Titre et description", "Reason": "Raison", "AbstractPlaceholder": "De quoi parle ce document ? Qui en aura besoin et quand ? ...", @@ -169,7 +158,6 @@ "ReasonPlaceholder": "Précisez la raison...", "EditDocument": "Modifier le document", "Key": "Clé", - "SectionKey": "Clé de section", "CommentsSequence": "Séquence des commentaires", "Email": "E-mail", "Password": "Mot de passe", diff --git a/plugins/controlled-documents-assets/lang/ru.json b/plugins/controlled-documents-assets/lang/ru.json index 78d3a15dcf..4cbfbc355c 100644 --- a/plugins/controlled-documents-assets/lang/ru.json +++ b/plugins/controlled-documents-assets/lang/ru.json @@ -9,11 +9,7 @@ "Requests": "Запросы", "EffectiveDate": "Дата выпуска", "PlannedEffectiveDate": "Планируемая дата выпуска", - "Section": "Section", "Rank": "Ранг", - "CollaborativeSection": "Коллаборативная секция", - "CollaboratorSectionId": "ID коллаборационной секции", - "AttachmentsSection": "Секция прикрепленных файлов", "DocumentRequest": "Запрос", "DocumentReviewRequest": "Запрос рецензии документа", "DocumentApprovalRequest": "Запрос утверждения документа", @@ -22,7 +18,6 @@ "Guidance": "Руководство", "Required": "Обязательно", "Description": "Описание", - "SectionTemplate": "Шаблон секции", "Major": "Мажорная", "Minor": "Минорная", "Patch": "Патч", @@ -104,8 +99,6 @@ "DocumentCodePlaceholder": "ДОК-1", "DocumentPrefixPlaceholder": "ДОК", "DocumentPrefix": "Префикс документов", - "Sections": "Разделы", - "TemplateSectionTitle": "Заголовок", "DocumentTemplateCreateLabel": "Шалблон", "DocumentCategoryCreateLabel": "Категория", "CreateDocumentCategory": "Создать категорию документа", @@ -114,6 +107,7 @@ "AttachmentsMax": "Максимальное число допустимых файлов", "Resolve": "Пометить выполненным", "Unresolve": "Пометить невыполненным", + "Pending": "В ожидании", "Resolved": "Выполненный", "ShowResolved": "Показать выполненные", "Ordering": "Сортировка", @@ -152,12 +146,6 @@ "ChangeControl": "Контроль Изменений", "ReviewInterval": "Интервал ревью", - "DocTemplateDeleteSectionTitle": "Подтверждение удаления секции", - "DocTemplateDeleteSectionConfirm": "Вы действительно хотите навсегда удалить секцию \"{section}\" из шаблона?", - - "AddSectionAbove": "Добавить новую секцию сверху", - "AddSectionBelow": "Добавить новую секцию снизу", - "SelectReviewers": "Выберите рецензентов", "SelectApprovers": "Выберите утверждающих", "RequestsToReviewTheDoc": "запрашивает у вас рецензию на документ", @@ -179,8 +167,6 @@ "InfoStepTitle": "Инфо", "TeamStepTitle": "Команда", - "Duplicate": "Дублировать", - "TitleAndDescr": "Заголовок и описание", "Reason": "Причина", "AbstractPlaceholder": "О чем этот документ? Кто будет его использовать и как? ...", @@ -193,7 +179,6 @@ "EditDocument": "Редактировать документ", "Key": "Ключ", - "SectionKey": "Ключ секции", "CommentsSequence": "Последовательность комментариев", "Email": "Email", diff --git a/plugins/controlled-documents-assets/lang/zh.json b/plugins/controlled-documents-assets/lang/zh.json index bb0287cfed..d553b14f82 100644 --- a/plugins/controlled-documents-assets/lang/zh.json +++ b/plugins/controlled-documents-assets/lang/zh.json @@ -9,11 +9,7 @@ "Requests": "请求", "EffectiveDate": "生效日期", "PlannedEffectiveDate": "计划生效日期", - "Section": "章节", "Rank": "等级", - "CollaborativeSection": "协作章节", - "CollaboratorSectionId": "协作章节 ID", - "AttachmentsSection": "附件章节", "DocumentRequest": "请求", "DocumentReviewRequest": "文档审查请求", "DocumentApprovalRequest": "文档审批请求", @@ -22,7 +18,6 @@ "Guidance": "指导", "Required": "必需的", "Description": "描述", - "SectionTemplate": "章节模板", "Major": "主要", "Minor": "次要", "Patch": "补丁", @@ -104,8 +99,6 @@ "DocumentCodePlaceholder": "DOC-1", "DocumentPrefixPlaceholder": "DOC", "DocumentPrefix": "文档前缀", - "Sections": "章节", - "TemplateSectionTitle": "标题", "DocumentTemplateCreateLabel": "模板", "DocumentCategoryCreateLabel": "类别", "CreateDocumentCategory": "创建类别", @@ -114,6 +107,7 @@ "AttachmentsMax": "允许的最大附件数", "Resolve": "解决", "Unresolve": "取消解决", + "Pending": "待定", "Resolved": "已解决", "ShowResolved": "显示已解决的评论", "Ordering": "排序", @@ -152,12 +146,6 @@ "ChangeControl": "变更控制", "ReviewInterval": "审查间隔", - "DocTemplateDeleteSectionTitle": "确认删除章节", - "DocTemplateDeleteSectionConfirm": "您确定要从模板中永久删除章节 \"{section}\" 吗?", - - "AddSectionAbove": "在上方添加新章节", - "AddSectionBelow": "在下方添加新章节", - "SelectReviewers": "选择审查人", "SelectApprovers": "选择批准人", "RequestsToReviewTheDoc": "请求您审查文档", @@ -179,8 +167,6 @@ "InfoStepTitle": "信息", "TeamStepTitle": "团队", - "Duplicate": "复制", - "TitleAndDescr": "标题和描述", "Reason": "原因", "AbstractPlaceholder": "这份文档是关于什么的?谁需要它以及何时需要?...", @@ -193,7 +179,6 @@ "EditDocument": "编辑文档", "Key": "键", - "SectionKey": "章节键", "CommentsSequence": "评论顺序", "Email": "电子邮件", diff --git a/plugins/controlled-documents-assets/src/index.ts b/plugins/controlled-documents-assets/src/index.ts index c0d0c5ea68..c6e4ca615e 100644 --- a/plugins/controlled-documents-assets/src/index.ts +++ b/plugins/controlled-documents-assets/src/index.ts @@ -30,8 +30,5 @@ loadMetadata(documents.icon, { StateObsolete: `${icons}#state-obsolete`, ArrowUp: `${icons}#arrow-up`, ArrowDown: `${icons}#arrow-down`, - Duplicate: `${icons}#duplicate`, - EditDescription: `${icons}#edit-description`, - EditGuidance: `${icons}#edit-guidance`, Configure: `${icons}#configure` }) diff --git a/plugins/controlled-documents-resources/src/components/CreateDocument.svelte b/plugins/controlled-documents-resources/src/components/CreateDocument.svelte index d15be6a931..ab8f87e533 100644 --- a/plugins/controlled-documents-resources/src/components/CreateDocument.svelte +++ b/plugins/controlled-documents-resources/src/components/CreateDocument.svelte @@ -72,7 +72,6 @@ category: '' as Ref, abstract: '', state: DocumentState.Draft, - sections: 0, requests: 0, snapshots: 0, reviewers: [], @@ -131,9 +130,6 @@ limit: 1 } ) - - // Note: previously there was inline document section editing. Restore later if will be needed as per UX. - // See QE CreateReport component for reference. May unify general approach on creating docs with content right away. diff --git a/plugins/controlled-documents-resources/src/components/DocumentSectionDeletePopup.svelte b/plugins/controlled-documents-resources/src/components/DocumentSectionDeletePopup.svelte deleted file mode 100644 index ff019eda40..0000000000 --- a/plugins/controlled-documents-resources/src/components/DocumentSectionDeletePopup.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/plugins/controlled-documents-resources/src/components/FieldSectionEditor.svelte b/plugins/controlled-documents-resources/src/components/FieldSectionEditor.svelte deleted file mode 100644 index 585502da69..0000000000 --- a/plugins/controlled-documents-resources/src/components/FieldSectionEditor.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - - -
- - -
-
- -
-
-
-
- -
- - .  - - {#if sectionType} - -
- -
-
- -
-
-
-
- -
-
- -
- -
- -
-
-
- - diff --git a/plugins/controlled-documents-resources/src/components/create-doc/QmsDocumentWizard.svelte b/plugins/controlled-documents-resources/src/components/create-doc/QmsDocumentWizard.svelte index 0dc46d6281..762a6b57ca 100644 --- a/plugins/controlled-documents-resources/src/components/create-doc/QmsDocumentWizard.svelte +++ b/plugins/controlled-documents-resources/src/components/create-doc/QmsDocumentWizard.svelte @@ -120,7 +120,6 @@ author: currentUser.person as Ref, owner: currentUser.person as Ref, state: DocumentState.Draft, - sections: 0, snapshots: 0, changeControl: ccRecordId, content: getCollaborativeDoc(generateId()), diff --git a/plugins/controlled-documents-resources/src/components/create-doc/QmsTemplateWizard.svelte b/plugins/controlled-documents-resources/src/components/create-doc/QmsTemplateWizard.svelte index 398886838d..75a2a2f6be 100644 --- a/plugins/controlled-documents-resources/src/components/create-doc/QmsTemplateWizard.svelte +++ b/plugins/controlled-documents-resources/src/components/create-doc/QmsTemplateWizard.svelte @@ -18,7 +18,6 @@ ChangeControl, ControlledDocument, DEFAULT_PERIODIC_REVIEW_INTERVAL, - DEFAULT_SECTION_TITLE, DocumentState, DocumentTemplate, TEMPLATE_PREFIX, @@ -117,7 +116,6 @@ author: currentUser.person as Ref, owner: currentUser.person as Ref, state: DocumentState.Draft, - sections: 0, snapshots: 0, changeControl: ccRecordId, content: getCollaborativeDoc(generateId()), @@ -166,8 +164,7 @@ docObject.docPrefix, spec, category, - currentUser.person as Ref, - { title: DEFAULT_SECTION_TITLE } + currentUser.person as Ref ) if (!success) { diff --git a/plugins/controlled-documents-resources/src/components/document/DocSectionEditor.svelte b/plugins/controlled-documents-resources/src/components/document/DocSectionEditor.svelte deleted file mode 100644 index 4ff3c11d6c..0000000000 --- a/plugins/controlled-documents-resources/src/components/document/DocSectionEditor.svelte +++ /dev/null @@ -1,215 +0,0 @@ - - - - documentSectionToggled(value._id)} -> - - - - - - {index + 1} - - - - - -
- {#if $isEditable && !dragging} - updateSectionTitle(title)} /> - {:else} - {title} - {/if} -
-
- -
- {#if $canAddDocumentComments} -
-
- - {#if isEditingDescription || descr.length > 0} -
- -
- {/if} -
- - - -
- - diff --git a/plugins/controlled-documents-resources/src/components/document/DocumentDiffViewer.svelte b/plugins/controlled-documents-resources/src/components/document/DocumentDiffViewer.svelte index 1afe6a2b77..13f7bd6ed3 100644 --- a/plugins/controlled-documents-resources/src/components/document/DocumentDiffViewer.svelte +++ b/plugins/controlled-documents-resources/src/components/document/DocumentDiffViewer.svelte @@ -4,6 +4,7 @@ import { type Doc } from '@hcengineering/core' import { CollaborationIds, type Ydoc } from '@hcengineering/text-editor' import { + CollaborationDiffViewer, StringDiffViewer, TiptapCollabProvider, createTiptapCollaborationData, @@ -15,67 +16,25 @@ ControlledDocumentSnapshot, ControlledDocumentState, Document, - DocumentSection, DocumentState } from '@hcengineering/controlled-documents' import plugin from '../../plugin' import { $controlledDocument as controlledDocument, - $controlledDocumentSections as sections, $comparedDocument as compareTo, - $comparedDocumentSections as compareToSections, $documentComparisonVersions as documentComparisonVersions, - ComparisonSectionPair, - comparisonRequested, - loadComparedDocumentSectionsFx + comparisonRequested } from '../../stores/editors/document' import { COLLABORATOR_URL, TOKEN, getTranslatedControlledDocStates, getTranslatedDocumentStates } from '../../utils' - import DocumentSectionPairDiffViewer from './DocumentSectionPairDiffViewer.svelte' import DocumentTitle from './DocumentTitle.svelte' const client = getClient() const hierarchy = client.getHierarchy() const ydoc = getContext(CollaborationIds.Doc) - let collapsedPairIndices = new Set() let comparedYdoc: Ydoc | undefined = undefined let comparedProvider: TiptapCollabProvider | undefined = undefined let loading = true - const isLoadPending = loadComparedDocumentSectionsFx.pending - - const handleSectionDiffPairs = (firstSections: DocumentSection[], secondSections: DocumentSection[]) => { - const result: ComparisonSectionPair[] = [] - const firstSectionKeys = new Set(firstSections.map((section) => section.key)) - const secondSectionKeys = new Set(secondSections.map((section) => section.key)) - let secondIndex = 0 - let firstIndex = 0 - while (firstIndex < firstSections.length) { - const firstSection = firstSections[firstIndex] - if (secondSectionKeys.has(firstSection.key)) { - while (secondIndex < secondSections.length && !firstSectionKeys.has(secondSections[secondIndex].key)) { - result.push([null, { section: secondSections[secondIndex], index: secondIndex + 1 }]) - secondIndex++ - } - } - if (secondIndex < secondSections.length && firstSection.key === secondSections[secondIndex].key) { - result.push([ - { section: firstSection, index: firstIndex + 1 }, - { section: secondSections[secondIndex], index: secondIndex + 1 } - ]) - secondIndex++ - } else { - result.push([{ section: firstSection, index: firstIndex + 1 }, null]) - } - firstIndex++ - } - - while (secondIndex < secondSections.length) { - result.push([null, { section: secondSections[secondIndex], index: secondIndex + 1 }]) - secondIndex++ - } - - return result - } const handleSelect = (event: CustomEvent) => { const version = $documentComparisonVersions.find((item) => item._id === event.detail._id) @@ -142,8 +101,6 @@ comparedProvider.loaded.then(() => (loading = false)) } - $: sectionDiffPairs = handleSectionDiffPairs($sections, $compareToSections) - onDestroy(() => { comparedProvider?.destroy() }) @@ -167,35 +124,29 @@ on:selected={handleSelect} /> -{#if loading || $isLoadPending} +{#if loading} {:else} -
-
- - - -
- {#each sectionDiffPairs as pair, index} - { - if (collapsedPairIndices.has(index)) { - collapsedPairIndices.delete(index) - } else { - collapsedPairIndices.add(index) - } - collapsedPairIndices = new Set(collapsedPairIndices) - }} +
+ + - {/each} + + +
{/if} + + diff --git a/plugins/controlled-documents-resources/src/components/document/DocumentSectionPairDiffViewer.svelte b/plugins/controlled-documents-resources/src/components/document/DocumentSectionPairDiffViewer.svelte deleted file mode 100644 index e5091a5291..0000000000 --- a/plugins/controlled-documents-resources/src/components/document/DocumentSectionPairDiffViewer.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - {#if _class && hierarchy.isDerived(_class, documents.class.CollaborativeDocumentSection)} - - {:else} - {plugin.string.ComparisonModeNotSupported} - {/if} - - diff --git a/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte b/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte index 472ee5fc6c..d3ad61a67a 100644 --- a/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte +++ b/plugins/controlled-documents-resources/src/components/document/EditDocContent.svelte @@ -1,5 +1,5 @@ -{#if $controlledDocument} +{#if $controlledDocument && collaborativeDoc} -
-
- handleShowHeading(ev.detail)} /> + {#if headings.length > 0} +
+
- {#if headings.length > 0} -
- -
-
- {/if} - -
-
- - {#if $isEditable} - { - title = event.detail - }} - on:blur={handleUpdateTitle} - /> - {:else} - {$controlledDocument.title} - {/if} - -
- {#if $sections} - {#each $sections as section, i (section._id)} - -
{ - handleDragStart(ev, section._id) +
+ {/if} + +
+
+ handleShowHeading(ev.detail)} /> +
+ +
+ + {#if $isEditable} + { + title = event.detail }} - on:dragleave|preventDefault={() => { - if (dragOverId === section._id) dragOverId = null - return false - }} - on:dragover|preventDefault={() => { - dragOverId = section._id - return false - }} - on:dragend={resetDrag} - on:drop|preventDefault={(ev) => handleDrop(ev, section._id)} - animate:flip={{ duration: 400 }} - > - -
- - -
{ - openSectionMenu(ev, section) - }} - > - -
-
-
-
- {/each} - {/if} + on:blur={handleUpdateTitle} + /> + {:else} + {$controlledDocument.title} + {/if} + + (editor = e.detail)} + on:open-document={async (event) => { + const doc = await client.findOne(event.detail._class, { _id: event.detail._id }) + if (doc != null) { + const location = await getObjectLinkFragment(client.getHierarchy(), doc, {}, view.component.EditDoc) + navigate(location) + } + }} + attachFile={async (file) => { + return await createEmbedding(file) + }} + />
@@ -226,6 +311,34 @@ @media print { margin-left: -1rem; + overflow: visible; + } + + // Workaround to quickly enumerate headings for controlled docs + :global(h1) { + counter-increment: h1; + counter-reset: h2; + + &::before { + content: counter(h1) '. '; + } + } + + :global(h2) { + counter-increment: h2; + counter-reset: h3; + + &::before { + content: counter(h1) '.' counter(h2) '. '; + } + } + + :global(h3) { + counter-increment: h3; + + &::before { + content: counter(h1) '.' counter(h2) '.' counter(h3) '. '; + } } } @@ -238,44 +351,15 @@ z-index: 1; } - .doc-title { - padding-left: 3.25rem; + .tocContent { + padding-left: 2.25rem; } - .row { - position: relative; - margin-left: 0; - - .draggable-container { - width: 100%; - height: 100%; - - .draggable-mark { - padding: 0.375rem 0.125rem; - &:hover { - background-color: var(--theme-button-hovered); - border-radius: 0.375rem; - cursor: pointer; - } - &.dragging { - cursor: grabbing; - position: relative; - align-self: baseline; - } - } - } - - &:hover { - .draggable-mark { - opacity: 0.9; - } - } + .content { + padding-left: 3.25rem; } .bottomSpacing { padding-bottom: 30vh; } - .drag-over-highlight { - opacity: 0.2; - } diff --git a/plugins/controlled-documents-resources/src/components/document/editors/AbstractEditor.svelte b/plugins/controlled-documents-resources/src/components/document/editors/AbstractEditor.svelte index 20c1e04ac9..6332b4d61b 100644 --- a/plugins/controlled-documents-resources/src/components/document/editors/AbstractEditor.svelte +++ b/plugins/controlled-documents-resources/src/components/document/editors/AbstractEditor.svelte @@ -24,9 +24,10 @@ export let value: Document | undefined export let readonly = true - let abstract = value?.abstract const client = getClient() + $: abstract = value?.abstract + const handleUpdateAbstract = () => { if (readonly) { return diff --git a/plugins/controlled-documents-resources/src/components/document/editors/AttachmentsSectionEditor.svelte b/plugins/controlled-documents-resources/src/components/document/editors/AttachmentsSectionEditor.svelte deleted file mode 100644 index 52bf556a46..0000000000 --- a/plugins/controlled-documents-resources/src/components/document/editors/AttachmentsSectionEditor.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - -{#if withScroll} - -
- -
-
-{:else} - -{/if} diff --git a/plugins/controlled-documents-resources/src/components/document/editors/CollaborativeSectionEditor.svelte b/plugins/controlled-documents-resources/src/components/document/editors/CollaborativeSectionEditor.svelte deleted file mode 100644 index 455713c91e..0000000000 --- a/plugins/controlled-documents-resources/src/components/document/editors/CollaborativeSectionEditor.svelte +++ /dev/null @@ -1,257 +0,0 @@ - - - -{#if collaborativeDoc} - {#key value._id} - (editor = e.detail)} - on:open-document={async (event) => { - const doc = await client.findOne(event.detail._class, { _id: event.detail._id }) - if (doc != null) { - const location = await getObjectLinkFragment(client.getHierarchy(), doc, {}, view.component.EditDoc) - navigate(location) - } - }} - attachFile={async (file) => { - return await createEmbedding(file, value) - }} - /> - {/key} -{/if} diff --git a/plugins/controlled-documents-resources/src/components/document/editors/DescriptionEditor.svelte b/plugins/controlled-documents-resources/src/components/document/editors/DescriptionEditor.svelte deleted file mode 100644 index 24cd72946e..0000000000 --- a/plugins/controlled-documents-resources/src/components/document/editors/DescriptionEditor.svelte +++ /dev/null @@ -1,87 +0,0 @@ - - - -
-