mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-12 12:47:45 +02:00
Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Generated
+3
@@ -9666,6 +9666,9 @@ importers:
|
||||
'@hcengineering/model-core':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../core
|
||||
'@hcengineering/model-print':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../print
|
||||
'@hcengineering/model-tracker':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../tracker
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
isEmptyMarkup,
|
||||
isEmptyNode,
|
||||
jsonToMarkup,
|
||||
type MarkupMark,
|
||||
MarkupMarkType,
|
||||
MarkupNode,
|
||||
MarkupNodeType,
|
||||
@@ -300,6 +301,32 @@ describe('jsonToPmNode', () => {
|
||||
expect(node.content.child(0).type.name).toEqual('text')
|
||||
expect(node.content.child(0).text).toEqual('Hello, world!')
|
||||
})
|
||||
|
||||
it('ServerKit schema includes node-uuid mark and parses text carrying it (QMS anchors)', () => {
|
||||
const schema = getSchema(extensions)
|
||||
expect(schema.marks['node-uuid']).toBeDefined()
|
||||
const json: MarkupNode = {
|
||||
type: MarkupNodeType.doc,
|
||||
content: [
|
||||
{
|
||||
type: MarkupNodeType.paragraph,
|
||||
attrs: { textAlign: null as any },
|
||||
content: [
|
||||
{
|
||||
type: MarkupNodeType.text,
|
||||
text: 'hello',
|
||||
marks: [{ type: 'node-uuid' } as unknown as MarkupMark]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
const node = jsonToPmNode(json, schema)
|
||||
expect(node.type.name).toEqual('doc')
|
||||
const para = node.content.child(0)
|
||||
const text = para.content.child(0)
|
||||
expect(text.marks.some((m) => m.type.name === 'node-uuid')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('htmlToMarkup', () => {
|
||||
|
||||
@@ -428,9 +428,9 @@ export default function buildModel (): Builder {
|
||||
questionsId,
|
||||
{
|
||||
label: setting.string.Configure,
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
beta: false,
|
||||
hidden: true,
|
||||
hidden: false,
|
||||
classFilter: defaultFilter
|
||||
}
|
||||
],
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"@hcengineering/model-core": "workspace:^0.7.0",
|
||||
"@hcengineering/model-tracker": "workspace:^0.7.0",
|
||||
"@hcengineering/model-view": "workspace:^0.7.0",
|
||||
"@hcengineering/model-print": "workspace:^0.7.0",
|
||||
"@hcengineering/platform": "workspace:^0.7.20",
|
||||
"@hcengineering/view": "workspace:^0.7.0"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { type Builder } from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import tracker from '@hcengineering/model-tracker'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import print from '@hcengineering/model-print'
|
||||
import {
|
||||
TAnswer,
|
||||
TAssessment,
|
||||
@@ -215,7 +216,15 @@ function defineActions (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.mixin(questions.class.Question, core.class.Class, view.mixin.IgnoreActions, {
|
||||
actions: [view.action.Open, view.action.OpenInNewTab, view.action.Delete, tracker.action.NewRelatedIssue]
|
||||
actions: [
|
||||
view.action.Open,
|
||||
view.action.OpenInNewTab,
|
||||
view.action.Delete,
|
||||
tracker.action.NewRelatedIssue,
|
||||
view.action.CopyLink,
|
||||
view.action.AddRelation,
|
||||
print.action.Print
|
||||
]
|
||||
})
|
||||
|
||||
createAction(
|
||||
|
||||
+20
-16
@@ -890,24 +890,28 @@ export function createModel (builder: Builder): void {
|
||||
view.action.Join
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: view.component.AddRelationPopup,
|
||||
fillProps: {
|
||||
_objects: 'value'
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup as ViewAction<Record<string, any>>,
|
||||
actionProps: {
|
||||
component: view.component.AddRelationPopup,
|
||||
fillProps: {
|
||||
_objects: 'value'
|
||||
}
|
||||
},
|
||||
label: core.string.AddRelation,
|
||||
input: 'any',
|
||||
icon: view.icon.CopyLink,
|
||||
category: view.category.Editor,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'associate'
|
||||
}
|
||||
},
|
||||
label: core.string.AddRelation,
|
||||
input: 'any',
|
||||
icon: view.icon.CopyLink,
|
||||
category: view.category.Editor,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['context', 'browser'],
|
||||
group: 'associate'
|
||||
}
|
||||
})
|
||||
view.action.AddRelation
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
|
||||
@@ -134,10 +134,3 @@
|
||||
{/await}
|
||||
{/if}
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.actions {
|
||||
height: 0.8rem;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { jsonToPmNode, MarkupNode } from '@hcengineering/text'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { Editor, Extension, mergeAttributes } from '@tiptap/core'
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import { DecorationSet } from '@tiptap/pm/view'
|
||||
@@ -31,21 +34,29 @@
|
||||
export let objectClass: Ref<Class<Doc>> | undefined = undefined
|
||||
|
||||
let element: HTMLElement
|
||||
let editor: Editor
|
||||
let editor: Editor | undefined
|
||||
|
||||
let _decoration = DecorationSet.empty
|
||||
let oldContent: MarkupNode | undefined
|
||||
let decorateFailed = false
|
||||
let editorMountFailed = false
|
||||
|
||||
function updateEditor (editor: Editor, comparedVersion?: MarkupNode): void {
|
||||
if (comparedVersion === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const node = jsonToPmNode(comparedVersion, editor.schema)
|
||||
const r = calculateDecorations(editor, oldContent, node)
|
||||
if (r !== undefined) {
|
||||
oldContent = r.oldContent
|
||||
_decoration = r.decorations
|
||||
try {
|
||||
const node = jsonToPmNode(comparedVersion, editor.schema)
|
||||
const r = calculateDecorations(editor, oldContent, node)
|
||||
if (r !== undefined) {
|
||||
oldContent = r.oldContent
|
||||
_decoration = r.decorations
|
||||
}
|
||||
decorateFailed = false
|
||||
} catch (err: unknown) {
|
||||
decorateFailed = true
|
||||
Analytics.handleError(err instanceof Error ? err : new Error(String(err)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,33 +88,66 @@
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const kit = await getEditorKit({
|
||||
objectClass,
|
||||
commentNode: true
|
||||
})
|
||||
try {
|
||||
const kit = await getEditorKit({
|
||||
objectClass,
|
||||
commentNode: true,
|
||||
qms: {
|
||||
qmsInlineComment: {
|
||||
isHighlightModeOn: () => false,
|
||||
getNodeHighlight: () => null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
editor = new Editor({
|
||||
editorProps: { attributes: mergeAttributes(defaultEditorAttributes, { class: 'flex-grow' }) },
|
||||
element,
|
||||
content,
|
||||
editable: false,
|
||||
extensions: [kit, DecorationExtension],
|
||||
onTransaction: () => {
|
||||
// force re-render so `editor.isActive` works as expected
|
||||
editor = editor
|
||||
}
|
||||
})
|
||||
editor = new Editor({
|
||||
editorProps: { attributes: mergeAttributes(defaultEditorAttributes, { class: 'flex-grow' }) },
|
||||
element,
|
||||
content,
|
||||
editable: false,
|
||||
extensions: [kit, DecorationExtension],
|
||||
onContentError: ({ error, disableCollaboration }) => {
|
||||
disableCollaboration()
|
||||
Analytics.handleError(error)
|
||||
editorMountFailed = true
|
||||
},
|
||||
onTransaction: () => {
|
||||
// force re-render so `editor.isActive` works as expected
|
||||
editor = editor
|
||||
}
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
editorMountFailed = true
|
||||
Analytics.handleError(err instanceof Error ? err : new Error(String(err)))
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (editor !== undefined) {
|
||||
editor.destroy()
|
||||
}
|
||||
editor?.destroy()
|
||||
editor = undefined
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="ref-container">
|
||||
<div class="textInput">
|
||||
<div class="textInput" hidden={editorMountFailed}>
|
||||
<div class="select-text" style="width: 100%;" bind:this={element} />
|
||||
</div>
|
||||
{#if editorMountFailed}
|
||||
<div class="error-fallback">
|
||||
<Label label={presentation.string.FailedToPreview} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if decorateFailed && !editorMountFailed}
|
||||
<div class="error-fallback">
|
||||
<Label label={presentation.string.FailedToPreview} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.error-fallback {
|
||||
padding: 0.5rem 0;
|
||||
color: var(--theme-caption-color);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,6 +41,18 @@ import view from './plugin'
|
||||
import { type FocusSelection, type SelectionStore } from './selection'
|
||||
import { restrictionStore } from './utils'
|
||||
|
||||
/**
|
||||
* Collapses a single-element array to the element, otherwise returns the original value.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function normalizeActionContext<T extends Doc> (doc: T | T[] | undefined): T | T[] | undefined {
|
||||
if (Array.isArray(doc) && doc.length === 1) {
|
||||
return doc[0]
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -114,7 +126,7 @@ export async function filterAvailableActions (
|
||||
} else {
|
||||
const visibilityTester = await getResource(action.visibilityTester)
|
||||
|
||||
if (await visibilityTester(doc)) {
|
||||
if (await visibilityTester(normalizeActionContext(doc))) {
|
||||
result.push(action)
|
||||
}
|
||||
}
|
||||
@@ -132,7 +144,7 @@ export async function invokeAction (
|
||||
): Promise<void> {
|
||||
const impl = await getResource(action.action)
|
||||
Analytics.handleEvent(action.analyticsEvent ?? action._id)
|
||||
await impl(Array.isArray(object) && object.length === 1 ? object[0] : object, evt, {
|
||||
await impl(normalizeActionContext(object), evt, {
|
||||
...action.actionProps,
|
||||
...props
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
} from '@hcengineering/ui'
|
||||
import { Action, ActionCategory, ViewContext } from '@hcengineering/view'
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte'
|
||||
import { filterActions, getSelection } from '../actions'
|
||||
import { filterActions, getSelection, normalizeActionContext } from '../actions'
|
||||
import view from '../plugin'
|
||||
import { focusStore, selectionStore } from '../selection'
|
||||
import { openDoc } from '../utils'
|
||||
@@ -99,7 +99,7 @@
|
||||
} else {
|
||||
const visibilityTester = await getResource(action.visibilityTester)
|
||||
|
||||
if (await visibilityTester(docs)) {
|
||||
if (await visibilityTester(normalizeActionContext(docs))) {
|
||||
resultActions.push(action)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,8 @@ const view = plugin(viewId, {
|
||||
RemoveRelation: '' as Ref<Action>,
|
||||
|
||||
CopyLink: '' as Ref<Action<Doc, any>>,
|
||||
CopyDocumentMarkdown: '' as Ref<Action<Doc, any>>
|
||||
CopyDocumentMarkdown: '' as Ref<Action<Doc, any>>,
|
||||
AddRelation: '' as Ref<Action<Doc, any>>
|
||||
},
|
||||
viewlet: {
|
||||
Table: '' as Ref<ViewletDescriptor>,
|
||||
|
||||
Reference in New Issue
Block a user