Files
huly-platform/plugins/controlled-documents-resources/src/components/document/DocSectionEditor.svelte
T

216 lines
7.1 KiB
Svelte

<!--
// Copyright © 2022-2023 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 chunter from '@hcengineering/chunter'
import { getClient } from '@hcengineering/presentation'
import { isEmptyMarkup } from '@hcengineering/text'
import { Heading } from '@hcengineering/text-editor'
import { Button, Component, EditBox } from '@hcengineering/ui'
import documents, { Document, DocumentSection, DocumentTemplateSection } from '@hcengineering/controlled-documents'
import Info from '../icons/Info.svelte'
import {
$documentCommentHighlightedLocation as highlighted,
$canAddDocumentComments as canAddDocumentComments,
$canViewDocumentComments as canViewDocumentComments,
$collapsedDocumentSectionIds as collapsedSectionIds,
$documentSectionEditingDescription as documentSectionEditingDescription,
$groupedDocumentComments as groupedDocumentComments,
$isEditable as isEditable,
documentCommentsDisplayRequested,
documentSectionToggled,
showAddCommentPopupFx,
updateDocumentSectionDescriptionFx
} from '../../stores/editors/document'
import { openGuidanceEditor } from '../../utils'
import FieldSectionEditor from '../FieldSectionEditor.svelte'
import DescriptionEditor from './editors/DescriptionEditor.svelte'
export let document: Document
export let value: DocumentSection
export let index: number
export let dragging = false
export let headings: Heading[] = [] // out
const client = getClient()
const h = client.getHierarchy()
$: editor = h.as(h.getClass(value._class), documents.mixin.DocumentSectionEditor)?.editor
let sectionHasGuidance = false
let templateSection: DocumentTemplateSection | undefined
$: getDocumentTemplateSection(value)
async function getDocumentTemplateSection (section: DocumentSection) {
if (h.hasMixin(value, documents.mixin.DocumentTemplateSection)) {
templateSection = h.as(value, documents.mixin.DocumentTemplateSection)
} else if (value.templateSectionId != null) {
templateSection = await client.findOne(documents.mixin.DocumentTemplateSection, { _id: value.templateSectionId })
}
if (templateSection) {
const guidance = templateSection.guidance
sectionHasGuidance = !isEmptyMarkup(guidance)
}
}
let contentHeadings: Heading[] = []
$: sectionHeading = makeSectionHeading(value, index)
$: headings = [sectionHeading, ...contentHeadings]
function handleSectionContentHeadings (headings: Heading[]): void {
contentHeadings = headings
}
function makeSectionHeading (section: DocumentSection, index: number): Heading {
index = index + 1
const id = `section-${index}`
const title = `${index}. ${section.title}`
return { id, title, level: 0 }
}
async function handleOpenAddCommentPopup (ev?: Event): Promise<void> {
if (!$canAddDocumentComments) {
return
}
await showAddCommentPopupFx({
element: (ev as MouseEvent).target as HTMLElement,
sectionKey: value.key
})
}
function handleDisplayDocumentComments (ev?: Event): void {
if (!$canViewDocumentComments) {
return
}
if (!$groupedDocumentComments.hasDocumentComments(value.key)) {
return
}
ev?.stopPropagation()
documentCommentsDisplayRequested({ element: (ev as MouseEvent).target as HTMLElement, sectionKey: value.key })
}
async function updateSectionTitle (title: string) {
await client.update(value, { title })
}
let sectionElement: HTMLElement
export function getSectionElement (): HTMLElement {
return sectionElement
}
let title = value.title
let descr = ''
$: getDescription(templateSection)
$: isActiveSectionNode = !!$highlighted && $highlighted.sectionKey === value.key && !$highlighted.nodeId
function getDescription (section?: DocumentTemplateSection) {
if (section != null) {
descr = h.as(section, documents.mixin.DocumentTemplateSection).description ?? ''
}
}
$: isEditingDescription = $documentSectionEditingDescription === value._id
async function stopEditingDescription () {
if (!templateSection) {
return
}
updateDocumentSectionDescriptionFx({ section: templateSection, description: descr })
}
function showGuidance (ev: MouseEvent) {
if (templateSection == null) {
return
}
const isEditingTemplate = templateSection._id === value._id
openGuidanceEditor(client, templateSection, index + 1, isEditingTemplate ? 'canEdit' : 'readonly', ev)
}
</script>
<FieldSectionEditor
editable={$isEditable}
expanded={!$collapsedSectionIds.has(value._id)}
on:toggle={() => documentSectionToggled(value._id)}
>
<svelte:fragment slot="before-header">
<slot name="before-header" />
</svelte:fragment>
<svelte:fragment slot="index">
<span class="sectionTitle">
{index + 1}
</span>
</svelte:fragment>
<svelte:fragment slot="header">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
id={sectionHeading.id}
class:text-editor-highlighted-node-warning={isActiveSectionNode ||
$groupedDocumentComments.hasDocumentComments(value.key)}
class:text-editor-highlighted-node-selected={isActiveSectionNode}
class="sectionTitle"
bind:this={sectionElement}
on:click={handleDisplayDocumentComments}
>
{#if $isEditable && !dragging}
<EditBox maxWidth="100%" bind:value={title} propagateClick on:change={() => updateSectionTitle(title)} />
{:else}
{title}
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="header-extra">
<div class="flex-row-center ml-2 mr-10">
{#if $canAddDocumentComments}
<Button icon={chunter.icon.Chunter} kind="list-header" size="small" on:click={handleOpenAddCommentPopup} />
{/if}
{#if templateSection && sectionHasGuidance}
<Button icon={Info} kind="list-header" size="small" on:click={showGuidance} />
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="before-content">
{#if isEditingDescription || descr.length > 0}
<div class="section-descr no-print">
<DescriptionEditor bind:value={descr} disabled={!isEditingDescription} on:blur={stopEditingDescription} />
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="content">
<Component
is={editor}
props={{
value,
document,
onHeadings: handleSectionContentHeadings
}}
on:change
/>
</svelte:fragment>
</FieldSectionEditor>
<style lang="scss">
.sectionTitle {
font-size: 1.75rem;
line-height: 150%;
}
</style>