mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-22 23:51:46 +00:00
Markup editor updates and few more little fixes (#2502)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
8fa7c938da
commit
0bcd74fa25
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -21,7 +21,7 @@
|
|||||||
"MINIO_SECRET_KEY": "minioadmin",
|
"MINIO_SECRET_KEY": "minioadmin",
|
||||||
"SERVER_SECRET": "secret",
|
"SERVER_SECRET": "secret",
|
||||||
"REKONI_URL": "http://localhost:4004",
|
"REKONI_URL": "http://localhost:4004",
|
||||||
"OPENAI_TOKEN": "",
|
// "OPENAI_TOKEN": "",
|
||||||
// "RETRANSLATE_URL": "http://127.0.0.1:4500",
|
// "RETRANSLATE_URL": "http://127.0.0.1:4500",
|
||||||
//"RETRANSLATE_URL": "https://208.167.249.201",
|
//"RETRANSLATE_URL": "https://208.167.249.201",
|
||||||
// "RETRANSLATE_TOKEN": ""
|
// "RETRANSLATE_TOKEN": ""
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -317,13 +317,15 @@ export function createModel (builder: Builder): void {
|
|||||||
classPresenter(
|
classPresenter(
|
||||||
builder,
|
builder,
|
||||||
core.class.TypeMarkup,
|
core.class.TypeMarkup,
|
||||||
view.component.HTMLPresenter,
|
view.component.MarkupPresenter,
|
||||||
view.component.StringEditor,
|
view.component.MarkupEditor,
|
||||||
view.component.StringEditorPopup
|
view.component.MarkupEditorPopup
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.mixin(core.class.TypeMarkup, core.class.Class, view.mixin.InlineAttributEditor, {
|
builder.mixin(core.class.TypeMarkup, core.class.Class, view.mixin.InlineAttributEditor, {
|
||||||
editor: view.component.HTMLEditor
|
editor: view.component.HTMLEditor
|
||||||
})
|
})
|
||||||
|
|
||||||
classPresenter(builder, core.class.TypeBoolean, view.component.BooleanPresenter, view.component.BooleanEditor)
|
classPresenter(builder, core.class.TypeBoolean, view.component.BooleanPresenter, view.component.BooleanEditor)
|
||||||
classPresenter(builder, core.class.TypeTimestamp, view.component.TimestampPresenter)
|
classPresenter(builder, core.class.TypeTimestamp, view.component.TimestampPresenter)
|
||||||
classPresenter(builder, core.class.TypeDate, view.component.DatePresenter, view.component.DateEditor)
|
classPresenter(builder, core.class.TypeDate, view.component.DatePresenter, view.component.DateEditor)
|
||||||
|
@ -47,7 +47,7 @@ export default mergeIds(viewId, view, {
|
|||||||
IntlStringPresenter: '' as AnyComponent,
|
IntlStringPresenter: '' as AnyComponent,
|
||||||
NumberEditor: '' as AnyComponent,
|
NumberEditor: '' as AnyComponent,
|
||||||
NumberPresenter: '' as AnyComponent,
|
NumberPresenter: '' as AnyComponent,
|
||||||
HTMLPresenter: '' as AnyComponent,
|
MarkupPresenter: '' as AnyComponent,
|
||||||
BooleanPresenter: '' as AnyComponent,
|
BooleanPresenter: '' as AnyComponent,
|
||||||
BooleanEditor: '' as AnyComponent,
|
BooleanEditor: '' as AnyComponent,
|
||||||
TimestampPresenter: '' as AnyComponent,
|
TimestampPresenter: '' as AnyComponent,
|
||||||
@ -59,7 +59,9 @@ export default mergeIds(viewId, view, {
|
|||||||
GithubPresenter: '' as AnyComponent,
|
GithubPresenter: '' as AnyComponent,
|
||||||
ClassPresenter: '' as AnyComponent,
|
ClassPresenter: '' as AnyComponent,
|
||||||
EnumEditor: '' as AnyComponent,
|
EnumEditor: '' as AnyComponent,
|
||||||
HTMLEditor: '' as AnyComponent
|
HTMLEditor: '' as AnyComponent,
|
||||||
|
MarkupEditor: '' as AnyComponent,
|
||||||
|
MarkupEditorPopup: '' as AnyComponent
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Table: '' as IntlString,
|
Table: '' as IntlString,
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.attributes-bar-container {
|
.attributes-bar-container {
|
||||||
|
flex-shrink: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 2fr;
|
grid-template-columns: 1fr 2fr;
|
||||||
grid-auto-flow: row;
|
grid-auto-flow: row;
|
||||||
|
126
packages/presentation/src/components/DraggableList.svelte
Normal file
126
packages/presentation/src/components/DraggableList.svelte
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { flip } from 'svelte/animate'
|
||||||
|
|
||||||
|
import { Doc } from '@hcengineering/core'
|
||||||
|
|
||||||
|
import { IconMoreV } from '@hcengineering/ui'
|
||||||
|
import { getClient } from '../utils'
|
||||||
|
|
||||||
|
type DocWithRank = Doc & { rank: string }
|
||||||
|
|
||||||
|
export let objects: Doc[] | DocWithRank[]
|
||||||
|
export let handleMove: ((fromIndex: number, toIndex: number) => void) | undefined = undefined
|
||||||
|
export let calcRank: (doc: DocWithRank, next: DocWithRank) => string
|
||||||
|
export let showContextMenu: ((evt: MouseEvent, doc: Doc) => void) | undefined = undefined
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
let draggingIndex: number | null = null
|
||||||
|
let hoveringIndex: number | null = null
|
||||||
|
let dragOverIndex: number = -1
|
||||||
|
|
||||||
|
function resetDrag () {
|
||||||
|
draggingIndex = null
|
||||||
|
hoveringIndex = null
|
||||||
|
dragOverIndex = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragStart (ev: DragEvent, index: number) {
|
||||||
|
if (ev.dataTransfer) {
|
||||||
|
ev.dataTransfer.effectAllowed = 'move'
|
||||||
|
ev.dataTransfer.dropEffect = 'move'
|
||||||
|
draggingIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHasRank (objects: Doc[] | (Doc & { rank: string })[]): objects is (Doc & { rank: string })[] {
|
||||||
|
return 'rank' in objects[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDrop (ev: DragEvent, toIndex: number) {
|
||||||
|
if (ev.dataTransfer && draggingIndex !== null && toIndex !== draggingIndex) {
|
||||||
|
ev.dataTransfer.dropEffect = 'move'
|
||||||
|
|
||||||
|
if (handleMove) {
|
||||||
|
handleMove(draggingIndex, toIndex)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!checkHasRank(objects)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const [prev, next] = [
|
||||||
|
objects[draggingIndex < toIndex ? toIndex : toIndex - 1],
|
||||||
|
objects[draggingIndex < toIndex ? toIndex + 1 : toIndex]
|
||||||
|
]
|
||||||
|
const object = objects[draggingIndex]
|
||||||
|
|
||||||
|
await client.update(object, { rank: calcRank(prev, next) })
|
||||||
|
}
|
||||||
|
resetDrag()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="antiAccordion">
|
||||||
|
{#each objects as object, index (object._id)}
|
||||||
|
<div class="description pb-1" animate:flip={{ duration: 400 }}>
|
||||||
|
<div
|
||||||
|
class:is-dragging={index === draggingIndex}
|
||||||
|
class:is-dragged-over-up={draggingIndex !== null && index < draggingIndex && index === hoveringIndex}
|
||||||
|
class:is-dragged-over-down={draggingIndex !== null && index > draggingIndex && index === hoveringIndex}
|
||||||
|
class:drag-over-highlight={index === dragOverIndex}
|
||||||
|
draggable={true}
|
||||||
|
on:contextmenu|preventDefault={(ev) => showContextMenu?.(ev, object)}
|
||||||
|
on:dragstart={(ev) => handleDragStart(ev, index)}
|
||||||
|
on:dragover|preventDefault={() => {
|
||||||
|
dragOverIndex = index
|
||||||
|
return false
|
||||||
|
}}
|
||||||
|
on:dragenter={() => (hoveringIndex = index)}
|
||||||
|
on:drop|preventDefault={(ev) => handleDrop(ev, index)}
|
||||||
|
on:dragend={resetDrag}
|
||||||
|
>
|
||||||
|
<div class="draggable-container">
|
||||||
|
<div class="caption mb-0 flex flex-grow flex-row-center">
|
||||||
|
<div class="draggable-mark fs-title dark-color whitespace-nowrap mr-2"><IconMoreV size={'small'} /></div>
|
||||||
|
<div class="fs-title dark-color whitespace-nowrap mr-2">
|
||||||
|
{`${index + 1}.`}
|
||||||
|
</div>
|
||||||
|
<slot name="object" {index} />
|
||||||
|
</div>
|
||||||
|
<slot name="object-footer" {index} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.drag-over-highlight {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
.draggable-container {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.draggable-mark {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -44,6 +44,7 @@ export { default as IconMembers } from './components/icons/Members.svelte'
|
|||||||
export { default as IconMembersOutline } from './components/icons/MembersOutline.svelte'
|
export { default as IconMembersOutline } from './components/icons/MembersOutline.svelte'
|
||||||
export { default as ObjectSearchPopup } from './components/ObjectSearchPopup.svelte'
|
export { default as ObjectSearchPopup } from './components/ObjectSearchPopup.svelte'
|
||||||
export { default as IndexedDocumentPreview } from './components/IndexedDocumentPreview.svelte'
|
export { default as IndexedDocumentPreview } from './components/IndexedDocumentPreview.svelte'
|
||||||
|
export { default as DraggableList } from './components/DraggableList.svelte'
|
||||||
export { connect, versionError } from './connect'
|
export { connect, versionError } from './connect'
|
||||||
export { default } from './plugin'
|
export { default } from './plugin'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
@ -37,36 +37,42 @@
|
|||||||
"@hcengineering/core": "^0.6.20",
|
"@hcengineering/core": "^0.6.20",
|
||||||
"@hcengineering/ui": "^0.6.3",
|
"@hcengineering/ui": "^0.6.3",
|
||||||
"svelte": "^3.47",
|
"svelte": "^3.47",
|
||||||
"@tiptap/core": "~2.0.0-beta.199",
|
"@tiptap/core": "~2.0.0-beta.209",
|
||||||
"@tiptap/starter-kit": "~2.0.0-beta.199",
|
"@tiptap/starter-kit": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-highlight": "~2.0.0-beta.199",
|
"@tiptap/extension-highlight": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-placeholder": "~2.0.0-beta.199",
|
"@tiptap/extension-placeholder": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-mention": "~2.0.0-beta.199",
|
"@tiptap/extension-mention": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-typography": "~2.0.0-beta.199",
|
"@tiptap/extension-typography": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-link": "~2.0.0-beta.199",
|
"@tiptap/extension-link": "~2.0.0-beta.209",
|
||||||
"@tiptap/suggestion": "~2.0.0-beta.199",
|
"@tiptap/suggestion": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-task-list": "~2.0.0-beta.199",
|
"@tiptap/extension-task-list": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-task-item": "~2.0.0-beta.199",
|
"@tiptap/extension-task-item": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-collaboration": "~2.0.0-beta.199",
|
"@tiptap/extension-collaboration": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-collaboration-cursor": "~2.0.0-beta.199",
|
"@tiptap/extension-collaboration-cursor": "~2.0.0-beta.209",
|
||||||
|
"@tiptap/prosemirror-tables": "^1.1.4",
|
||||||
"emoji-regex": "^10.1.0",
|
"emoji-regex": "^10.1.0",
|
||||||
"prosemirror-collab": "~1.3.0",
|
"prosemirror-gapcursor": "^1.3.1",
|
||||||
"prosemirror-state": "~1.4.1",
|
"prosemirror-dropcursor": "^1.6.1",
|
||||||
"prosemirror-transform": "~1.7.0",
|
"prosemirror-collab": "^1.3.0",
|
||||||
"yjs": "^13.5.42",
|
"prosemirror-state": "^1.4.2",
|
||||||
|
"prosemirror-transform": "^1.7.0",
|
||||||
|
"prosemirror-schema-list": "^1.2.2",
|
||||||
|
"prosemirror-commands": "^1.5.0",
|
||||||
|
"yjs": "^13.5.44",
|
||||||
"y-websocket": "^1.4.5",
|
"y-websocket": "^1.4.5",
|
||||||
"y-prosemirror": "1.0.20",
|
"y-prosemirror": "^1.2.0",
|
||||||
"prosemirror-changeset": "~2.2.0",
|
"prosemirror-changeset": "^2.2.0",
|
||||||
"prosemirror-model": "~1.18.1",
|
"prosemirror-model": "^1.18.3",
|
||||||
"prosemirror-view": "~1.29.0",
|
"prosemirror-view": "^1.29.1",
|
||||||
"rfc6902": "~5.0.1",
|
"prosemirror-history": "^1.3.0",
|
||||||
"diff": "~5.1.0",
|
"rfc6902": "^5.0.1",
|
||||||
"@tiptap/extension-code-block": "~2.0.0-beta.200",
|
"diff": "^5.1.0",
|
||||||
"@tiptap/extension-gapcursor": "~2.0.0-beta.200",
|
"@tiptap/extension-code-block": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-heading": "~2.0.0-beta.200",
|
"@tiptap/extension-gapcursor": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-table": "~2.0.0-beta.202",
|
"@tiptap/extension-heading": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-table-cell": "~2.0.0-beta.202",
|
"@tiptap/extension-table": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-table-header": "~2.0.0-beta.202",
|
"@tiptap/extension-table-cell": "~2.0.0-beta.209",
|
||||||
"@tiptap/extension-table-row": "~2.0.0-beta.202"
|
"@tiptap/extension-table-header": "~2.0.0-beta.209",
|
||||||
|
"@tiptap/extension-table-row": "~2.0.0-beta.209"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,15 @@
|
|||||||
setContext(CollaborationIds.Doc, ydoc)
|
setContext(CollaborationIds.Doc, ydoc)
|
||||||
setContext(CollaborationIds.Provider, wsProvider)
|
setContext(CollaborationIds.Provider, wsProvider)
|
||||||
wsProvider.on('status', (event: any) => {
|
wsProvider.on('status', (event: any) => {
|
||||||
console.log(documentId, event.status) // logs "connected" or "disconnected"
|
console.log('Collaboration:', documentId, event.status) // logs "connected" or "disconnected"
|
||||||
|
})
|
||||||
|
wsProvider.on('synched', (event: any) => {
|
||||||
|
console.log('Collaboration:', event) // logs "connected" or "disconnected"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
setTimeout(() => {
|
|
||||||
wsProvider?.disconnect()
|
wsProvider?.disconnect()
|
||||||
}, 100)
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
export let previewUnlimit: boolean = false
|
export let previewUnlimit: boolean = false
|
||||||
export let focusable: boolean = false
|
export let focusable: boolean = false
|
||||||
export let enableFormatting = false
|
export let enableFormatting = false
|
||||||
|
export let autofocus = false
|
||||||
|
|
||||||
const Mode = {
|
const Mode = {
|
||||||
View: 1,
|
View: 1,
|
||||||
@ -118,6 +119,7 @@
|
|||||||
{maxHeight}
|
{maxHeight}
|
||||||
{focusable}
|
{focusable}
|
||||||
{enableFormatting}
|
{enableFormatting}
|
||||||
|
{autofocus}
|
||||||
bind:content={rawValue}
|
bind:content={rawValue}
|
||||||
bind:this={textEditor}
|
bind:this={textEditor}
|
||||||
on:attach
|
on:attach
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
export let maxHeight: 'max' | 'card' | 'limited' | string | undefined = undefined
|
export let maxHeight: 'max' | 'card' | 'limited' | string | undefined = undefined
|
||||||
export let withoutTopBorder = false
|
export let withoutTopBorder = false
|
||||||
export let enableFormatting = false
|
export let enableFormatting = false
|
||||||
|
export let autofocus = false
|
||||||
|
|
||||||
let textEditor: TextEditor
|
let textEditor: TextEditor
|
||||||
|
|
||||||
@ -201,7 +202,7 @@
|
|||||||
a.action(evt?.target as HTMLElement, editorHandler)
|
a.action(evt?.target as HTMLElement, editorHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
let needFocus = false
|
let needFocus = autofocus
|
||||||
const focused = false
|
const focused = false
|
||||||
|
|
||||||
$: if (textEditor && needFocus) {
|
$: if (textEditor && needFocus) {
|
||||||
|
@ -419,6 +419,7 @@ input.search {
|
|||||||
.mt-9 { margin-top: 2.25rem; }
|
.mt-9 { margin-top: 2.25rem; }
|
||||||
.mt-10 { margin-top: 2.5rem; }
|
.mt-10 { margin-top: 2.5rem; }
|
||||||
.mt-14 { margin-top: 3.5rem; }
|
.mt-14 { margin-top: 3.5rem; }
|
||||||
|
.mb-0 { margin-bottom: 0 !important; }
|
||||||
.mb-1 { margin-bottom: .25rem; }
|
.mb-1 { margin-bottom: .25rem; }
|
||||||
.mb-2 { margin-bottom: .5rem; }
|
.mb-2 { margin-bottom: .5rem; }
|
||||||
.mb-3 { margin-bottom: .75rem; }
|
.mb-3 { margin-bottom: .75rem; }
|
||||||
@ -452,7 +453,7 @@ input.search {
|
|||||||
.pt-3 { padding-top: .75rem; }
|
.pt-3 { padding-top: .75rem; }
|
||||||
.pt-4 { padding-top: 1rem; }
|
.pt-4 { padding-top: 1rem; }
|
||||||
.pt-6 { padding-top: 1.5rem; }
|
.pt-6 { padding-top: 1.5rem; }
|
||||||
.pb-1 { padding-bottom: .25rem; }
|
.pb-1 { padding-bottom: .25rem !important; }
|
||||||
.pb-2 { padding-bottom: .5rem; }
|
.pb-2 { padding-bottom: .5rem; }
|
||||||
.pb-3 { padding-bottom: .75rem; }
|
.pb-3 { padding-bottom: .75rem; }
|
||||||
.pb-4 { padding-bottom: 1rem; }
|
.pb-4 { padding-bottom: 1rem; }
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
export let disabled: boolean = false
|
export let disabled: boolean = false
|
||||||
export let loading: boolean = false
|
export let loading: boolean = false
|
||||||
export let width: string | undefined = undefined
|
export let width: string | undefined = undefined
|
||||||
|
export let height: string | undefined = undefined
|
||||||
export let resetIconSize: boolean = false
|
export let resetIconSize: boolean = false
|
||||||
export let highlight: boolean = false
|
export let highlight: boolean = false
|
||||||
export let selected: boolean = false
|
export let selected: boolean = false
|
||||||
@ -95,6 +96,7 @@
|
|||||||
class:notSelected
|
class:notSelected
|
||||||
disabled={disabled || loading}
|
disabled={disabled || loading}
|
||||||
style:width
|
style:width
|
||||||
|
style:height
|
||||||
{title}
|
{title}
|
||||||
type={kind === 'primary' ? 'submit' : 'button'}
|
type={kind === 'primary' ? 'submit' : 'button'}
|
||||||
on:click|stopPropagation|preventDefault
|
on:click|stopPropagation|preventDefault
|
||||||
|
@ -117,6 +117,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="editbox-container"
|
class="editbox-container"
|
||||||
class:flex-grow={fullSize}
|
class:flex-grow={fullSize}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
import type { TabModel } from '../types'
|
import type { TabModel } from '../types'
|
||||||
import Label from './Label.svelte'
|
import Label from './Label.svelte'
|
||||||
import Component from './Component.svelte'
|
import Component from './Component.svelte'
|
||||||
|
import { Icon } from '..'
|
||||||
|
|
||||||
export let model: TabModel
|
export let model: TabModel
|
||||||
export let selected = 0
|
export let selected = 0
|
||||||
@ -23,6 +24,7 @@
|
|||||||
|
|
||||||
<div class="flex-stretch container">
|
<div class="flex-stretch container">
|
||||||
{#each model as tab, i}
|
{#each model as tab, i}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="flex-row-center tab"
|
class="flex-row-center tab"
|
||||||
class:selected={i === selected}
|
class:selected={i === selected}
|
||||||
@ -30,6 +32,11 @@
|
|||||||
selected = i
|
selected = i
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{#if tab.icon !== undefined}
|
||||||
|
<div class="mr-2">
|
||||||
|
<Icon icon={tab.icon} size={'small'} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<Label label={tab.label} />
|
<Label label={tab.label} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
"AllFilters": "all filters",
|
"AllFilters": "all filters",
|
||||||
"MatchCriteria": "Match criteria",
|
"MatchCriteria": "Match criteria",
|
||||||
"DontMatchCriteria": "Don't match criteria",
|
"DontMatchCriteria": "Don't match criteria",
|
||||||
"View": "View"
|
"View": "View",
|
||||||
|
"MarkupEditor": "Edit of rich content field"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
"AllFilters": "всем фильтрам",
|
"AllFilters": "всем фильтрам",
|
||||||
"MatchCriteria": "Соответсвует условию",
|
"MatchCriteria": "Соответсвует условию",
|
||||||
"DontMatchCriteria": "Не соответвует условию",
|
"DontMatchCriteria": "Не соответвует условию",
|
||||||
"View": "Вид"
|
"View": "Вид",
|
||||||
|
"MarkupEditor": "Изменение форматированного поля"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
71
plugins/view-resources/src/components/MarkupEditor.svelte
Normal file
71
plugins/view-resources/src/components/MarkupEditor.svelte
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
|
// Copyright © 2021 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 type { IntlString } from '@hcengineering/platform'
|
||||||
|
import { MessageViewer } from '@hcengineering/presentation'
|
||||||
|
import { Button, eventToHTMLElement, Label, showPopup } from '@hcengineering/ui'
|
||||||
|
import MarkupEditorPopup from './MarkupEditorPopup.svelte'
|
||||||
|
|
||||||
|
// export let label: IntlString
|
||||||
|
export let placeholder: IntlString
|
||||||
|
export let value: string
|
||||||
|
export let focus: boolean = false
|
||||||
|
export let onChange: (value: string) => void
|
||||||
|
export let kind: 'no-border' | 'link' = 'no-border'
|
||||||
|
export let readonly = false
|
||||||
|
// export let size: ButtonSize = 'x-large'
|
||||||
|
export let justify: 'left' | 'center' = 'center'
|
||||||
|
export let width: string | undefined = 'fit-content'
|
||||||
|
|
||||||
|
let shown: boolean = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if kind === 'link'}
|
||||||
|
<Button
|
||||||
|
{kind}
|
||||||
|
size={'x-large'}
|
||||||
|
{justify}
|
||||||
|
{width}
|
||||||
|
height={value ? 'auto' : undefined}
|
||||||
|
on:click={(ev) => {
|
||||||
|
if (!shown && !readonly) {
|
||||||
|
showPopup(MarkupEditorPopup, { value }, eventToHTMLElement(ev), (res) => {
|
||||||
|
if (res != null) {
|
||||||
|
value = res
|
||||||
|
onChange(value)
|
||||||
|
}
|
||||||
|
shown = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="content">
|
||||||
|
<div class="lines-limit-4" style:text-align={'left'} class:dark-color={readonly}>
|
||||||
|
{#if value}
|
||||||
|
<MessageViewer message={value} />
|
||||||
|
{:else}
|
||||||
|
<span class="dark-color"><Label label={placeholder} /></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
</Button>
|
||||||
|
{:else if readonly}
|
||||||
|
{#if value}
|
||||||
|
<span class="overflow-label">{value}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="dark-color"><Label label={placeholder} /></span>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
@ -0,0 +1,48 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
|
// Copyright © 2021 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 { Card } from '@hcengineering/presentation'
|
||||||
|
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import view from '../plugin'
|
||||||
|
|
||||||
|
export let value: string
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
export let maxHeight: string = '40vh'
|
||||||
|
const checkValue = (evt: CustomEvent): void => {
|
||||||
|
const res: string | undefined = evt.detail === null ? undefined : evt.detail
|
||||||
|
if (value !== res && res != null) {
|
||||||
|
dispatch('change', res)
|
||||||
|
value = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
label={view.string.MarkupEditor}
|
||||||
|
canSave={true}
|
||||||
|
okLabel={view.string.Save}
|
||||||
|
okAction={() => {
|
||||||
|
dispatch('close', value)
|
||||||
|
}}
|
||||||
|
on:close={() => dispatch('close', null)}
|
||||||
|
on:changeContent
|
||||||
|
>
|
||||||
|
<div class="flex-grow mt-4">
|
||||||
|
<StyledTextBox autofocus content={value} alwaysEdit mode={2} hideExtraButtons {maxHeight} on:value={checkValue} />
|
||||||
|
</div>
|
||||||
|
</Card>
|
@ -54,6 +54,8 @@ import UpDownNavigator from './components/UpDownNavigator.svelte'
|
|||||||
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
|
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
|
||||||
import ValueSelector from './components/ValueSelector.svelte'
|
import ValueSelector from './components/ValueSelector.svelte'
|
||||||
import HTMLEditor from './components/HTMLEditor.svelte'
|
import HTMLEditor from './components/HTMLEditor.svelte'
|
||||||
|
import MarkupEditor from './components/MarkupEditor.svelte'
|
||||||
|
import MarkupEditorPopup from './components/MarkupEditorPopup.svelte'
|
||||||
import SortableList from './components/list/SortableList.svelte'
|
import SortableList from './components/list/SortableList.svelte'
|
||||||
import SortableListItem from './components/list/SortableListItem.svelte'
|
import SortableListItem from './components/list/SortableListItem.svelte'
|
||||||
import {
|
import {
|
||||||
@ -118,7 +120,8 @@ export {
|
|||||||
NumberPresenter,
|
NumberPresenter,
|
||||||
TimestampPresenter,
|
TimestampPresenter,
|
||||||
SortableList,
|
SortableList,
|
||||||
SortableListItem
|
SortableListItem,
|
||||||
|
MarkupEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
@ -149,6 +152,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
YoutubePresenter,
|
YoutubePresenter,
|
||||||
ActionsPopup,
|
ActionsPopup,
|
||||||
StringEditorPopup: EditBoxPopup,
|
StringEditorPopup: EditBoxPopup,
|
||||||
|
MarkupEditor,
|
||||||
|
MarkupEditorPopup,
|
||||||
BooleanTruePresenter,
|
BooleanTruePresenter,
|
||||||
EnumEditor,
|
EnumEditor,
|
||||||
FilterTypePopup,
|
FilterTypePopup,
|
||||||
|
@ -54,6 +54,7 @@ export default mergeIds(viewId, view, {
|
|||||||
AnyFilter: '' as IntlString,
|
AnyFilter: '' as IntlString,
|
||||||
AllFilters: '' as IntlString,
|
AllFilters: '' as IntlString,
|
||||||
MatchCriteria: '' as IntlString,
|
MatchCriteria: '' as IntlString,
|
||||||
DontMatchCriteria: '' as IntlString
|
DontMatchCriteria: '' as IntlString,
|
||||||
|
MarkupEditor: '' as IntlString
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
"lodash.debounce": "~4.0.8",
|
"lodash.debounce": "~4.0.8",
|
||||||
"y-protocols": "~1.0.5",
|
"y-protocols": "~1.0.5",
|
||||||
"ws": "^8.10.0",
|
"ws": "^8.10.0",
|
||||||
"yjs": "^13.5.42",
|
"yjs": "^13.5.44",
|
||||||
"@hcengineering/minio": "^0.6.0"
|
"@hcengineering/minio": "^0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +271,6 @@ async function doIssueUpdate (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const updatedProject = newParent !== undefined ? newParent.project : null
|
const updatedProject = newParent !== undefined ? newParent.project : null
|
||||||
const updatedSprint = newParent !== undefined ? newParent.sprint : null
|
|
||||||
const updatedParents =
|
const updatedParents =
|
||||||
newParent !== undefined ? [{ parentId: newParent._id, parentTitle: newParent.title }, ...newParent.parents] : []
|
newParent !== undefined ? [{ parentId: newParent._id, parentTitle: newParent.title }, ...newParent.parents] : []
|
||||||
|
|
||||||
@ -282,14 +281,13 @@ async function doIssueUpdate (
|
|||||||
? {}
|
? {}
|
||||||
: { parents: [...issue.parents].slice(0, parentInfoIndex + 1).concat(updatedParents) }
|
: { parents: [...issue.parents].slice(0, parentInfoIndex + 1).concat(updatedParents) }
|
||||||
|
|
||||||
return { ...parentsUpdate, project: updatedProject, sprint: updatedSprint }
|
return { ...parentsUpdate, project: updatedProject }
|
||||||
}
|
}
|
||||||
|
|
||||||
res.push(
|
res.push(
|
||||||
control.txFactory.createTxUpdateDoc(updateTx.objectClass, updateTx.objectSpace, updateTx.objectId, {
|
control.txFactory.createTxUpdateDoc(updateTx.objectClass, updateTx.objectSpace, updateTx.objectId, {
|
||||||
parents: updatedParents,
|
parents: updatedParents,
|
||||||
project: updatedProject,
|
project: updatedProject
|
||||||
sprint: updatedSprint
|
|
||||||
}),
|
}),
|
||||||
...(await updateSubIssues(updateTx, control, update))
|
...(await updateSubIssues(updateTx, control, update))
|
||||||
)
|
)
|
||||||
|
@ -172,9 +172,14 @@ export async function backup (transactorUrl: string, workspaceId: WorkspaceId, s
|
|||||||
let addedDocuments = 0
|
let addedDocuments = 0
|
||||||
|
|
||||||
// update digest tar
|
// update digest tar
|
||||||
|
const needRetrieveChunks: Ref<Doc>[][] = []
|
||||||
|
|
||||||
|
// Load all digest from collection.
|
||||||
while (true) {
|
while (true) {
|
||||||
|
try {
|
||||||
const it = await connection.loadChunk(c, idx)
|
const it = await connection.loadChunk(c, idx)
|
||||||
idx = it.idx
|
idx = it.idx
|
||||||
|
console.log(needRetrieveChunks.length)
|
||||||
|
|
||||||
const needRetrieve: Ref<Doc>[] = []
|
const needRetrieve: Ref<Doc>[] = []
|
||||||
|
|
||||||
@ -194,13 +199,50 @@ export async function backup (transactorUrl: string, workspaceId: WorkspaceId, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (needRetrieve.length > 0) {
|
if (needRetrieve.length > 0) {
|
||||||
const docs = await connection.loadDocs(c, needRetrieve)
|
needRetrieveChunks.push(needRetrieve)
|
||||||
|
}
|
||||||
|
if (it.finished) {
|
||||||
|
await connection.closeChunk(idx)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err)
|
||||||
|
if (idx !== undefined) {
|
||||||
|
await connection.closeChunk(idx)
|
||||||
|
}
|
||||||
|
// Try again
|
||||||
|
idx = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (needRetrieveChunks.length > 0) {
|
||||||
|
const needRetrieve = needRetrieveChunks.shift() as Ref<Doc>[]
|
||||||
|
|
||||||
|
console.log('Retrieve chunk:', needRetrieve.length)
|
||||||
|
let docs: Doc[] = []
|
||||||
|
try {
|
||||||
|
docs = await connection.loadDocs(c, needRetrieve)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log(err)
|
||||||
|
// Put back.
|
||||||
|
needRetrieveChunks.push(needRetrieve)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Chunk data into small pieces
|
// Chunk data into small pieces
|
||||||
if (addedDocuments > dataBlobSize && _pack !== undefined) {
|
if (addedDocuments > dataBlobSize && _pack !== undefined) {
|
||||||
_pack.finalize()
|
_pack.finalize()
|
||||||
_pack = undefined
|
_pack = undefined
|
||||||
addedDocuments = 0
|
addedDocuments = 0
|
||||||
|
|
||||||
|
if (changed > 0) {
|
||||||
|
snapshot.domains[c] = domainInfo
|
||||||
|
domainInfo.added = Object.keys(changes.added).length
|
||||||
|
domainInfo.updated = Object.keys(changes.updated).length
|
||||||
|
domainInfo.removed = changes.removed.length
|
||||||
|
await storage.writeFile(domainInfo.snapshot, gzipSync(JSON.stringify(changes)))
|
||||||
|
// This will allow to retry in case of critical error.
|
||||||
|
await storage.writeFile(infoFile, gzipSync(JSON.stringify(backupInfo, undefined, 2)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (_pack === undefined) {
|
if (_pack === undefined) {
|
||||||
_pack = pack()
|
_pack = pack()
|
||||||
@ -238,11 +280,6 @@ export async function backup (transactorUrl: string, workspaceId: WorkspaceId, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.finished) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changes.removed = Array.from(digest.keys())
|
changes.removed = Array.from(digest.keys())
|
||||||
if (changes.removed.length > 0) {
|
if (changes.removed.length > 0) {
|
||||||
changed++
|
changed++
|
||||||
@ -255,6 +292,8 @@ export async function backup (transactorUrl: string, workspaceId: WorkspaceId, s
|
|||||||
domainInfo.removed = changes.removed.length
|
domainInfo.removed = changes.removed.length
|
||||||
await storage.writeFile(domainInfo.snapshot, gzipSync(JSON.stringify(changes)))
|
await storage.writeFile(domainInfo.snapshot, gzipSync(JSON.stringify(changes)))
|
||||||
_pack?.finalize()
|
_pack?.finalize()
|
||||||
|
// This will allow to retry in case of critical error.
|
||||||
|
await storage.writeFile(infoFile, gzipSync(JSON.stringify(backupInfo, undefined, 2)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,17 +307,13 @@ export function start (
|
|||||||
perMessageDeflate: {
|
perMessageDeflate: {
|
||||||
zlibDeflateOptions: {
|
zlibDeflateOptions: {
|
||||||
// See zlib defaults.
|
// See zlib defaults.
|
||||||
chunkSize: 1024,
|
chunkSize: 10 * 1024,
|
||||||
memLevel: 7,
|
memLevel: 7,
|
||||||
level: 3
|
level: 3
|
||||||
},
|
},
|
||||||
zlibInflateOptions: {
|
zlibInflateOptions: {
|
||||||
chunkSize: 10 * 1024
|
chunkSize: 10 * 1024
|
||||||
},
|
}
|
||||||
// Below options specified as default values.
|
|
||||||
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
|
||||||
threshold: 1024 // Size (in bytes) below which messages
|
|
||||||
// should not be compressed if context takeover is disabled.
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
Loading…
Reference in New Issue
Block a user