Browse Source

feat(nc-gui): rich text setup in form field

pull/7741/head
Ramesh Mane 4 months ago
parent
commit
02a4b8e48c
  1. 10
      packages/nc-gui/assets/nc-icons/link.svg
  2. 35
      packages/nc-gui/components/cell/RichText.vue
  3. 236
      packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue
  4. 28
      packages/nc-gui/components/smartsheet/Form.vue
  5. 1
      packages/nc-gui/utils/iconUtils.ts

10
packages/nc-gui/assets/nc-icons/link.svg

@ -1,4 +1,8 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.6665 9.16668C6.95281 9.54943 7.31808 9.86613 7.73754 10.0953C8.157 10.3245 8.62084 10.4608 9.0976 10.4949C9.57437 10.529 10.0529 10.4603 10.5007 10.2932C10.9486 10.1261 11.3552 9.86473 11.6932 9.52668L13.6932 7.52668C14.3004 6.89801 14.6363 6.056 14.6288 5.18201C14.6212 4.30802 14.2706 3.47198 13.6526 2.85395C13.0345 2.23592 12.1985 1.88536 11.3245 1.87777C10.4505 1.87017 9.60851 2.20615 8.97984 2.81335L7.83317 3.95335" stroke="#3366FF" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.33347 7.83332C9.04716 7.45057 8.68189 7.13387 8.26243 6.90469C7.84297 6.67552 7.37913 6.53924 6.90237 6.5051C6.4256 6.47095 5.94707 6.53974 5.49924 6.7068C5.0514 6.87386 4.64472 7.13527 4.3068 7.47332L2.3068 9.47332C1.69961 10.102 1.36363 10.944 1.37122 11.818C1.37881 12.692 1.72938 13.528 2.3474 14.146C2.96543 14.7641 3.80147 15.1146 4.67546 15.1222C5.54945 15.1298 6.39146 14.7938 7.02013 14.1867L8.16013 13.0467" stroke="#3366FF" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<path
d="M6.6665 9.16668C6.95281 9.54943 7.31808 9.86613 7.73754 10.0953C8.157 10.3245 8.62084 10.4608 9.0976 10.4949C9.57437 10.529 10.0529 10.4603 10.5007 10.2932C10.9486 10.1261 11.3552 9.86473 11.6932 9.52668L13.6932 7.52668C14.3004 6.89801 14.6363 6.056 14.6288 5.18201C14.6212 4.30802 14.2706 3.47198 13.6526 2.85395C13.0345 2.23592 12.1985 1.88536 11.3245 1.87777C10.4505 1.87017 9.60851 2.20615 8.97984 2.81335L7.83317 3.95335"
stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M9.33347 7.83332C9.04716 7.45057 8.68189 7.13387 8.26243 6.90469C7.84297 6.67552 7.37913 6.53924 6.90237 6.5051C6.4256 6.47095 5.94707 6.53974 5.49924 6.7068C5.0514 6.87386 4.64472 7.13527 4.3068 7.47332L2.3068 9.47332C1.69961 10.102 1.36363 10.944 1.37122 11.818C1.37881 12.692 1.72938 13.528 2.3474 14.146C2.96543 14.7641 3.80147 15.1146 4.67546 15.1222C5.54945 15.1298 6.39146 14.7938 7.02013 14.1867L8.16013 13.0467"
stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

35
packages/nc-gui/components/cell/RichText.vue

@ -16,6 +16,7 @@ const props = defineProps<{
syncValueChange?: boolean
showMenu?: boolean
fullMode?: boolean
isFormField?: boolean
}>()
const emits = defineEmits(['update:value'])
@ -126,6 +127,24 @@ const editor = useEditor({
editable: !props.readOnly,
})
watch(props, () => {
console.log('readonly node')
if (props.isFormField) {
if (props.readOnly) {
editor.value?.setEditable(false)
} else {
editor.value?.setEditable(true)
setTimeout(() => {
editor.value?.chain().focus().run()
}, 50)
}
}
})
watchEffect(() => {
console.log('read only ', props.readOnly)
})
const setEditorContent = (contentMd: any, focusEndOfDoc?: boolean) => {
if (!editor.value) return
@ -177,21 +196,22 @@ watch(editorDom, () => {
'flex flex-col flex-grow nc-rich-text-full': fullMode,
'nc-rich-text-embed flex flex-col pl-1 w-full': !fullMode,
'readonly': readOnly,
'nc-form-rich-text-field !p-0': isFormField,
}"
:tabindex="readOnlyCell ? -1 : 0"
>
<div
v-if="showMenu && !readOnly"
v-if="showMenu && !readOnly && !isFormField"
class="absolute top-0 right-0.5 xs:hidden"
:class="{
'max-w-[calc(100%_-_198px)] flex justify-end rounded-tr-2xl overflow-hidden': fullMode,
}"
>
<div class="nc-longtext-scrollbar">
<CellRichTextSelectedBubbleMenu v-if="editor" :editor="editor" embed-mode />
<CellRichTextSelectedBubbleMenu v-if="editor" :editor="editor" embed-mode :is-form-field="isFormField" />
</div>
</div>
<CellRichTextSelectedBubbleMenuPopup v-if="editor" :editor="editor" />
<CellRichTextSelectedBubbleMenuPopup v-if="editor && !isFormField" :editor="editor" />
<CellRichTextLinkOptions v-if="editor" :editor="editor" />
<EditorContent
ref="editorDom"
@ -205,6 +225,9 @@ watch(editorDom, () => {
!fullMode && readOnly && rowHeight && !isExpandedFormOpen && !isForm,
}"
/>
<div v-if="isFormField && !readOnly">
<CellRichTextSelectedBubbleMenu v-if="editor" :editor="editor" embed-mode is-form-field />
</div>
</div>
</template>
@ -223,7 +246,11 @@ watch(editorDom, () => {
.nc-rich-text-embed {
.ProseMirror {
@apply !border-transparent max-h-full;
min-height: 8rem;
}
&:not(.nc-form-rich-text-field) {
.ProseMirror {
min-height: 8rem;
}
}
&.readonly {
.nc-textarea-rich-editor {

236
packages/nc-gui/components/cell/RichText/SelectedBubbleMenu.vue

@ -14,6 +14,7 @@ import MsFormatQuote from '~icons/material-symbols/format-quote'
interface Props {
editor: Editor
embedMode?: boolean
isFormField?: boolean
}
const props = defineProps<Props>()
@ -83,9 +84,10 @@ const onToggleLink = () => {
:class="{
'embed-mode': embedMode,
'full-mode': !embedMode,
'form-field-mode': isFormField,
}"
>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -105,7 +107,7 @@ const onToggleLink = () => {
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -124,7 +126,7 @@ const onToggleLink = () => {
<MdiFormatItalic />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -144,7 +146,7 @@ const onToggleLink = () => {
<MdiFormatUnderline />
</NcButton>
</NcTooltip>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title>
<div class="flex flex-col items-center">
<div>
@ -163,141 +165,143 @@ const onToggleLink = () => {
<MdiFormatStrikeThrough />
</NcButton>
</NcTooltip>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('general.code') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('codeBlock') }"
@click="editor!.chain().focus().toggleCodeBlock().run()"
>
<MsCode />
</NcButton>
</NcTooltip>
<NcTooltip v-else :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.quote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('code') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleCode().run()"
>
<MsFormatQuote />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<template v-if="!isFormField">
<NcTooltip v-if="embedMode">
<template #title> {{ $t('general.code') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('codeBlock') }"
@click="editor!.chain().focus().toggleCodeBlock().run()"
>
<MsCode />
</NcButton>
</NcTooltip>
<NcTooltip v-else :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.quote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('code') }"
:disabled="editor.isActive('codeBlock')"
@click="editor!.chain().focus().toggleCode().run()"
>
<MsFormatQuote />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<template v-if="embedMode">
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading1') }}
<template v-if="embedMode">
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading1') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 1</div>
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 1</div>
</div>
</template>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 1 }).run()"
>
<MsFormatH1 />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading2') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 2</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 2 }).run()"
>
<MsFormatH2 />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading3') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 3</div>
</div>
</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 3 }).run()"
>
<MsFormatH3 />
</NcButton>
</NcTooltip>
<div class="divider"></div>
</template>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('labels.blockQuote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 1 }).run()"
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor!.chain().focus().toggleBlockquote().run()"
>
<MsFormatH1 />
<TablerBlockQuote class="-mt-0.25" />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading2') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 2</div>
</div>
</template>
<template #title> {{ $t('labels.bulletList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 2 }).run()"
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor!.chain().focus().toggleBulletList().run()"
>
<MsFormatH2 />
<MdiFormatBulletList />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title>
<div class="flex flex-col items-center">
<div>
{{ $t('labels.heading3') }}
</div>
<div>{{ cmdOrCtrlKey }} {{ altKey }} 3</div>
</div>
</template>
<template #title> {{ $t('labels.numberedList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor!.chain().focus().toggleHeading({ level: 3 }).run()"
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor!.chain().focus().toggleOrderedList().run()"
>
<MsFormatH3 />
<MdiFormatListNumber />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.taskList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('taskList') }"
@click="editor!.chain().focus().toggleTaskList().run()"
>
<MdiFormatListCheckbox />
</NcButton>
</NcTooltip>
<div class="divider"></div>
</template>
<NcTooltip v-if="embedMode">
<template #title> {{ $t('labels.blockQuote') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor!.chain().focus().toggleBlockquote().run()"
>
<TablerBlockQuote class="-mt-0.25" />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.bulletList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor!.chain().focus().toggleBulletList().run()"
>
<MdiFormatBulletList />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.numberedList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor!.chain().focus().toggleOrderedList().run()"
>
<MdiFormatListNumber />
</NcButton>
</NcTooltip>
<NcTooltip>
<template #title> {{ $t('labels.taskList') }}</template>
<NcButton
size="small"
type="text"
:class="{ 'is-active': editor.isActive('taskList') }"
@click="editor!.chain().focus().toggleTaskList().run()"
>
<MdiFormatListCheckbox />
</NcButton>
</NcTooltip>
<div class="divider"></div>
<NcTooltip :disabled="editor.isActive('codeBlock')">
<NcTooltip :placement="isFormField ? 'bottom' : undefined" :disabled="editor.isActive('codeBlock')">
<template #title> {{ $t('general.link') }}</template>
<NcButton
size="small"
@ -306,7 +310,8 @@ const onToggleLink = () => {
:disabled="editor.isActive('codeBlock')"
@click="onToggleLink"
>
<div class="flex flex-row items-center px-0.5">
<GeneralIcon v-if="isFormField" icon="link2"></GeneralIcon>
<div v-else class="flex flex-row items-center px-0.5">
<MdiLink />
<div class="!text-xs !ml-1">{{ $t('general.link') }}</div>
</div>
@ -343,6 +348,9 @@ const onToggleLink = () => {
.bubble-menu.embed-mode {
@apply border-transparent !shadow-none;
}
.bubble-menu.form-field-mode {
@apply bg-transparent px-0;
}
.embed-mode.bubble-menu {
@apply !py-0 !my-0 !border-0;

28
packages/nc-gui/components/smartsheet/Form.vue

@ -515,7 +515,7 @@ const handleOnUploadImage = (data: Record<string, any> = {}) => {
onClickOutside(draggableRef, (e) => {
if (
(e.target as HTMLElement)?.closest(
'.nc-dropdown-single-select-cell, .nc-dropdown-multi-select-cell, .nc-dropdown-user-select-cell',
'.nc-dropdown-single-select-cell, .nc-dropdown-multi-select-cell, .nc-dropdown-user-select-cell, .nc-form-rich-text-field',
)
) {
return
@ -824,7 +824,7 @@ useEventListener(
]"
@click.stop="onFormItemClick({ title: 'nc-form-heading' })"
>
<a-form-item v-if="isEditable" class="!my-0">
<!-- <a-form-item v-if="isEditable" class="!my-0">
<a-textarea
v-model:value="formViewData.heading"
class="nc-form-focus-element !p-0 !m-0 w-full !font-bold !text-2xl !border-0 !rounded-none !text-gray-900"
@ -845,11 +845,25 @@ useEventListener(
@blur="updateView"
@keydown.enter="updateView"
/>
</a-form-item>
<div v-else class="font-bold text-2xl text-gray-900">
{{ formViewData.heading }}
</div>
</a-form-item> -->
<LazyCellRichText
v-if="isEditable"
v-model:value="formViewData.heading"
class="nc-form-focus-element font-bold text-2xl text-gray-900"
is-form-field
:read-only="activeRow !== 'nc-form-heading'"
data-testid="nc-form-heading"
data-title="nc-form-heading"
@update:value="updateView"
/>
<LazyCellRichText
v-else
v-model:value="formViewData.heading"
class="font-bold text-2xl text-gray-900"
is-form-field
read-only
/>
</div>
<!-- form description -->

1
packages/nc-gui/utils/iconUtils.ts

@ -326,6 +326,7 @@ export const iconMap = {
translate: h('span', { class: 'material-symbols' }, 'translate'),
preview: h('span', { class: 'material-symbols' }, 'visibility'),
link: h('span', { class: 'material-symbols' }, 'link'),
link2: NcLink,
returnKey: h('span', { class: 'material-symbols' }, 'keyboard_return'),
keyboard: h('span', { class: 'material-symbols' }, 'keyboard'),
accountPlus: h('span', { class: 'material-symbols' }, 'person_add'),

Loading…
Cancel
Save