TSK-831: Edit issue fixes (#3140)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-05-04 20:21:06 +07:00 committed by GitHub
parent f5bd3edc48
commit c606a037f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 151 additions and 30 deletions

View File

@ -95,7 +95,7 @@
</svelte:fragment>
<svelte:fragment slot="utils">
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
<Component is={calendar.component.DocReminder} props={{ value: object, title, focusIndex: 9000 }} />
{#if isUtils && $$slots.utils}
<div class="buttons-divider" />
<slot name="utils" />
@ -145,7 +145,7 @@
{#if !withoutActivity}
<Component
is={activity.component.Activity}
props={{ object, showCommenInput: !withoutInput, shouldScroll: embedded }}
props={{ object, showCommenInput: !withoutInput, shouldScroll: embedded, focusIndex: 1000 }}
/>
{/if}
</div>
@ -156,7 +156,7 @@
{#if !withoutActivity}
<Component
is={activity.component.Activity}
props={{ object, showCommenInput: !withoutInput, shouldScroll: embedded }}
props={{ object, showCommenInput: !withoutInput, shouldScroll: embedded, focusIndex: 1000 }}
/>
{/if}
</div>

View File

@ -15,8 +15,18 @@
<script lang="ts">
import { Asset, IntlString, getResource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { AnySvelteComponent, IconEmoji } from '@hcengineering/ui'
import { Button, EmojiPopup, Icon, Spinner, handler, showPopup, tooltip } from '@hcengineering/ui'
import {
AnySvelteComponent,
Button,
EmojiPopup,
Icon,
IconEmoji,
Spinner,
handler,
registerFocus,
showPopup,
tooltip
} from '@hcengineering/ui'
import { AnyExtension } from '@tiptap/core'
import { createEventDispatcher } from 'svelte'
import { Completion } from '../Completion'
@ -50,6 +60,7 @@
export let placeholder: IntlString | undefined = undefined
export let extraActions: RefAction[] | undefined = undefined
export let loading: boolean = false
const client = getClient()
let textEditor: TextEditor
@ -196,6 +207,24 @@
}
})
}
// Focusable control with index
let focused = false
export let focusIndex = -1
const { idx, focusManager } = registerFocus(focusIndex, {
focus: () => {
focused = true
textEditor.focus()
return textEditor.isEditable()
},
isFocus: () => focused
})
const updateFocus = () => {
if (focusIndex !== -1) {
console.trace('focuse')
focusManager?.setFocus(idx)
}
}
</script>
<div class="ref-container">
@ -292,6 +321,13 @@
textEditor.clear()
}
}}
on:on:blur={() => {
focused = false
}}
on:focus={() => {
focused = true
updateFocus()
}}
extensions={editorExtensions}
on:selection-update={updateFormattingState}
on:update

View File

@ -86,6 +86,9 @@
const dispatch = createEventDispatcher()
let focused = false
export function isFocused (): boolean {
return focused
}
let needFocus = false
$: if (textEditor && needFocus) {
@ -101,8 +104,9 @@
export let focusIndex = -1
const { idx, focusManager } = registerFocus(focusIndex, {
focus: () => {
focused = true
focus()
return false
return textEditor.isEditable()
},
isFocus: () => focused
})

View File

@ -14,12 +14,12 @@
// limitations under the License.
//
import { SvelteComponent } from 'svelte'
import { ComponentType, SvelteComponent } from 'svelte'
export class SvelteRenderer {
private readonly component: SvelteComponent
constructor (comp: typeof SvelteComponent, props: any) {
constructor (comp: typeof SvelteComponent | ComponentType, props: any) {
const options = { target: document.body, props }
this.component = new (comp as any)(options)
}

View File

@ -16,15 +16,15 @@
<script lang="ts">
import { IntlString, translate } from '@hcengineering/platform'
import { AnyExtension, Editor, Extension, HTMLContent } from '@tiptap/core'
import type { FocusPosition } from '@tiptap/core'
import { AnyExtension, Editor, Extension, HTMLContent } from '@tiptap/core'
// import Typography from '@tiptap/extension-typography'
import { Level } from '@tiptap/extension-heading'
import Placeholder from '@tiptap/extension-placeholder'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import textEditorPlugin from '../plugin'
import { FormatMode } from '../types'
import { defaultExtensions } from './extensions'
import { Level } from '@tiptap/extension-heading'
export let content: string = ''
export let placeholder: IntlString = textEditorPlugin.string.EditorPlaceholder

View File

@ -14,16 +14,16 @@
-->
<script lang="ts">
import { afterUpdate, createEventDispatcher } from 'svelte'
import { deviceOptionsStore as deviceInfo } from '../../'
import { resizeObserver } from '../resize'
import Button from './Button.svelte'
import Scroller from './Scroller.svelte'
import IconClose from './icons/Close.svelte'
import IconDetails from './icons/Details.svelte'
import IconMaxWidth from './icons/MaxWidth.svelte'
import IconMinWidth from './icons/MinWidth.svelte'
import IconScale from './icons/Scale.svelte'
import IconScaleFull from './icons/ScaleFull.svelte'
import IconMinWidth from './icons/MinWidth.svelte'
import IconMaxWidth from './icons/MaxWidth.svelte'
import Scroller from './Scroller.svelte'
import { deviceOptionsStore as deviceInfo } from '../../'
export let innerWidth: number = 0
export let panelWidth: number = 0
@ -93,6 +93,7 @@
<div class="popupPanel-title {twoRows && !withoutTitle ? 'row-top' : 'row'}">
{#if allowClose && !embedded}
<Button
focusIndex={10000}
icon={IconClose}
kind={'transparent'}
size={'medium'}
@ -114,6 +115,7 @@
{/if}
{#if $$slots.aside && isAside}
<Button
focusIndex={10008}
icon={IconDetails}
kind={'transparent'}
size={'medium'}
@ -125,6 +127,7 @@
{/if}
{#if useMaxWidth !== undefined}
<Button
focusIndex={10009}
icon={useMaxWidth ? IconMaxWidth : IconMinWidth}
kind={'transparent'}
size={'medium'}
@ -137,6 +140,7 @@
{/if}
{#if isFullSize}
<Button
focusIndex={100010}
icon={fullSize ? IconScale : IconScaleFull}
kind={'transparent'}
size={'medium'}

View File

@ -29,6 +29,7 @@
export let showCommenInput: boolean = true
export let transparent: boolean = false
export let shouldScroll: boolean = false
export let focusIndex: number = -1
getResource(notification.function.GetNotificationClient).then((res) => {
updatesStore = res().docUpdatesStore
@ -134,7 +135,7 @@
</div>
{#if showCommenInput}
<div class="ref-input">
<Component is={chunter.component.CommentInput} props={{ object }} />
<Component is={chunter.component.CommentInput} props={{ object, focusIndex }} />
</div>
{/if}

View File

@ -35,6 +35,7 @@
export let shouldSaveDraft: boolean = false
export let attachments: IdMap<Attachment> = new Map()
export let loading = false
export let focusIndex: number = -1
export function submit (): void {
refInput.submit()
}
@ -278,6 +279,7 @@
</div>
{/if}
<ReferenceInput
{focusIndex}
bind:this={refInput}
{content}
{iconSend}

View File

@ -40,7 +40,7 @@
export let refContainer: HTMLElement | undefined = undefined
export let shouldSaveDraft: boolean = false
export let useAttachmentPreview = false
export let focusIndex: number | undefined = undefined
export let focusIndex: number | undefined = -1
let draftKey = objectId ? `${objectId}_attachments` : undefined
$: draftKey = objectId ? `${objectId}_attachments` : undefined
@ -50,6 +50,9 @@
export function focus (): void {
refInput.focus()
}
export function isFocused (): boolean {
return refInput.isFocused()
}
export function isEditable (): boolean {
return refInput.isEditable()
}

View File

@ -23,6 +23,7 @@
export let object: Doc
export let shouldSaveDraft: boolean = true
export let focusIndex: number = -1
const client = getClient()
const _class = chunter.class.Comment
@ -108,6 +109,7 @@
</script>
<AttachmentRefInput
{focusIndex}
bind:this={commentInputBox}
bind:content={inputContent}
{_class}

View File

@ -24,15 +24,17 @@
import {
Button,
EditBox,
FocusHandler,
IconMixin,
IconMoreH,
Label,
Spinner,
createFocusManager,
getCurrentResolvedLocation,
navigate,
showPopup
} from '@hcengineering/ui'
import { ContextMenu, DocNavLink, UpDownNavigator } from '@hcengineering/view-resources'
import { ActionContext, ContextMenu, DocNavLink, UpDownNavigator } from '@hcengineering/view-resources'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { generateIssueShortLink, getIssueId } from '../../../issues'
import tracker from '../../../plugin'
@ -139,8 +141,23 @@
onMount(() => {
dispatch('open', { ignoreKeys: ['comments', 'name', 'description', 'number'] })
})
const manager = createFocusManager()
export function canClose (): boolean {
if (descriptionBox.isFocused()) {
return false
}
return true
}
</script>
<FocusHandler {manager} />
<ActionContext
context={{
mode: 'editor'
}}
/>
{#if issue !== undefined}
<Panel
object={issue}
@ -181,10 +198,17 @@
{/if}
</div>
{/if}
<EditBox bind:value={title} placeholder={tracker.string.IssueTitlePlaceholder} kind="large-style" on:blur={save} />
<EditBox
focusIndex={1}
bind:value={title}
placeholder={tracker.string.IssueTitlePlaceholder}
kind="large-style"
on:blur={save}
/>
<div class="w-full mt-6">
{#key issue._id}
<AttachmentStyledBox
focusIndex={30}
bind:this={descriptionBox}
useAttachmentPreview={true}
objectId={_id}
@ -207,7 +231,12 @@
<div class="mt-6">
{#key issue._id && currentProject !== undefined}
{#if currentProject !== undefined}
<SubIssues {issue} shouldSaveDraft projects={new Map([[currentProject?._id, currentProject]])} />
<SubIssues
focusIndex={50}
{issue}
shouldSaveDraft
projects={new Map([[currentProject?._id, currentProject]])}
/>
{/if}
{/key}
</div>

View File

@ -15,6 +15,7 @@
<script lang="ts">
import { Doc, DocumentQuery, Ref } from '@hcengineering/core'
import { Issue, Project } from '@hcengineering/tracker'
import { registerFocus } from '@hcengineering/ui'
import { ViewOptions, Viewlet } from '@hcengineering/view'
import {
ActionContext,
@ -42,6 +43,25 @@
list?.select(offset, of)
}
})
let docs: Doc[] = []
function select () {
listProvider.update(docs)
listProvider.updateFocus(docs[0])
list?.select(0, undefined)
}
// Focusable control with index
let focused = false
export let focusIndex = -1
registerFocus(focusIndex, {
focus: () => {
;(window.document.activeElement as HTMLElement).blur()
focused = true
select()
return true
},
isFocus: () => focused
})
</script>
<ActionContext
@ -70,6 +90,7 @@
listProvider.updateSelection(event.detail.docs, event.detail.value)
}}
on:content={(evt) => {
docs = evt.detail
listProvider.update(evt.detail)
}}
/>

View File

@ -70,6 +70,8 @@
}
$: viewOptions = viewlet !== undefined ? getViewOptions(viewlet, $viewOptionStore) : undefined
export let focusIndex = -1
</script>
<div class="flex-between">
@ -139,7 +141,13 @@
{#if !isCollapsed}
<ExpandCollapse isExpanded={!isCollapsed}>
<div class="list" class:collapsed={isCollapsed}>
<SubIssueList projects={_projects} {viewlet} {viewOptions} query={{ attachedTo: issue._id }} />
<SubIssueList
focusIndex={focusIndex === -1 ? -1 : focusIndex + 1}
projects={_projects}
{viewlet}
{viewOptions}
query={{ attachedTo: issue._id }}
/>
</div>
</ExpandCollapse>
{/if}

View File

@ -156,11 +156,12 @@ async function Open (
| undefined
): Promise<void> {
evt.preventDefault()
const d = Array.isArray(doc) ? doc[0] : doc
const client = getClient()
const hierarchy = client.getHierarchy()
const panelComponent = hierarchy.classHierarchyMixin(doc._class, view.mixin.ObjectPanel)
const panelComponent = hierarchy.classHierarchyMixin(d._class, view.mixin.ObjectPanel)
const component = props?.component ?? panelComponent?.component ?? view.component.EditDoc
const loc = await getObjectLinkFragment(hierarchy, doc, {}, component)
const loc = await getObjectLinkFragment(hierarchy, d, {}, component)
navigate(loc)
}
@ -180,15 +181,12 @@ function ShowPanel (
rightSection?: AnyComponent
}
): void {
if (Array.isArray(doc)) {
console.error('Wrong show Panel parameters')
return
}
const d = Array.isArray(doc) ? doc[0] : doc
evt.preventDefault()
showPanel(
props.component ?? view.component.EditDoc,
doc._id,
Hierarchy.mixinOrClass(doc),
d._id,
Hierarchy.mixinOrClass(d),
props.element ?? 'content',
props.rightSection
)

View File

@ -39,9 +39,22 @@
$: select(undefined, 0, element, 'vertical')
</script>
<Button icon={IconDownOutline} kind={'secondary'} size={'medium'} on:click={(evt) => next(evt, true)} />
<Button icon={IconUpOutline} kind={'secondary'} size={'medium'} on:click={(evt) => next(evt, false)} />
<Button
focusIndex={10005}
icon={IconDownOutline}
kind={'secondary'}
size={'medium'}
on:click={(evt) => next(evt, true)}
/>
<Button
focusIndex={10006}
icon={IconUpOutline}
kind={'secondary'}
size={'medium'}
on:click={(evt) => next(evt, false)}
/>
<Button
focusIndex={10007}
showTooltip={{ label: ui.string.Back, direction: 'bottom' }}
icon={IconNavPrev}
kind={'secondary'}