Document fixes (#2319)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-10-23 23:33:16 +07:00 committed by GitHub
parent 427337a88a
commit 0097da2710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 413 additions and 91 deletions

View File

@ -34,6 +34,7 @@ import { trackerOperation } from '@hcengineering/model-tracker'
import { boardOperation } from '@hcengineering/model-board'
import { demoOperation } from '@hcengineering/model-demo'
import { hrOperation } from '@hcengineering/model-hr'
import { documentOperation } from '@hcengineering/model-document'
export const migrateOperations: MigrateOperation[] = [
coreOperation,
@ -54,5 +55,6 @@ export const migrateOperations: MigrateOperation[] = [
settingOperation,
trackerOperation,
boardOperation,
hrOperation
hrOperation,
documentOperation
]

View File

@ -39,6 +39,7 @@
"@hcengineering/document": "^0.6.0",
"@hcengineering/document-resources": "^0.6.0",
"@hcengineering/view": "^0.6.1",
"@hcengineering/contact": "~0.6.5"
"@hcengineering/contact": "~0.6.5",
"@hcengineering/tags": "~0.6.2"
}
}

View File

@ -13,11 +13,12 @@
// limitations under the License.
//
import { Employee } from '@hcengineering/contact'
import contact, { Employee } from '@hcengineering/contact'
import type { Class, Domain, Markup, Ref } from '@hcengineering/core'
import { IndexKind } from '@hcengineering/core'
import { Document, documentId, DocumentVersion, RichDocumentContent, Step } from '@hcengineering/document'
import {
ArrOf,
Builder,
Collection,
Hidden,
@ -36,17 +37,20 @@ import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
import presentation from '@hcengineering/model-presentation'
import view, { actionTemplates, createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import tags from '@hcengineering/tags'
import document from './plugin'
export const DOMAIN_DOCUMENT = 'document' as Domain
@Model(document.class.RichDocumentContent, core.class.AttachedDoc, DOMAIN_DOCUMENT)
@UX(document.string.Document)
export class TRichDocumentContent extends TAttachedDoc implements RichDocumentContent {
steps!: Step[]
version!: number
}
@Model(document.class.DocumentVersion, core.class.AttachedDoc, DOMAIN_DOCUMENT)
@UX(document.string.Version)
export class TDocumentVersion extends TAttachedDoc implements DocumentVersion {
@Prop(TypeNumber(), document.string.Version)
@ReadOnly()
@ -79,6 +83,9 @@ export class TDocument extends TDoc implements Document {
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number
@Prop(Collection(tags.class.TagReference), document.string.Labels)
labels?: number
@Prop(TypeRef(core.class.Class), core.string.ClassLabel)
declare _class: Ref<Class<this>>
@ -98,35 +105,92 @@ export class TDocument extends TDoc implements Document {
@ReadOnly()
@Hidden()
versionCounter!: number
@Prop(ArrOf(TypeRef(contact.class.Employee)), document.string.Responsible)
responsible!: Ref<Employee>[]
}
export function createModel (builder: Builder): void {
builder.createModel(TDocument, TRichDocumentContent, TDocumentVersion)
builder.mixin(document.class.Document, core.class.Class, view.mixin.ObjectFactory, {
component: document.component.CreateDocument
})
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: document.string.Documents,
label: document.string.DocumentApplication,
icon: document.icon.DocumentApplication,
alias: documentId,
hidden: false,
component: document.component.Documents
navigatorModel: {
specials: [
{
id: 'my-documents',
position: 'top',
label: document.string.MyDocuments,
icon: document.icon.Document,
component: document.component.MyDocuments
},
{
id: 'library',
position: 'top',
label: document.string.Library,
icon: document.icon.Document,
component: document.component.Documents
}
],
spaces: []
},
navHeaderComponent: document.component.NewDocumentHeader
},
document.app.Documents
)
builder.mixin(document.class.Document, core.class.Class, view.mixin.ObjectFactory, {
component: document.component.CreateDocument
})
builder.mixin(document.class.Document, core.class.Class, view.mixin.ClassFilters, {
filters: ['_class', 'modifiedOn']
})
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: document.class.Document,
descriptor: view.viewlet.Table,
config: ['', 'comments', 'attachments', 'modifiedOn'],
config: [
'',
{
key: '',
presenter: document.component.Status,
label: document.string.Status
},
{
key: '',
presenter: document.component.Version,
label: document.string.Version
},
{
key: '',
presenter: document.component.Revision,
label: document.string.Revision
},
'comments',
'attachments',
{
key: '',
presenter: tags.component.TagsPresenter,
label: document.string.Labels,
sortingKey: 'labels',
props: {
_class: document.class.Document,
key: 'labels',
icon: document.icon.Document
}
},
'modifiedOn'
],
options: {
lookup: {
_id: {
@ -173,5 +237,5 @@ export function createModel (builder: Builder): void {
})
}
export { contactOperation } from './migration'
export { documentOperation } from './migration'
export { document as default }

View File

@ -14,8 +14,9 @@
//
import { TxOperations } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import { createOrUpdate, MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import tags from '@hcengineering/tags'
import document from './index'
async function createSpace (tx: TxOperations): Promise<void> {
@ -38,10 +39,24 @@ async function createSpace (tx: TxOperations): Promise<void> {
}
}
export const contactOperation: MigrateOperation = {
export const documentOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
await createOrUpdate(
tx,
tags.class.TagCategory,
tags.space.Tags,
{
icon: tags.icon.Tags,
label: 'Labels',
targetClass: document.class.Document,
tags: [],
default: true
},
document.category.Other
)
}
}

View File

@ -19,6 +19,7 @@ import { documentId } from '@hcengineering/document'
import document from '@hcengineering/document-resources/src/plugin'
import { ObjectSearchCategory, ObjectSearchFactory } from '@hcengineering/model-presentation'
import { mergeIds, Resource } from '@hcengineering/platform'
import { TagCategory } from '@hcengineering/tags'
import { AnyComponent } from '@hcengineering/ui'
import { ActionCategory } from '@hcengineering/view'
@ -34,7 +35,8 @@ export default mergeIds(documentId, document, {
DocumentQueryCategory: '' as Ref<ObjectSearchCategory>
},
category: {
Document: '' as Ref<ActionCategory>
Document: '' as Ref<ActionCategory>,
Other: '' as Ref<TagCategory>
},
viewlet: {
TableDocument: '' as Ref<Doc>

View File

@ -17,6 +17,16 @@
"CreateDocumentVersion": "Review content version",
"ApprovedBy": "Approved by",
"Status": "Status",
"LastRevision": "Last revision"
"LastRevision": "Last revision",
"DocumentApplication": "Documents",
"MyDocuments": "My documents",
"Library": "Library",
"Labels": "Labels",
"AddLabel": "Add label...",
"Responsible": "Responsible",
"PendingReview": "Pending",
"Latest": "Actual",
"Draft": "Draft"
}
}

View File

@ -17,6 +17,16 @@
"CreateDocumentVersion": "Оцените версию документа",
"ApprovedBy": "Подтверждено",
"Status": "Статус",
"LastRevision": "Последняя ревизия"
}
"LastRevision": "Последняя ревизия",
"DocumentApplication": "Документы",
"MyDocuments": "Мои документы",
"Library": "Библиотека",
"Labels": "Метки",
"AddLabel": "Добавить метку...",
"Responsible": "Ответственные",
"PendingReview": "Ожидает",
"Latest": "Актуальная",
"Draft": "Драфт"
}
}

View File

@ -59,6 +59,8 @@
"emoji-regex": "^10.1.0",
"prosemirror-collab": "~1.3.0",
"prosemirror-state": "~1.4.1",
"prosemirror-transform": "~1.7.0"
"prosemirror-transform": "~1.7.0",
"@hcengineering/contact": "~0.6.5",
"@hcengineering/tags": "~0.6.2"
}
}

View File

@ -15,7 +15,8 @@
//
-->
<script lang="ts">
import { Data, generateId } from '@hcengineering/core'
import { EmployeeAccount } from '@hcengineering/contact'
import { Data, generateId, getCurrentAccount } from '@hcengineering/core'
import { Document } from '@hcengineering/document'
import { Card, getClient } from '@hcengineering/presentation'
import { Button, createFocusManager, EditBox, FocusHandler } from '@hcengineering/ui'
@ -27,6 +28,7 @@
}
const id = generateId()
const currentUser = getCurrentAccount() as EmployeeAccount
const object: Data<Document> = {
name: '',
@ -36,7 +38,8 @@
attachments: 0,
labels: 0,
comments: 0,
versionCounter: 0
versionCounter: 0,
responsible: [currentUser.employee]
}
const dispatch = createEventDispatcher()

View File

@ -16,26 +16,12 @@
-->
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Document, DocumentVersion } from '@hcengineering/document'
import { getPanelURI, Icon, Label } from '@hcengineering/ui'
import { Document } from '@hcengineering/document'
import { getPanelURI, Icon } from '@hcengineering/ui'
import document from '../plugin'
export let value: WithLookup<Document>
export let inline = false
let lastVersion: DocumentVersion | undefined
$: if (value.$lookup?.versions !== undefined) {
let vv = -1
let dv: DocumentVersion | undefined
for (const v of value.$lookup.versions as DocumentVersion[]) {
if (v.version > vv) {
dv = v
vv = v.version
}
}
lastVersion = dv
}
</script>
{#if value}
@ -48,17 +34,5 @@
<Icon icon={document.icon.Document} size={'small'} />
</div>
<span class="label">{value.name}</span>
<span class="ml-1">
{#if lastVersion}
- {lastVersion.version}
{#if lastVersion.sequenceNumber !== value.editSequence}
(<Label label={document.string.Revision} /> {lastVersion.sequenceNumber} / {value.editSequence})
{:else}
(<Label label={document.string.Revision} /> {value.editSequence})
{/if}
{:else}
(<Label label={document.string.Revision} /> {value.editSequence})
{/if}
</span>
</a>
{/if}

View File

@ -16,27 +16,20 @@
-->
<script lang="ts">
import { Doc, DocumentQuery } from '@hcengineering/core'
import { Document } from '@hcengineering/document'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
Button,
deviceOptionsStore as deviceInfo,
Icon,
IconAdd,
Label,
Loading,
SearchEdit,
showPopup
} from '@hcengineering/ui'
import { deviceOptionsStore as deviceInfo, Icon, Label, Loading, SearchEdit } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { ActionContext, FilterButton, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
import document from '../plugin'
import CreateDocument from './CreateDocument.svelte'
export let query: DocumentQuery<Document> = {}
let search = ''
let resultQuery: DocumentQuery<Doc> = {}
let resultQuery: DocumentQuery<Doc> = query
function updateResultQuery (search: string): void {
resultQuery = search === '' ? {} : { $search: search }
function updateResultQuery (search: string, query: DocumentQuery<Document>): void {
resultQuery = search === '' ? { ...query } : { ...query, $search: search }
}
let viewlet: Viewlet | undefined
@ -68,10 +61,6 @@
}
})
function showCreateDialog (ev: Event) {
showPopup(CreateDocument, { space: document.space.Documents, targetElement: ev.target }, ev.target as HTMLElement)
}
let twoRows: boolean
$: twoRows = $deviceInfo.docWidth <= 680
</script>
@ -93,18 +82,11 @@
<SearchEdit
bind:value={search}
on:change={() => {
updateResultQuery(search)
updateResultQuery(search, query)
}}
/>
</div>
<div class="ac-header-full" class:secondRow={twoRows}>
<Button
icon={IconAdd}
label={document.string.DocumentCreateLabel}
kind={'primary'}
size={'small'}
on:click={(ev) => showCreateDialog(ev)}
/>
<ViewletSettingButton {viewlet} />
</div>
</div>

View File

@ -22,12 +22,13 @@
import { Panel } from '@hcengineering/panel'
import { getResource } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import {
Button,
Component,
EditBox,
eventToHTMLElement,
Grid,
IconAdd,
IconEdit,
IconMoreH,
@ -279,7 +280,18 @@
<div class="divider" />
<Grid column={2} rowGap={1}>
<div class="tab-content">
<span class="label labelTop">
<Label label={document.string.Labels} />
</span>
<div class="flex">
<Component
is={tags.component.TagsAttributeEditor}
props={{ object: documentObject, label: document.string.AddLabel }}
/>
</div>
<div class="divider" />
<span class="label">
<Label label={document.string.LastRevision} />
</span>
@ -289,7 +301,7 @@
<Label label={document.string.Versions} />
</span>
<span>{documentObject?.versions}</span>
</Grid>
</div>
</svelte:fragment>
</Panel>
{/if}
@ -301,10 +313,6 @@
color: var(--theme-caption-color);
}
.content {
height: auto;
}
.description-preview {
color: var(--theme-content-color);
line-height: 150%;
@ -313,13 +321,6 @@
color: var(--theme-content-trans-color);
}
}
.divider {
margin-top: 1rem;
margin-bottom: 1rem;
grid-column: 1 / 3;
height: 1px;
background-color: var(--divider-color);
}
.tool {
align-self: start;
@ -331,4 +332,25 @@
opacity: 1;
}
}
.tab-content {
display: grid;
grid-template-columns: 1fr 1.5fr;
grid-auto-flow: row;
justify-content: start;
align-items: center;
gap: 1rem;
margin-top: 1rem;
width: 100%;
height: min-content;
}
.divider {
grid-column: 1 / 3;
height: 1px;
background-color: var(--divider-color);
}
.labelTop {
align-self: start;
}
</style>

View File

@ -0,0 +1,9 @@
<script lang="ts">
import { EmployeeAccount } from '@hcengineering/contact'
import { getCurrentAccount } from '@hcengineering/core'
import Documents from './Documents.svelte'
const currentUser = getCurrentAccount() as EmployeeAccount
</script>
<Documents query={{ responsible: currentUser.employee }} />

View File

@ -0,0 +1,35 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Button, showPopup } from '@hcengineering/ui'
import document from '../plugin'
import CreateDocument from './CreateDocument.svelte'
async function newDocument (): Promise<void> {
showPopup(CreateDocument, {}, 'top')
}
</script>
<div class="antiNav-subheader gap-2">
<div class="flex-grow text-md">
<Button
icon={document.icon.Document}
label={document.string.CreateDocument}
justify={'left'}
width={'100%'}
on:click={newDocument}
/>
</div>
</div>

View File

@ -0,0 +1,45 @@
<!--
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Document, DocumentVersion } from '@hcengineering/document'
export let value: WithLookup<Document>
let lastVersion: DocumentVersion | undefined
$: if (value.$lookup?.versions !== undefined) {
let vv = -1
let dv: DocumentVersion | undefined
for (const v of value.$lookup.versions as DocumentVersion[]) {
if (v.version > vv) {
dv = v
vv = v.version
}
}
lastVersion = dv
}
</script>
{#if value}
<div class="flex-row-center">
{#if lastVersion}
{lastVersion.sequenceNumber}<span class="p-1">/</span>
{/if}
{value.editSequence}
</div>
{/if}

View File

@ -0,0 +1,78 @@
<!--
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Document, DocumentVersion } from '@hcengineering/document'
import { EastSideColor, FernColor, Label, SeaBuckthornColor } from '@hcengineering/ui'
import document from '../plugin'
export let value: WithLookup<Document>
let lastVersion: DocumentVersion | undefined
$: if (value.$lookup?.versions !== undefined) {
let vv = -1
let dv: DocumentVersion | undefined
for (const v of value.$lookup.versions as DocumentVersion[]) {
if (v.version > vv) {
dv = v
vv = v.version
}
}
lastVersion = dv
}
</script>
{#if value}
<div class="status-container">
{#if value.versions === 0}
<div class="status p-1" style:background-color={EastSideColor}>
<Label label={document.string.Draft} />
</div>
{/if}
{#if value.editSequence !== lastVersion?.sequenceNumber}
<div class="status p-1" style:background-color={SeaBuckthornColor}>
<Label label={document.string.PendingReview} />
</div>
{/if}
{#if value.editSequence === lastVersion?.sequenceNumber}
<div class="status p-1" style:background-color={FernColor}>
<Label label={document.string.Latest} />
</div>
{/if}
</div>
{/if}
<style lang="scss">
.status-container {
display: flex;
.status {
border-radius: 8px;
margin: 0.1rem;
font-weight: 500;
font-size: 10px;
text-transform: uppercase;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,40 @@
<!--
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Document, DocumentVersion } from '@hcengineering/document'
export let value: WithLookup<Document>
let lastVersion: DocumentVersion | undefined
$: if (value.$lookup?.versions !== undefined) {
let vv = -1
let dv: DocumentVersion | undefined
for (const v of value.$lookup.versions as DocumentVersion[]) {
if (v.version > vv) {
dv = v
vv = v.version
}
}
lastVersion = dv
}
</script>
{#if lastVersion}
{lastVersion.version}
{/if}

View File

@ -22,9 +22,14 @@ import CreateDocument from './components/CreateDocument.svelte'
import DocumentPresenter from './components/DocumentPresenter.svelte'
import DocumentVersionPresenter from './components/DocumentVersionPresenter.svelte'
import Documents from './components/Documents.svelte'
import MyDocuments from './components/MyDocuments.svelte'
import DocumentVersions from './components/DocumentVersions.svelte'
import EditDoc from './components/EditDoc.svelte'
import DocumentItem from './components/DocumentItem.svelte'
import NewDocumentHeader from './components/NewDocumentHeader.svelte'
import Status from './components/Status.svelte'
import Revision from './components/Revision.svelte'
import Version from './components/Version.svelte'
import document from './plugin'
@ -70,7 +75,12 @@ export default async (): Promise<Resources> => ({
Documents,
EditDoc,
DocumentVersions,
DocumentVersionPresenter
DocumentVersionPresenter,
NewDocumentHeader,
MyDocuments,
Status,
Revision,
Version
},
completion: {
DocumentQuery: async (

View File

@ -19,7 +19,12 @@ import { AnyComponent } from '@hcengineering/ui'
export default mergeIds(documentId, document, {
component: {
EditDoc: '' as AnyComponent
EditDoc: '' as AnyComponent,
MyDocuments: '' as AnyComponent,
NewDocumentHeader: '' as AnyComponent,
Status: '' as AnyComponent,
Version: '' as AnyComponent,
Revision: '' as AnyComponent
},
string: {
Apply: '' as IntlString,
@ -39,6 +44,16 @@ export default mergeIds(documentId, document, {
CreateDocumentVersion: '' as IntlString,
ApprovedBy: '' as IntlString,
Status: '' as IntlString,
LastRevision: '' as IntlString
LastRevision: '' as IntlString,
DocumentApplication: '' as IntlString,
MyDocuments: '' as IntlString,
Library: '' as IntlString,
Labels: '' as IntlString,
Responsible: '' as IntlString,
AddLabel: '' as IntlString,
PendingReview: '' as IntlString,
Latest: '' as IntlString,
Draft: '' as IntlString
}
})

View File

@ -58,6 +58,9 @@ export interface Document extends Doc {
attachments?: number
comments?: number
labels?: number
// List of reponsible persons, for document.
responsible: Ref<Employee>[]
}
/**