mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
TSK-831: Edit issue fixes (#3140)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
f5bd3edc48
commit
c606a037f1
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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'}
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}}
|
||||
/>
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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'}
|
||||
|
Loading…
Reference in New Issue
Block a user