mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-7579: Text editor actions (#6103)
Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
parent
14487823a9
commit
fba1469647
@ -454,7 +454,7 @@ dependencies:
|
||||
version: file:projects/model-templates.tgz
|
||||
'@rush-temp/model-text-editor':
|
||||
specifier: file:./projects/model-text-editor.tgz
|
||||
version: file:projects/model-text-editor.tgz
|
||||
version: file:projects/model-text-editor.tgz(@tiptap/pm@2.2.4)
|
||||
'@rush-temp/model-time':
|
||||
specifier: file:./projects/model-time.tgz
|
||||
version: file:projects/model-time.tgz
|
||||
@ -1190,9 +1190,6 @@ dependencies:
|
||||
comment-json:
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.3
|
||||
compression:
|
||||
specifier: ~1.7.4
|
||||
version: 1.7.4
|
||||
compression-webpack-plugin:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(webpack@5.90.3)
|
||||
@ -18969,7 +18966,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/attachment-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-mF2tlDyxapMhNMctw3bDd2lr7PI5S3gh/QMVEtFTIJuO03UEwIul29Xi0xlKWUH/afiSe9397lP3jbU0rm5OMg==, tarball: file:projects/attachment-resources.tgz}
|
||||
resolution: {integrity: sha512-ZEkxtUt4tXWqiEQOGDSFHr65Zw9PtVeCQJ5zS8zdDdpF+vh1BAaLW7OydhYafKYw5RheG50HOK/R6wPDcNd3Lw==, tarball: file:projects/attachment-resources.tgz}
|
||||
id: file:projects/attachment-resources.tgz
|
||||
name: '@rush-temp/attachment-resources'
|
||||
version: 0.0.0
|
||||
@ -19700,7 +19697,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/collaborator.tgz(@tiptap/pm@2.2.4)(bufferutil@4.0.8)(prosemirror-model@1.19.4)(utf-8-validate@6.0.4)(y-protocols@1.0.6):
|
||||
resolution: {integrity: sha512-VOfczS2vceO0InYO/QbF0peWyX0/gct5EbWid4nKn1CJM166EN1NbgC4k/OnMSkmpjjmlOY23tfoUhyze5B6JA==, tarball: file:projects/collaborator.tgz}
|
||||
resolution: {integrity: sha512-PNTgV2uVlKqXedhCD+Yq4pN+lDtT6b4g9O6UZ7C98Q7dJcAsb22WduinT84/y0gU7JPEbljzyrrAv8Jvj8PnCQ==, tarball: file:projects/collaborator.tgz}
|
||||
id: file:projects/collaborator.tgz
|
||||
name: '@rush-temp/collaborator'
|
||||
version: 0.0.0
|
||||
@ -21508,7 +21505,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-controlled-documents.tgz:
|
||||
resolution: {integrity: sha512-/R834PH7U0ysT1eecrsWFubbW1aIKoxs7PewHwYICCvQSM21Bfe+7FzM1rm3KyaZ0gyu8J6HRONdi55vLRzN/g==, tarball: file:projects/model-controlled-documents.tgz}
|
||||
resolution: {integrity: sha512-Fg2GDNEA/E5UucxJtuVYq9G9aPq2BTBbo5HYwk64AzpWOfYEI0mzv+FAeAeTMPRKwZR8TDKxKgNCmmMXsmo/PQ==, tarball: file:projects/model-controlled-documents.tgz}
|
||||
name: '@rush-temp/model-controlled-documents'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -22564,11 +22561,13 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/model-text-editor.tgz:
|
||||
resolution: {integrity: sha512-QAA7Ajq9B3WQ5bJPqPw8XKkaV1l42Wdqj88uhdc5yA/I2KG7bvo6BB94R/V/so3bt98tvyjDzWj5KzCvFshfWA==, tarball: file:projects/model-text-editor.tgz}
|
||||
file:projects/model-text-editor.tgz(@tiptap/pm@2.2.4):
|
||||
resolution: {integrity: sha512-a2Q7SUya+W86b84tBEmHhf0+LJNVxtfweKCuwzGh77gfmSz8ok0aQjpRA61JHQuk8NfP96K+CKAA4oJh1LMpAg==, tarball: file:projects/model-text-editor.tgz}
|
||||
id: file:projects/model-text-editor.tgz
|
||||
name: '@rush-temp/model-text-editor'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@tiptap/starter-kit': 2.2.4(@tiptap/pm@2.2.4)
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint: 8.56.0
|
||||
@ -22577,9 +22576,9 @@ packages:
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
prettier: 3.2.5
|
||||
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
- '@tiptap/pm'
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
@ -23287,7 +23286,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/presentation.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-PcNk328Y0w6TESBRrSlt0ARPyyFaygF6vO3aIi27DcqbF6SQJi50QFk3PShNb/OVhGkFTyXx8mn++2WpI4QPKA==, tarball: file:projects/presentation.tgz}
|
||||
resolution: {integrity: sha512-qkCAH3xI1PgIlcZMYba7hsH3mpizW9nfMF13DaxgTu7glkC4ycPl+CHxhiqUadpHPfPQXNk6g7mNj2fS9ZyuGw==, tarball: file:projects/presentation.tgz}
|
||||
id: file:projects/presentation.tgz
|
||||
name: '@rush-temp/presentation'
|
||||
version: 0.0.0
|
||||
@ -26918,7 +26917,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/text-editor-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-wMIq/Qz9IudMF8tWnHCcSb11OmrfEByJDICav6WZMUK3EZRHp+YMhGYOtDsOaCSeN/CuJxnR8IUU9G+F5zTTBw==, tarball: file:projects/text-editor-assets.tgz}
|
||||
resolution: {integrity: sha512-HrJXNpWkSNwguy4rSL6Cihc3/1eTmi0KQAmyLix1ci88uOK/9kKga7lMnKAsuobrB9DjbuLgPpcEMNpavMi13w==, tarball: file:projects/text-editor-assets.tgz}
|
||||
id: file:projects/text-editor-assets.tgz
|
||||
name: '@rush-temp/text-editor-assets'
|
||||
version: 0.0.0
|
||||
|
@ -55,6 +55,7 @@
|
||||
"@hcengineering/training": "^0.1.0",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.20"
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/text-editor": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import notification from '@hcengineering/notification'
|
||||
import setting from '@hcengineering/setting'
|
||||
import tags from '@hcengineering/tags'
|
||||
import print from '@hcengineering/model-print'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
|
||||
import documents from './plugin'
|
||||
import { definePermissions } from './permissions'
|
||||
@ -866,6 +867,7 @@ export function createModel (builder: Builder): void {
|
||||
definePermissions(builder)
|
||||
defineNotifications(builder)
|
||||
defineSearch(builder)
|
||||
defineTextActions(builder)
|
||||
}
|
||||
|
||||
export function defineNotifications (builder: Builder): void {
|
||||
@ -1018,5 +1020,17 @@ export function defineSearch (builder: Builder): void {
|
||||
)
|
||||
}
|
||||
|
||||
export function defineTextActions (builder: Builder): void {
|
||||
// Comment category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: documents.function.Comment,
|
||||
icon: chunter.icon.Chunter,
|
||||
visibilityTester: documents.function.IsCommentVisible,
|
||||
label: chunter.string.Message,
|
||||
category: 100,
|
||||
index: 5
|
||||
})
|
||||
}
|
||||
|
||||
export { documentsOperation } from './migration'
|
||||
export { documents as default }
|
||||
|
@ -22,6 +22,7 @@ import { type TagCategory } from '@hcengineering/tags'
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
import { type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||
import { type NotificationType, type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type TextActionVisibleFunction, type TextActionFunction } from '@hcengineering/text-editor'
|
||||
|
||||
export default mergeIds(documentsId, documents, {
|
||||
component: {
|
||||
@ -55,7 +56,11 @@ export default mergeIds(documentsId, documents, {
|
||||
OtherTemplate: '' as Ref<TagCategory>
|
||||
},
|
||||
function: {
|
||||
DocumentIdentifierProvider: '' as Resource<<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>>
|
||||
DocumentIdentifierProvider: '' as Resource<
|
||||
<T extends Doc>(client: Client, ref: Ref<T>, doc?: T) => Promise<string>
|
||||
>,
|
||||
Comment: '' as Resource<TextActionFunction>,
|
||||
IsCommentVisible: '' as Resource<TextActionVisibleFunction>
|
||||
},
|
||||
actionImpl: {
|
||||
AddCollaborativeSectionAbove: '' as ViewAction,
|
||||
|
@ -25,7 +25,8 @@
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.3.3",
|
||||
"@hcengineering/text-editor-resources": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
|
@ -16,13 +16,22 @@
|
||||
import { DOMAIN_MODEL } from '@hcengineering/core'
|
||||
import { type Builder, Model } from '@hcengineering/model'
|
||||
import core, { TDoc } from '@hcengineering/model-core'
|
||||
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
|
||||
import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import {
|
||||
type ExtensionCreator,
|
||||
type TextEditorExtensionFactory,
|
||||
type RefInputAction,
|
||||
type RefInputActionItem
|
||||
type RefInputActionItem,
|
||||
type TextEditorAction,
|
||||
type TextActionFunction,
|
||||
type TextActionVisibleFunction,
|
||||
type TextActionActiveFunction,
|
||||
type ActiveDescriptor,
|
||||
type TogglerDescriptor,
|
||||
type TextEditorActionKind
|
||||
} from '@hcengineering/text-editor'
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { EditorKitOptions } from '@hcengineering/text-editor-resources'
|
||||
import textEditor from './plugin'
|
||||
|
||||
export { textEditorOperation } from './migration'
|
||||
@ -45,6 +54,290 @@ export class TTextEditorExtensionFactory extends TDoc implements TextEditorExten
|
||||
create!: Resource<ExtensionCreator>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TRefInputActionItem, TTextEditorExtensionFactory)
|
||||
@Model(textEditor.class.TextEditorAction, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TTextEditorAction extends TDoc implements TextEditorAction {
|
||||
kind?: TextEditorActionKind
|
||||
action!: TogglerDescriptor | Resource<TextActionFunction>
|
||||
visibilityTester?: Resource<TextActionVisibleFunction>
|
||||
icon!: Asset
|
||||
isActive?: ActiveDescriptor | Resource<TextActionActiveFunction>
|
||||
label!: IntlString
|
||||
category!: number
|
||||
index!: number
|
||||
}
|
||||
|
||||
function createHeaderAction (builder: Builder, level: number): void {
|
||||
let icon: Asset
|
||||
switch (level) {
|
||||
case 1:
|
||||
icon = textEditor.icon.Header1
|
||||
break
|
||||
case 2:
|
||||
icon = textEditor.icon.Header2
|
||||
break
|
||||
case 3:
|
||||
icon = textEditor.icon.Header3
|
||||
break
|
||||
default:
|
||||
throw new Error(`Not supported header level: ${level}`)
|
||||
}
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleHeading',
|
||||
params: {
|
||||
level
|
||||
}
|
||||
},
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
icon,
|
||||
isActive: {
|
||||
name: 'heading',
|
||||
params: {
|
||||
level
|
||||
}
|
||||
},
|
||||
label: getEmbeddedLabel(`H${level}`),
|
||||
category: 10,
|
||||
index: 5 * level
|
||||
})
|
||||
}
|
||||
|
||||
function createImageAlignmentAction (builder: Builder, align: 'center' | 'left' | 'right'): void {
|
||||
let icon: Asset
|
||||
let label: IntlString
|
||||
let index: number
|
||||
switch (align) {
|
||||
case 'left':
|
||||
icon = textEditor.icon.AlignLeft
|
||||
label = textEditor.string.AlignLeft
|
||||
index = 5
|
||||
break
|
||||
case 'center':
|
||||
icon = textEditor.icon.AlignCenter
|
||||
label = textEditor.string.AlignCenter
|
||||
index = 10
|
||||
break
|
||||
case 'right':
|
||||
icon = textEditor.icon.AlignRight
|
||||
label = textEditor.string.AlignRight
|
||||
index = 15
|
||||
break
|
||||
}
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
kind: 'image',
|
||||
action: {
|
||||
command: 'setImageAlignment',
|
||||
params: {
|
||||
align
|
||||
}
|
||||
},
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
icon,
|
||||
isActive: {
|
||||
name: 'image',
|
||||
params: {
|
||||
align
|
||||
}
|
||||
},
|
||||
label,
|
||||
category: 80,
|
||||
index
|
||||
})
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TRefInputActionItem, TTextEditorExtensionFactory, TTextEditorAction)
|
||||
|
||||
createHeaderAction(builder, 1)
|
||||
createHeaderAction(builder, 2)
|
||||
createHeaderAction(builder, 3)
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleBold'
|
||||
},
|
||||
icon: textEditor.icon.Bold,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'bold'
|
||||
},
|
||||
label: textEditor.string.Bold,
|
||||
category: 20,
|
||||
index: 5
|
||||
})
|
||||
|
||||
// Decoration category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleItalic'
|
||||
},
|
||||
icon: textEditor.icon.Italic,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'italic'
|
||||
},
|
||||
label: textEditor.string.Italic,
|
||||
category: 20,
|
||||
index: 10
|
||||
})
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleStrike'
|
||||
},
|
||||
icon: textEditor.icon.Strikethrough,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'strike'
|
||||
},
|
||||
label: textEditor.string.Strikethrough,
|
||||
category: 20,
|
||||
index: 15
|
||||
})
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleUnderline'
|
||||
},
|
||||
icon: textEditor.icon.Underline,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'underline'
|
||||
},
|
||||
label: textEditor.string.Underlined,
|
||||
category: 20,
|
||||
index: 20
|
||||
})
|
||||
|
||||
// Link category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: textEditor.function.FormatLink,
|
||||
icon: textEditor.icon.Link,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'link'
|
||||
},
|
||||
label: textEditor.string.Link,
|
||||
category: 30,
|
||||
index: 5
|
||||
})
|
||||
|
||||
// List category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleOrderedList'
|
||||
},
|
||||
icon: textEditor.icon.ListNumber,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'orderedList'
|
||||
},
|
||||
label: textEditor.string.OrderedList,
|
||||
category: 40,
|
||||
index: 5
|
||||
})
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleBulletList'
|
||||
},
|
||||
icon: textEditor.icon.ListBullet,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'bulletList'
|
||||
},
|
||||
label: textEditor.string.BulletedList,
|
||||
category: 40,
|
||||
index: 10
|
||||
})
|
||||
|
||||
// Quote category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleBlockquote'
|
||||
},
|
||||
icon: textEditor.icon.Quote,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'blockquote'
|
||||
},
|
||||
label: textEditor.string.Blockquote,
|
||||
category: 50,
|
||||
index: 5
|
||||
})
|
||||
|
||||
// Code category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleCode'
|
||||
},
|
||||
icon: textEditor.icon.Code,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'code'
|
||||
},
|
||||
label: textEditor.string.Code,
|
||||
category: 60,
|
||||
index: 5
|
||||
})
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: {
|
||||
command: 'toggleCodeBlock'
|
||||
},
|
||||
icon: textEditor.icon.CodeBlock,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
isActive: {
|
||||
name: 'codeBlock'
|
||||
},
|
||||
label: textEditor.string.CodeBlock,
|
||||
category: 60,
|
||||
index: 10
|
||||
})
|
||||
|
||||
// Table category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
action: textEditor.function.OpenTableOptions,
|
||||
icon: textEditor.icon.TableProps,
|
||||
visibilityTester: textEditor.function.IsEditableTableActive,
|
||||
label: textEditor.string.TableOptions,
|
||||
category: 70,
|
||||
index: 5
|
||||
})
|
||||
|
||||
// Image align category
|
||||
createImageAlignmentAction(builder, 'left')
|
||||
createImageAlignmentAction(builder, 'center')
|
||||
createImageAlignmentAction(builder, 'right')
|
||||
|
||||
// Image view category
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
kind: 'image',
|
||||
action: textEditor.function.OpenImage,
|
||||
icon: textEditor.icon.ScaleOut,
|
||||
label: textEditor.string.ViewImage,
|
||||
category: 90,
|
||||
index: 5
|
||||
})
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
kind: 'image',
|
||||
action: textEditor.function.ExpandImage,
|
||||
icon: textEditor.icon.Expand,
|
||||
label: textEditor.string.ViewOriginal,
|
||||
category: 90,
|
||||
index: 10
|
||||
})
|
||||
|
||||
builder.createDoc(textEditor.class.TextEditorAction, core.space.Model, {
|
||||
kind: 'image',
|
||||
action: textEditor.function.MoreImageActions,
|
||||
visibilityTester: textEditor.function.IsEditable,
|
||||
icon: textEditor.icon.MoreH,
|
||||
label: textEditor.string.MoreActions,
|
||||
category: 100,
|
||||
index: 5
|
||||
})
|
||||
}
|
||||
|
@ -14,8 +14,22 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
// Import plugin directly to prevent .svelte components to being exposed to type typescript.
|
||||
import textEditor, { textEditorId } from '@hcengineering/text-editor'
|
||||
import { mergeIds, type Resource } from '@hcengineering/platform'
|
||||
import textEditor, {
|
||||
type TextActionFunction,
|
||||
type TextActionVisibleFunction,
|
||||
textEditorId
|
||||
} from '@hcengineering/text-editor'
|
||||
|
||||
export default mergeIds(textEditorId, textEditor, {})
|
||||
export default mergeIds(textEditorId, textEditor, {
|
||||
function: {
|
||||
FormatLink: '' as Resource<TextActionFunction>,
|
||||
OpenTableOptions: '' as Resource<TextActionFunction>,
|
||||
OpenImage: '' as Resource<TextActionFunction>,
|
||||
ExpandImage: '' as Resource<TextActionFunction>,
|
||||
MoreImageActions: '' as Resource<TextActionFunction>,
|
||||
|
||||
IsEditableTableActive: '' as Resource<TextActionVisibleFunction>,
|
||||
IsEditable: '' as Resource<TextActionVisibleFunction>
|
||||
}
|
||||
})
|
||||
|
@ -17,13 +17,12 @@
|
||||
import { createEventDispatcher, onDestroy, tick } from 'svelte'
|
||||
import { CollaborativeDocumentSection } from '@hcengineering/controlled-documents'
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { navigate } from '@hcengineering/ui'
|
||||
import { CollaborativeDoc, Ref, generateId, Blob } from '@hcengineering/core'
|
||||
import view from '@hcengineering/view'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Heading, TextNodeAction } from '@hcengineering/text-editor'
|
||||
import { Editor, Heading } from '@hcengineering/text-editor'
|
||||
import {
|
||||
CollaboratorEditor,
|
||||
FocusExtension,
|
||||
@ -31,7 +30,8 @@
|
||||
IsEmptyContentExtension,
|
||||
NodeHighlightExtension,
|
||||
NodeHighlightType,
|
||||
highlightUpdateCommand
|
||||
highlightUpdateCommand,
|
||||
getNodeElement
|
||||
} from '@hcengineering/text-editor-resources'
|
||||
import { getCollaborationUser, getObjectLinkFragment } from '@hcengineering/view-resources'
|
||||
|
||||
@ -46,8 +46,7 @@
|
||||
$isEditable as isEditable,
|
||||
documentCommentsDisplayRequested,
|
||||
documentCommentsHighlightUpdated,
|
||||
documentCommentsLocationNavigateRequested,
|
||||
showAddCommentPopupFx
|
||||
documentCommentsLocationNavigateRequested
|
||||
} from '../../../stores/editors/document'
|
||||
|
||||
export let value: CollaborativeDocumentSection
|
||||
@ -61,6 +60,7 @@
|
||||
let textEditor: CollaboratorEditor
|
||||
let isFocused = false
|
||||
let isEmpty = true
|
||||
let editor: Editor
|
||||
|
||||
const handleRefreshHighlight = () => {
|
||||
if (!textEditor) {
|
||||
@ -90,7 +90,7 @@
|
||||
|
||||
await tick()
|
||||
|
||||
const element = textEditor.getNodeElement(nodeId)
|
||||
const element = getNodeElement(editor, nodeId)
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
@ -98,20 +98,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
const handleGetNodeId = () => {
|
||||
if (isEmpty || !textEditor) {
|
||||
return null
|
||||
}
|
||||
if (!selectedNodeId) {
|
||||
const nodeId = generateId()
|
||||
if (textEditor.setNodeUuid(nodeId)) {
|
||||
selectedNodeId = nodeId
|
||||
}
|
||||
}
|
||||
|
||||
return selectedNodeId
|
||||
}
|
||||
|
||||
const handleNodeHighlight = (id: string) => {
|
||||
if ($highlighted) {
|
||||
const { sectionKey, nodeId } = $highlighted
|
||||
@ -127,30 +113,6 @@
|
||||
return null
|
||||
}
|
||||
|
||||
const handleCommentAction = (): void => {
|
||||
if (isEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!$canAddDocumentComments) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedNodeId) {
|
||||
selectedNodeId = handleGetNodeId()
|
||||
}
|
||||
|
||||
if (selectedNodeId) {
|
||||
showAddCommentPopupFx({
|
||||
element: textEditor.getNodeElement(selectedNodeId),
|
||||
nodeId: selectedNodeId,
|
||||
sectionKey: value.key
|
||||
})
|
||||
|
||||
textEditor.selectNode(selectedNodeId)
|
||||
}
|
||||
}
|
||||
|
||||
const handleShowDocumentComments = (uuid: string) => {
|
||||
if (!uuid) {
|
||||
return
|
||||
@ -161,7 +123,7 @@
|
||||
}
|
||||
|
||||
documentCommentsDisplayRequested({
|
||||
element: textEditor.getNodeElement(uuid),
|
||||
element: getNodeElement(editor, uuid),
|
||||
nodeId: uuid,
|
||||
sectionKey: value.key
|
||||
})
|
||||
@ -247,13 +209,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const commentAction: TextNodeAction = {
|
||||
id: '#comment',
|
||||
icon: chunter.icon.Chunter,
|
||||
label: chunter.string.Message,
|
||||
action: handleCommentAction
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribeHighlightRefresh()
|
||||
unsubscribeNavigateToLocation()
|
||||
@ -274,19 +229,19 @@
|
||||
{#key value._id}
|
||||
<CollaboratorEditor
|
||||
bind:this={textEditor}
|
||||
objectId={value.attachedTo}
|
||||
objectClass={value.attachedToClass}
|
||||
objectId={value._id}
|
||||
objectClass={value._class}
|
||||
objectSpace={value.space}
|
||||
{collaborativeDoc}
|
||||
{initialCollaborativeDoc}
|
||||
{user}
|
||||
readonly={!$isEditable}
|
||||
field={value.collaboratorSectionId}
|
||||
textNodeActions={$canAddDocumentComments && !isEmpty ? [commentAction] : []}
|
||||
editorAttributes={{ style: 'padding: 0 2em; margin: 0 -2em;' }}
|
||||
overflow="none"
|
||||
canShowPopups={!$arePopupsOpened}
|
||||
onExtensions={handleExtensions}
|
||||
on:editor={(e) => (editor = e.detail)}
|
||||
on:open-document={async (event) => {
|
||||
const doc = await client.findOne(event.detail._class, { _id: event.detail._id })
|
||||
if (doc != null) {
|
||||
|
@ -117,6 +117,7 @@ import {
|
||||
createDocument,
|
||||
createTemplate
|
||||
} from './utils'
|
||||
import { comment, isCommentVisible } from './text'
|
||||
|
||||
export { DocumentStatusTag, DocumentTitle, DocumentVersionPresenter, StatePresenter }
|
||||
|
||||
@ -372,7 +373,9 @@ export default async (): Promise<Resources> => ({
|
||||
GetDocumentMetaLinkFragment: getDocumentMetaLinkFragment,
|
||||
IsLatestDraftDoc: isLatestDraftDoc,
|
||||
DocumentIdentifierProvider: documentIdentifierProvider,
|
||||
ControlledDocumentTitleProvider: getControlledDocumentTitle
|
||||
ControlledDocumentTitleProvider: getControlledDocumentTitle,
|
||||
Comment: comment,
|
||||
IsCommentVisible: isCommentVisible
|
||||
},
|
||||
actionImpl: {
|
||||
AddCollaborativeSectionAbove: addCollaborativeSectionAbove,
|
||||
|
209
plugins/controlled-documents-resources/src/text.ts
Normal file
209
plugins/controlled-documents-resources/src/text.ts
Normal file
@ -0,0 +1,209 @@
|
||||
//
|
||||
// Copyright © 2024 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.
|
||||
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import documents, {
|
||||
ControlledDocumentState,
|
||||
DocumentState,
|
||||
type EditorMode,
|
||||
type ControlledDocument,
|
||||
type DocumentReviewRequest,
|
||||
type DocumentSection
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import { generateId, getCurrentAccount, type Ref } from '@hcengineering/core'
|
||||
import { type PersonAccount } from '@hcengineering/contact'
|
||||
import { RequestStatus } from '@hcengineering/request'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { type ActionContext } from '@hcengineering/text-editor'
|
||||
import { getNodeElement, selectNode, nodeUuidName } from '@hcengineering/text-editor-resources'
|
||||
|
||||
import { getCurrentEmployee } from './utils'
|
||||
import { showAddCommentPopupFx } from './stores/editors/document'
|
||||
import { $editorMode } from './stores/editors/document/editor'
|
||||
|
||||
async function getCurrentReviewRequest (doc: ControlledDocument): Promise<DocumentReviewRequest | undefined> {
|
||||
const client = getClient()
|
||||
|
||||
return await client.findOne(documents.class.DocumentReviewRequest, {
|
||||
attachedTo: doc._id,
|
||||
attachedToClass: doc._class,
|
||||
status: RequestStatus.Active
|
||||
})
|
||||
}
|
||||
|
||||
async function getDocumentStateForCurrentUser (
|
||||
doc: ControlledDocument
|
||||
): Promise<DocumentState | ControlledDocumentState | null> {
|
||||
if (doc == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const reviewRequest = await getCurrentReviewRequest(doc)
|
||||
|
||||
if (doc.controlledState === ControlledDocumentState.InReview) {
|
||||
if (reviewRequest == null) {
|
||||
return ControlledDocumentState.InReview
|
||||
}
|
||||
|
||||
const currentAccount = getCurrentAccount()._id as Ref<PersonAccount>
|
||||
if (reviewRequest.approved?.includes(currentAccount)) {
|
||||
return ControlledDocumentState.Reviewed
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.controlledState != null) {
|
||||
return doc.controlledState
|
||||
}
|
||||
|
||||
return doc.state
|
||||
}
|
||||
|
||||
function isDocumentOwner (doc: ControlledDocument): boolean {
|
||||
if (doc == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const employee = getCurrentEmployee()
|
||||
|
||||
return doc.owner === employee
|
||||
}
|
||||
|
||||
function isDocumentCoAuthor (doc: ControlledDocument): boolean {
|
||||
if (doc == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const employee = getCurrentEmployee()
|
||||
|
||||
if (employee === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
return doc.coAuthors.includes(employee)
|
||||
}
|
||||
|
||||
function isDocumentReviewer (doc: ControlledDocument): boolean {
|
||||
if (doc == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const employee = getCurrentEmployee()
|
||||
if (employee == null) {
|
||||
return false
|
||||
}
|
||||
return doc.reviewers?.includes(employee) ?? false
|
||||
}
|
||||
|
||||
function canViewDocumentComments (doc: ControlledDocument, mode: EditorMode): boolean {
|
||||
return (
|
||||
(isDocumentOwner(doc) || isDocumentCoAuthor(doc) || isDocumentReviewer(doc)) &&
|
||||
(mode === 'viewing' || mode === 'editing')
|
||||
)
|
||||
}
|
||||
|
||||
async function canAddDocumentComments (doc: ControlledDocument, mode: EditorMode): Promise<boolean> {
|
||||
if (!canViewDocumentComments(doc, mode)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const state = await getDocumentStateForCurrentUser(doc)
|
||||
|
||||
if (state === DocumentState.Draft) {
|
||||
return isDocumentOwner(doc) || isDocumentCoAuthor(doc)
|
||||
}
|
||||
|
||||
if (state === ControlledDocumentState.InReview) {
|
||||
return isDocumentOwner(doc) || isDocumentCoAuthor(doc) || isDocumentReviewer(doc)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function markNodeWithUuid (editor: Editor): string | undefined {
|
||||
if (editor === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const nodeId = generateId()
|
||||
editor.commands.setNodeUuid(nodeId)
|
||||
|
||||
return nodeId
|
||||
}
|
||||
|
||||
export async function comment (editor: Editor, event: MouseEvent, ctx: ActionContext): Promise<void> {
|
||||
const { objectId, objectClass } = ctx
|
||||
if (objectId === undefined || objectClass === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const section = (await client.findOne(objectClass, {
|
||||
_id: objectId
|
||||
})) as DocumentSection
|
||||
|
||||
if (section === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let selectedNodeId = editor.extensionStorage[nodeUuidName].activeNodeUuid
|
||||
|
||||
if (selectedNodeId == null) {
|
||||
selectedNodeId = markNodeWithUuid(editor)
|
||||
}
|
||||
|
||||
if (selectedNodeId == null) {
|
||||
return
|
||||
}
|
||||
|
||||
await showAddCommentPopupFx({
|
||||
element: getNodeElement(editor, selectedNodeId),
|
||||
nodeId: selectedNodeId,
|
||||
sectionKey: section.key
|
||||
})
|
||||
|
||||
selectNode(editor, selectedNodeId)
|
||||
}
|
||||
|
||||
export async function isCommentVisible (editor: Editor, ctx: ActionContext): Promise<boolean> {
|
||||
const { objectId, objectClass } = ctx
|
||||
if (objectId === undefined || objectClass === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
if (!client.getHierarchy().isDerived(objectClass, documents.class.DocumentSection)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const section = (await client.findOne(objectClass, {
|
||||
_id: objectId
|
||||
})) as DocumentSection
|
||||
|
||||
if (section === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
const doc = await client.findOne(documents.class.ControlledDocument, {
|
||||
_id: section.attachedTo as Ref<ControlledDocument>
|
||||
})
|
||||
|
||||
if (doc === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: move editor mode tracking to a new extension!
|
||||
const mode = $editorMode.getState()
|
||||
|
||||
return await canAddDocumentComments(doc, mode)
|
||||
}
|
186
plugins/text-editor-assets/assets/icons.svg
Normal file
186
plugins/text-editor-assets/assets/icons.svg
Normal file
@ -0,0 +1,186 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="header1" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M8 8C8 7.44772 7.55228 7 7 7C6.44772 7 6 7.44772 6 8V24C6 24.5523 6.44772 25 7 25C7.55228 25 8 24.5523 8 24V17H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V8C19 7.44772 18.5523 7 18 7C17.4477 7 17 7.44772 17 8V15H8V8Z" />
|
||||
<path
|
||||
d="M25.8751 15.5C25.8751 15.0709 25.5639 14.7051 25.1403 14.6363C24.7166 14.5676 24.3057 14.8162 24.17 15.2233L24.104 15.4213C23.8842 16.0806 23.451 16.6478 22.8728 17.0333L22.5147 17.272C22.1126 17.54 22.004 18.0833 22.272 18.4854C22.5401 18.8875 23.0834 18.9961 23.4855 18.7281L23.8435 18.4894C23.94 18.425 24.17 16.1562 24.1251 18.2868V23.125H23.0001C22.5168 23.125 22.1251 23.5168 22.1251 24C22.1251 24.4833 22.5168 24.875 23.0001 24.875H27.0001C27.4833 24.875 27.8751 24.4833 27.8751 24C27.8751 23.5168 27.4833 23.125 27.0001 23.125H25.8751V15.5Z" />
|
||||
</symbol>
|
||||
<symbol id="header2" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M8 8C8 7.44772 7.55228 7 7 7C6.44772 7 6 7.44772 6 8V24C6 24.5523 6.44772 25 7 25C7.55228 25 8 24.5523 8 24V17H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V8C19 7.44772 18.5523 7 18 7C17.4477 7 17 7.44772 17 8V15H8V8Z" />
|
||||
<path
|
||||
d="M25.0002 14.625C23.6175 14.6239 22.6106 15.4018 22.1701 16.7233C22.0173 17.1817 22.2651 17.6773 22.7235 17.8301C23.182 17.9829 23.6775 17.7351 23.8303 17.2767C24.0381 16.6534 24.3614 16.375 25.0002 16.375C25.6752 16.375 26.1033 16.8525 26.1252 17.5065C26.1228 18.4508 25.739 19.0073 25 19.5C23.5 20.5 22.1252 21.5 22.1252 24C22.1252 24.4832 22.517 24.875 23.0002 24.875H27.5002C27.9835 24.875 28.3752 24.4832 28.3752 24C28.3752 23.5167 27.9829 23.125 27.4997 23.125H24.0953C24.0953 22 26 21 27 20C27.509 19.4273 27.8752 18.4765 27.8752 17.5C27.8525 17.0167 27.7507 16.5443 27.5329 16.1087C27.0474 15.1378 26.0693 14.625 25.0002 14.625Z" />
|
||||
</symbol>
|
||||
<symbol id="header3" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M8 8C8 7.44772 7.55228 7 7 7C6.44772 7 6 7.44772 6 8V24C6 24.5523 6.44772 25 7 25C7.55228 25 8 24.5523 8 24V17H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V8C19 7.44772 18.5523 7 18 7C17.4477 7 17 7.44772 17 8V15H8V8Z" />
|
||||
<path
|
||||
d="M24.5983 16.8163C24.7929 16.7503 25.0009 16.7332 25.2037 16.7667C25.4065 16.8002 25.5979 16.8832 25.761 17.0083C25.9241 17.1334 26.0538 17.2969 26.1387 17.4841C26.2235 17.6714 26.2608 17.8766 26.2474 18.0818C26.234 18.2869 26.1701 18.4855 26.0616 18.6601C25.953 18.8347 25.8031 18.9798 25.6251 19.0825C25.6021 19.0958 25.5799 19.11 25.5585 19.125H25.0001C24.5168 19.125 24.1251 19.5168 24.1251 20C24.1251 20.4832 24.5168 20.875 25.0001 20.875H25.5586C25.5799 20.89 25.6021 20.9042 25.6252 20.9175C25.8032 21.0202 25.9531 21.1653 26.0617 21.3399C26.1702 21.5145 26.234 21.7131 26.2475 21.9182C26.2609 22.1234 26.2236 22.3286 26.1387 22.5159C26.0539 22.7031 25.9242 22.8665 25.7611 22.9917C25.598 23.1168 25.4066 23.1998 25.2038 23.2333C25.001 23.2668 24.793 23.2497 24.5984 23.1837C24.4037 23.1176 24.2283 23.0045 24.0878 22.8545C23.9473 22.7045 23.8459 22.5221 23.7927 22.3235C23.6677 21.8567 23.1879 21.5797 22.7211 21.7048C22.2543 21.8299 21.9773 22.3097 22.1024 22.7765C22.2301 23.253 22.4733 23.6907 22.8106 24.0508C23.1478 24.4109 23.5687 24.6822 24.0358 24.8408C24.503 24.9994 25.0021 25.0403 25.4888 24.9599C25.9756 24.8796 26.435 24.6804 26.8264 24.3801C27.2178 24.0797 27.5291 23.6875 27.7327 23.2381C27.9364 22.7887 28.026 22.2961 27.9937 21.8038C27.9615 21.3115 27.8083 20.8347 27.5478 20.4158C27.4558 20.2679 27.3515 20.1289 27.2362 20C27.3514 19.8711 27.4557 19.732 27.5477 19.5842C27.8082 19.1652 27.9614 18.6885 27.9937 18.1962C28.0259 17.7039 27.9363 17.2113 27.7327 16.7619C27.5291 16.3125 27.2178 15.9203 26.8264 15.6199C26.435 15.3196 25.9755 15.1204 25.4888 15.0401C25.002 14.9597 24.5029 15.0006 24.0358 15.1592C23.5686 15.3178 23.1477 15.5891 22.8105 15.9492C22.4732 16.3093 22.23 16.747 22.1023 17.2235C21.9772 17.6903 22.2542 18.1701 22.721 18.2952C23.1878 18.4203 23.6676 18.1433 23.7927 17.6765C23.8459 17.4779 23.9472 17.2955 24.0877 17.1455C24.2283 16.9955 24.4036 16.8824 24.5983 16.8163Z" />
|
||||
</symbol>
|
||||
<symbol id="underline" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M10 17V8C10 7.44772 9.55228 7 9 7C8.44772 7 8 7.44772 8 8V17C8 21.4183 11.5817 25 16 25H17V24.9381C20.9463 24.446 24 21.0796 24 17V8C24 7.44772 23.5523 7 23 7C22.4477 7 22 7.44772 22 8V17C22 20.3137 19.3137 23 16 23C12.6863 23 10 20.3137 10 17Z" />
|
||||
<path
|
||||
d="M8 27C7.44772 27 7 27.4477 7 28C7 28.5523 7.44772 29 8 29H24C24.5523 29 25 28.5523 25 28C25 27.4477 24.5523 27 24 27H8Z" />
|
||||
</symbol>
|
||||
<symbol id="strikethrough" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M7,7.1C7,6.5,7.3,6,7.8,5.6C8.4,5.3,9.2,5,10.1,5c1.3,0,2.4,0.6,2.8,1.2c0.1,0.2,0.4,0.3,0.7,0.2s0.3-0.4,0.2-0.7C13.1,4.6,11.6,4,10.1,4C9,4,8,4.3,7.3,4.8C6.5,5.4,6,6.2,6,7.1C6,7.8,6.3,8.5,6.8,9h1.7C7.6,8.6,7,7.8,7,7.1zM15.5,10c0.3,0,0.5,0.2,0.5,0.5S15.8,11,15.5,11h-2.3c0.5,0.5,0.8,1.1,0.8,1.9c0,0.9-0.5,1.7-1.3,2.2C12,15.7,11,16,9.9,16c-1.6,0-3-0.7-3.7-1.7C6,14,6.1,13.7,6.3,13.6c0.2-0.2,0.5-0.1,0.7,0.1C7.5,14.5,8.5,15,9.9,15c0.9,0,1.7-0.3,2.3-0.7c0.6-0.4,0.8-0.9,0.8-1.4c0-0.8-0.6-1.5-1.8-1.9H4.5C4.2,11,4,10.8,4,10.5S4.2,10,4.5,10H15.5z" />
|
||||
</symbol>
|
||||
<symbol id="bold" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M18.25 25H10C9.44772 25 9 24.5523 9 24V8C9 7.44772 9.44772 7 10 7H17.5C18.5022 7.00006 19.4834 7.28695 20.3277 7.82679C21.172 8.36662 21.8442 9.13684 22.2649 10.0465C22.6855 10.9561 22.837 11.9671 22.7015 12.96C22.5659 13.953 22.149 14.8864 21.5 15.65C22.3477 16.328 22.9645 17.252 23.2653 18.295C23.5662 19.3379 23.5364 20.4485 23.18 21.4738C22.8236 22.4991 22.1581 23.3887 21.2753 24.0202C20.3924 24.6517 19.3355 24.994 18.25 25ZM12 22H18.23C18.5255 22 18.8181 21.9418 19.091 21.8287C19.364 21.7157 19.6121 21.5499 19.821 21.341C20.0299 21.1321 20.1957 20.884 20.3087 20.611C20.4218 20.3381 20.48 20.0455 20.48 19.75C20.48 19.4545 20.4218 19.1619 20.3087 18.889C20.1957 18.616 20.0299 18.3679 19.821 18.159C19.6121 17.9501 19.364 17.7843 19.091 17.6713C18.8181 17.5582 18.5255 17.5 18.23 17.5H12V22ZM12 14.5H17.5C17.7955 14.5 18.0881 14.4418 18.361 14.3287C18.634 14.2157 18.8821 14.0499 19.091 13.841C19.2999 13.6321 19.4657 13.384 19.5787 13.111C19.6918 12.8381 19.75 12.5455 19.75 12.25C19.75 11.9545 19.6918 11.6619 19.5787 11.389C19.4657 11.116 19.2999 10.8679 19.091 10.659C18.8821 10.4501 18.634 10.2843 18.361 10.1713C18.0881 10.0582 17.7955 10 17.5 10H12V14.5Z" />
|
||||
</symbol>
|
||||
<symbol id="italic" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M24 9C24.5523 9 25 8.55228 25 8C25 7.44772 24.5523 7 24 7H13C12.4477 7 12 7.44772 12 8C12 8.55228 12.4477 9 13 9H17.14L12.77 23H8C7.44771 23 7 23.4477 7 24C7 24.5523 7.44772 25 8 25H19C19.5523 25 20 24.5523 20 24C20 23.4477 19.5523 23 19 23H14.86L19.23 9H24Z" />
|
||||
</symbol>
|
||||
<symbol id="link" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M29.25 6.75997C28.6926 6.20061 28.0302 5.75679 27.3009 5.45396C26.5716 5.15112 25.7897 4.99524 25 4.99524C24.2103 4.99524 23.4284 5.15112 22.6991 5.45396C22.278 5.62881 21.8792 5.85065 21.5101 6.1146C21.0614 6.43545 21.0693 7.07931 21.4594 7.46935C21.8499 7.85988 22.4827 7.84994 22.9575 7.5679C23.1218 7.47029 23.2933 7.38434 23.4707 7.31086C23.9571 7.10938 24.4785 7.00567 25.005 7.00567C25.5315 7.00567 26.0528 7.10938 26.5393 7.31086C27.0257 7.51235 27.4677 7.80767 27.84 8.17997C28.2123 8.55227 28.5076 8.99425 28.7091 9.48068C28.9106 9.96711 29.0143 10.4885 29.0143 11.015C29.0143 11.5415 28.9106 12.0628 28.7091 12.5493C28.5076 13.0357 28.2123 13.4777 27.84 13.85L19.84 21.85C19.0894 22.6019 18.0709 23.0248 17.0085 23.0257C15.9461 23.0267 14.9269 22.6055 14.175 21.855C13.4231 21.1044 13.0001 20.0859 12.9992 19.0235C12.9983 17.9611 13.4194 16.9419 14.17 16.19L14.8803 15.4746C15.2675 15.0846 15.2659 14.4537 14.8787 14.0637C14.4886 13.6709 13.8519 13.6681 13.4604 14.0596L12.75 14.77C12.1906 15.3274 11.7468 15.9897 11.444 16.7191C11.1411 17.4484 10.9852 18.2303 10.9852 19.02C10.9852 19.8097 11.1411 20.5916 11.444 21.3209C11.7468 22.0502 12.1906 22.7125 12.75 23.27C13.8815 24.387 15.41 25.0092 17 25C17.7927 25.0032 18.5782 24.8494 19.3111 24.5473C20.044 24.2452 20.7098 23.8009 21.27 23.24L29.27 15.24C30.3909 14.1123 31.0184 12.5858 31.0147 10.9958C31.0109 9.40582 30.3762 7.88232 29.25 6.75997Z" />
|
||||
<path
|
||||
d="M4.18997 24.82C3.81656 24.4483 3.52026 24.0065 3.31807 23.52C3.11589 23.0335 3.01181 22.5118 3.01181 21.985C3.01181 21.4581 3.11589 20.9365 3.31807 20.4499C3.52026 19.9634 3.81656 19.5216 4.18997 19.15L12.19 11.15C12.5616 10.7766 13.0034 10.4803 13.4899 10.2781C13.9765 10.0759 14.4981 9.97181 15.025 9.97181C15.5518 9.97181 16.0735 10.0759 16.56 10.2781C17.0465 10.4803 17.4883 10.7766 17.86 11.15C18.231 11.5246 18.5231 11.9698 18.7189 12.4594C18.9147 12.9489 19.0103 13.4728 19 14C19.003 14.5288 18.9012 15.0529 18.7004 15.5421C18.4995 16.0313 18.2037 16.4758 17.83 16.85L16.4072 18.2929C16.0213 18.6842 16.0289 19.3189 16.4175 19.7075C16.808 20.098 17.4466 20.1034 17.8371 19.7129L19.25 18.3C20.3785 17.1715 21.0124 15.6409 21.0124 14.045C21.0124 12.449 20.3785 10.9185 19.25 9.78997C18.1215 8.66147 16.5909 8.02749 14.995 8.02749C13.399 8.02749 11.8685 8.66147 10.74 9.78997L2.73997 17.79C2.17911 18.3476 1.73401 19.0106 1.43029 19.7408C1.12657 20.471 0.970215 21.2541 0.970215 22.045C0.970215 22.8358 1.12657 23.6189 1.43029 24.3492C1.73401 25.0794 2.17911 25.7424 2.73997 26.3C3.87879 27.4084 5.41087 28.0198 6.99997 28C8.26594 28.0012 9.49169 27.6069 10.5118 26.885C10.964 26.5651 10.961 25.921 10.5693 25.5293C10.1781 25.1381 9.54699 25.1518 9.0717 25.4348C8.90786 25.5324 8.73689 25.6184 8.56 25.6919C8.07349 25.8941 7.55182 25.9981 7.02497 25.9981C6.49812 25.9981 5.97645 25.8941 5.48994 25.6919C5.00342 25.4897 4.56164 25.1934 4.18997 24.82Z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="listBullet" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M4 9C5.10457 9 6 8.10457 6 7C6 5.89543 5.10457 5 4 5C2.89543 5 2 5.89543 2 7C2 8.10457 2.89543 9 4 9Z" />
|
||||
<path
|
||||
d="M9 7C9 6.44772 9.44772 6 10 6H29C29.5523 6 30 6.44772 30 7C30 7.55228 29.5523 8 29 8H10C9.44772 8 9 7.55228 9 7Z"
|
||||
/>
|
||||
<path
|
||||
d="M9 16C9 15.4477 9.44772 15 10 15H29C29.5523 15 30 15.4477 30 16C30 16.5523 29.5523 17 29 17H10C9.44772 17 9 16.5523 9 16Z"
|
||||
/>
|
||||
<path
|
||||
d="M10 24C9.44772 24 9 24.4477 9 25C9 25.5523 9.44772 26 10 26H29C29.5523 26 30 25.5523 30 25C30 24.4477 29.5523 24 29 24H10Z"
|
||||
/>
|
||||
<path
|
||||
d="M6 16C6 17.1046 5.10457 18 4 18C2.89543 18 2 17.1046 2 16C2 14.8954 2.89543 14 4 14C5.10457 14 6 14.8954 6 16Z"
|
||||
/>
|
||||
<path
|
||||
d="M4 27C5.10457 27 6 26.1046 6 25C6 23.8954 5.10457 23 4 23C2.89543 23 2 23.8954 2 25C2 26.1046 2.89543 27 4 27Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="listNumber" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M11 7C11 6.44772 11.4477 6 12 6H29C29.5523 6 30 6.44772 30 7C30 7.55228 29.5523 8 29 8H12C11.4477 8 11 7.55228 11 7Z"
|
||||
/>
|
||||
<path
|
||||
d="M11 16C11 15.4477 11.4477 15 12 15H29C29.5523 15 30 15.4477 30 16C30 16.5523 29.5523 17 29 17H12C11.4477 17 11 16.5523 11 16Z"
|
||||
/>
|
||||
<path
|
||||
d="M11 25C11 24.4477 11.4477 24 12 24H29C29.5523 24 30 24.4477 30 25C30 25.5523 29.5523 26 29 26H12C11.4477 26 11 25.5523 11 25Z"
|
||||
/>
|
||||
<path
|
||||
d="M4.87524 18C3.49245 17.999 2.48564 18.7768 2.04514 20.0983C1.89232 20.5568 2.14009 21.0523 2.59854 21.2051C3.05699 21.3579 3.55252 21.1102 3.70534 20.6517C3.91309 20.0284 4.23643 19.75 4.87524 19.75C5.55025 19.75 5.97826 20.2275 6.00023 20.8815C5.99778 21.8258 5.61399 22.3824 4.875 22.875C3.375 23.875 2.00024 24.875 2.00024 27.375C2.00024 27.8582 2.39199 28.25 2.87524 28.25H7.3752C7.85845 28.25 8.25024 27.8582 8.25024 27.375C8.25024 26.8918 7.85794 26.5 7.37469 26.5H3.97031C3.97031 25.375 5.875 24.375 6.875 23.375C7.38403 22.8024 7.75024 21.8515 7.75024 20.875C7.72751 20.3917 7.6257 19.9194 7.40786 19.4837C6.92243 18.5128 5.94432 18 4.87524 18Z"
|
||||
/>
|
||||
<path
|
||||
d="M5.75009 4.87508C5.75009 4.44593 5.43886 4.08012 5.01525 4.01138C4.59164 3.94264 4.1807 4.19125 4.04499 4.59838L3.979 4.79638C3.75924 5.45564 3.32601 6.02285 2.7478 6.40832L2.38973 6.64704C1.98764 6.91509 1.87899 7.45835 2.14705 7.86044C2.41511 8.26253 2.95837 8.37118 3.36046 8.10312L3.71853 7.86441C3.81501 7.80009 4.04499 5.53131 4.00009 7.66187V12.5001H2.87509C2.39184 12.5001 2.00009 12.8918 2.00009 13.3751C2.00009 13.8583 2.39184 14.2501 2.87509 14.2501H6.87509C7.35834 14.2501 7.75009 13.8583 7.75009 13.3751C7.75009 12.8918 7.35834 12.5001 6.87509 12.5001H5.75009V4.87508Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="quote" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M10.1,4.7C9.9,4.4,9.7,4.4,9.4,4.6C5.7,7,3.6,9.8,3.2,13c-0.7,5,3.8,7.4,6.1,5.2c2.3-2.2,0.9-5-0.7-5.8 C6.9,11.7,5.9,12,6,11c0.2-1,2.5-3.8,4.6-5.2c0.1-0.1,0.2-0.3,0.1-0.5C10.6,5.2,10.4,5,10.1,4.7z"
|
||||
/>
|
||||
<path
|
||||
d="M20.5,5.8c0.1-0.1,0.2-0.3,0.1-0.5c-0.1-0.1-0.3-0.3-0.5-0.7c-0.2-0.3-0.4-0.3-0.7-0.1C15.6,7,13.5,9.8,13.1,13 c-0.7,5,3.8,7.4,6.1,5.2c2.3-2.2,0.9-5-0.7-5.8c-1.6-0.8-2.6-0.5-2.5-1.5C16.1,10,18.5,7.1,20.5,5.8z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="code" viewBox="0 0 32 32">
|
||||
<path d="M2 16L10 24L11.4142 22.5858L4.82843 16L11.4142 9.41421L10 8L2 16Z" />
|
||||
<path
|
||||
d="M29.9999 16L21.9999 8L20.5857 9.41421L27.1715 16L20.5857 22.5858L21.9999 24L29.9999 16Z" />
|
||||
</symbol>
|
||||
<symbol id="codeBlock" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M4,21.7c-1,0-1.7-0.8-1.7-1.8l0-7c0-0.4,0.3-0.8,0.8-0.8s0.7,0.3,0.7,0.8l0,7c0,0.1,0.1,0.2,0.2,0.2l16,0 c0.1,0,0.2-0.1,0.2-0.2l0-16c0-0.1-0.1-0.3-0.2-0.3l-7,0c-0.4,0-0.8-0.3-0.8-0.8s0.3-0.7,0.8-0.7l7,0c1,0,1.8,0.8,1.7,1.8l0,16 c0,1-0.8,1.8-1.8,1.7L4,21.7z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5,7.9L6,5.3C6.1,5.1,6.2,5,6.2,4.8c0-0.2-0.1-0.3-0.2-0.4C5.9,4.3,5.7,4.2,5.6,4.2c-0.2,0-0.3,0.1-0.4,0.2L2.2,7.4 C2.1,7.6,2,7.7,2,7.9c0,0.2,0.1,0.3,0.2,0.4l2.9,2.8c0.1,0.1,0.3,0.2,0.4,0.2c0.2,0,0.3-0.1,0.4-0.2c0.1-0.1,0.2-0.3,0.2-0.4 S6.1,10.4,6,10.3L3.5,7.9z"
|
||||
/>
|
||||
<path
|
||||
d="M13.9,7.4l-2.9-3.1c-0.1-0.1-0.3-0.2-0.4-0.2c-0.2,0-0.3,0.1-0.4,0.2C9.9,4.5,9.9,4.7,9.9,4.8C9.9,5,9.9,5.1,10,5.3l2.5,2.6 l-2.5,2.4c-0.2,0.2-0.3,0.6,0,0.9c0.1,0.1,0.3,0.2,0.4,0.2c0.2,0,0.3-0.1,0.4-0.2l2.9-2.8c0.1-0.1,0.2-0.3,0.2-0.4 C14.1,7.7,14,7.6,13.9,7.4z"
|
||||
/>
|
||||
<path
|
||||
d="M9,2c0,0-0.1,0-0.1,0C8.6,2,8.3,2.2,8.3,2.5l-1.7,9.8C6.5,12.7,6.7,13,7.1,13c0,0,0.1,0,0.1,0c0.3,0,0.6-0.2,0.6-0.5 l1.7-9.8C9.6,2.4,9.3,2.1,9,2z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="tableProps" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19.2,11.2C19,11,18.8,11,18.6,11c-0.2,0-0.4,0.1-0.5,0.3l-5.2,7.2c-0.1,0.1-0.1,0.3-0.1,0.4v2c0,0.4,0.3,0.8,0.8,0.8h2c0.2,0,0.5-0.1,0.6-0.3l5.5-7.5c0.2-0.3,0.2-0.8-0.1-1L19.2,11.2z M15.1,20.2h-0.9v-1l4.7-6.4l1.1,0.8L15.1,20.2z"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M19.5,2.2h-15c-1.2,0-2.2,1-2.2,2.2v2v3v10c0,1.2,1,2.2,2.2,2.2h7c0.4,0,0.8-0.3,0.8-0.8s-0.3-0.8-0.8-0.8h-2V8.8h5v4.1c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8V8.8h4.2V10v0.9c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8V10V7V4.5C21.8,3.3,20.7,2.2,19.5,2.2z M8,20.2H4.5c-0.4,0-0.8-0.3-0.8-0.8v-10V8.8H8V20.2z M3.8,7.2V6.5v-2c0-0.4,0.3-0.8,0.8-0.8h15c0.4,0,0.8,0.3,0.8,0.8V7v0.2H3.8z"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="alignLeft" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M5 4C4.44772 4 4 4.44772 4 5V27C4 27.5523 4.44772 28 5 28C5.55228 28 6 27.5523 6 27V5C6 4.44772 5.55228 4 5 4Z"
|
||||
/>
|
||||
<path
|
||||
d="M27 8C27.5523 8 28 8.44772 28 9C28 9.55228 27.5523 10 27 10H11C10.4477 10 10 9.55228 10 9C10 8.44772 10.4477 8 11 8H27Z"
|
||||
/>
|
||||
<path
|
||||
d="M22 15C22.5523 15 23 15.4477 23 16C23 16.5523 22.5523 17 22 17H11C10.4477 17 10 16.5523 10 16C10 15.4477 10.4477 15 11 15H22Z"
|
||||
/>
|
||||
<path
|
||||
d="M28 23C28 22.4477 27.5523 22 27 22H11C10.4477 22 10 22.4477 10 23C10 23.5523 10.4477 24 11 24H27C27.5523 24 28 23.5523 28 23Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="alignCenter" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M15 5C15 4.44772 15.4477 4 16 4C16.5523 4 17 4.44772 17 5V27C17 27.5523 16.5523 28 16 28C15.4477 28 15 27.5523 15 27V24H7C6.44772 24 6 23.5523 6 23C6 22.4477 6.44772 22 7 22H15V17H9C8.44772 17 8 16.5523 8 16C8 15.4477 8.44772 15 9 15H15V10H5C4.44772 10 4 9.55228 4 9C4 8.44772 4.44772 8 5 8H15V5Z"
|
||||
/>
|
||||
<path d="M19 24H25C25.5523 24 26 23.5523 26 23C26 22.4477 25.5523 22 25 22H19V24Z" />
|
||||
<path d="M19 17H22C22.5523 17 23 16.5523 23 16C23 15.4477 22.5523 15 22 15H19V17Z" />
|
||||
<path d="M19 10H27C27.5523 10 28 9.55228 28 9C28 8.44772 27.5523 8 27 8H19V10Z" />
|
||||
</symbol>
|
||||
<symbol id="alignRight" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M27 4C26.4477 4 26 4.44772 26 5V27C26 27.5523 26.4477 28 27 28C27.5523 28 28 27.5523 28 27V5C28 4.44772 27.5523 4 27 4Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 8C4.44772 8 4 8.44772 4 9C4 9.55228 4.44772 10 5 10H21C21.5523 10 22 9.55228 22 9C22 8.44772 21.5523 8 21 8H5Z"
|
||||
/>
|
||||
<path
|
||||
d="M10 15C9.44772 15 9 15.4477 9 16C9 16.5523 9.44772 17 10 17H21C21.5523 17 22 16.5523 22 16C22 15.4477 21.5523 15 21 15H10Z"
|
||||
/>
|
||||
<path
|
||||
d="M4 23C4 22.4477 4.44772 22 5 22H21C21.5523 22 22 22.4477 22 23C22 23.5523 21.5523 24 21 24H5C4.44772 24 4 23.5523 4 23Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="moreH" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M8 18C9.10457 18 10 17.1046 10 16C10 14.8954 9.10457 14 8 14C6.89543 14 6 14.8954 6 16C6 17.1046 6.89543 18 8 18Z"
|
||||
/>
|
||||
<path
|
||||
d="M16 18C17.1046 18 18 17.1046 18 16C18 14.8954 17.1046 14 16 14C14.8954 14 14 14.8954 14 16C14 17.1046 14.8954 18 16 18Z"
|
||||
/>
|
||||
<path
|
||||
d="M26 16C26 17.1046 25.1046 18 24 18C22.8954 18 22 17.1046 22 16C22 14.8954 22.8954 14 24 14C25.1046 14 26 14.8954 26 16Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="expand" viewBox="-2 -2 16 16">
|
||||
<path
|
||||
d="M7.75 0.75C7.75 0.335786 8.08579 0 8.5 0H11.25C11.6642 0 12 0.335786 12 0.75V3.5C12 3.91421 11.6642 4.25 11.25 4.25C10.8358 4.25 10.5 3.91421 10.5 3.5V2.56066L7.78033 5.28033C7.48744 5.57322 7.01256 5.57322 6.71967 5.28033C6.42678 4.98744 6.42678 4.51256 6.71967 4.21967L9.43934 1.5H8.5C8.08579 1.5 7.75 1.16421 7.75 0.75ZM5.28033 6.71967C5.57322 7.01256 5.57322 7.48744 5.28033 7.78033L2.56066 10.5H3.5C3.91421 10.5 4.25 10.8358 4.25 11.25C4.25 11.6642 3.91421 12 3.5 12H0.75C0.335786 12 0 11.6642 0 11.25V8.5C0 8.08579 0.335786 7.75 0.75 7.75C1.16421 7.75 1.5 8.08579 1.5 8.5V9.43934L4.21967 6.71967C4.51256 6.42678 4.98744 6.42678 5.28033 6.71967Z"
|
||||
/>
|
||||
</symbol>
|
||||
<symbol id="scaleOut" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M20 4V6H24.586L18.293 12.2929C17.9024 12.6834 17.9024 13.3166 18.293 13.7071C18.6835 14.0976 19.3167 14.0976 19.7072 13.7071L26 7.414V12H28V4H20Z"
|
||||
/>
|
||||
<path
|
||||
d="M13.7073 19.7071C14.0978 19.3166 14.0978 18.6834 13.7073 18.2929C13.3167 17.9024 12.6836 17.9024 12.293 18.2929L6 24.586V20H4V28H12V26H7.414L13.7073 19.7071Z"
|
||||
/>
|
||||
<path
|
||||
d="M12 4V6H7.414L13.707 12.2929C14.0976 12.6834 14.0976 13.3166 13.707 13.7071C13.3165 14.0976 12.6833 14.0976 12.2928 13.7071L6 7.414V12H4V4H12Z"
|
||||
/>
|
||||
<path
|
||||
d="M18.2927 19.7071C17.9022 19.3166 17.9022 18.6834 18.2927 18.2929C18.6833 17.9024 19.3164 17.9024 19.707 18.2929L26 24.586V20H28V28H20V26H24.586L18.2927 19.7071Z"
|
||||
/>
|
||||
</symbol>
|
||||
</svg>
|
After Width: | Height: | Size: 19 KiB |
@ -33,7 +33,8 @@
|
||||
"@types/jest": "^29.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.11"
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/text-editor": "^0.6.0"
|
||||
},
|
||||
"repository": "https://github.com/hcenginneing/anticrm",
|
||||
"publishConfig": {
|
||||
|
@ -13,3 +13,29 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { loadMetadata } from '@hcengineering/platform'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
|
||||
const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(textEditor.icon, {
|
||||
Header1: `${icons}#header1`,
|
||||
Header2: `${icons}#header2`,
|
||||
Header3: `${icons}#header3`,
|
||||
Underline: `${icons}#underline`,
|
||||
Strikethrough: `${icons}#strikethrough`,
|
||||
Bold: `${icons}#bold`,
|
||||
Italic: `${icons}#italic`,
|
||||
Link: `${icons}#link`,
|
||||
ListNumber: `${icons}#listNumber`,
|
||||
ListBullet: `${icons}#listBullet`,
|
||||
Quote: `${icons}#quote`,
|
||||
Code: `${icons}#code`,
|
||||
CodeBlock: `${icons}#codeBlock`,
|
||||
TableProps: `${icons}#tableProps`,
|
||||
AlignLeft: `${icons}#alignLeft`,
|
||||
AlignCenter: `${icons}#alignCenter`,
|
||||
AlignRight: `${icons}#alignRight`,
|
||||
MoreH: `${icons}#moreH`,
|
||||
Expand: `${icons}#expand`,
|
||||
ScaleOut: `${icons}#scaleOut`
|
||||
})
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { KeyedAttribute, getAttribute, getClient } from '@hcengineering/presentation'
|
||||
import { AnySvelteComponent, registerFocus } from '@hcengineering/ui'
|
||||
import textEditor, { CollaborationUser, RefAction, TextNodeAction } from '@hcengineering/text-editor'
|
||||
import textEditor, { CollaborationUser, RefAction } from '@hcengineering/text-editor'
|
||||
|
||||
import CollaborativeTextEditor from './CollaborativeTextEditor.svelte'
|
||||
import { FocusExtension } from './extension/focus'
|
||||
@ -26,7 +26,6 @@
|
||||
export let object: Doc
|
||||
export let key: KeyedAttribute
|
||||
export let readonly = false
|
||||
export let textNodeActions: TextNodeAction[] = []
|
||||
export let refActions: RefAction[] = []
|
||||
|
||||
export let user: CollaborationUser
|
||||
@ -111,7 +110,6 @@
|
||||
objectAttr={key.key}
|
||||
{user}
|
||||
{userComponent}
|
||||
{textNodeActions}
|
||||
{refActions}
|
||||
{extensions}
|
||||
{attachFile}
|
||||
|
@ -51,23 +51,18 @@
|
||||
CollaborationUser,
|
||||
RefAction,
|
||||
TextEditorCommandHandler,
|
||||
TextEditorHandler,
|
||||
TextFormatCategory,
|
||||
TextNodeAction
|
||||
TextEditorHandler
|
||||
} from '@hcengineering/text-editor'
|
||||
import { addTableHandler } from '../utils'
|
||||
|
||||
import CollaborationUsers from './CollaborationUsers.svelte'
|
||||
import ImageStyleToolbar from './ImageStyleToolbar.svelte'
|
||||
import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte'
|
||||
import TextEditorToolbar from './TextEditorToolbar.svelte'
|
||||
import { noSelectionRender, renderCursor } from './editor/collaboration'
|
||||
import { defaultEditorAttributes } from './editor/editorProps'
|
||||
import { EmojiExtension } from './extension/emoji'
|
||||
import { FileUploadExtension } from './extension/fileUploadExt'
|
||||
import { ImageUploadExtension } from './extension/imageUploadExt'
|
||||
import { InlineCommandsExtension } from './extension/inlineCommands'
|
||||
import { InlinePopupExtension } from './extension/inlinePopup'
|
||||
import { InlineStyleToolbarExtension } from './extension/inlineStyleToolbar'
|
||||
import { LeftMenuExtension } from './extension/leftMenu'
|
||||
import { type FileAttachFunction } from './extension/types'
|
||||
import { completionConfig, inlineCommandsConfig } from './extensions'
|
||||
@ -92,16 +87,6 @@
|
||||
export let placeholder: IntlString = textEditor.string.EditorPlaceholder
|
||||
|
||||
export let extensions: AnyExtension[] = []
|
||||
export let textFormatCategories: TextFormatCategory[] = [
|
||||
TextFormatCategory.Heading,
|
||||
TextFormatCategory.TextDecoration,
|
||||
TextFormatCategory.Link,
|
||||
TextFormatCategory.List,
|
||||
TextFormatCategory.Quote,
|
||||
TextFormatCategory.Code,
|
||||
TextFormatCategory.Table
|
||||
]
|
||||
export let textNodeActions: TextNodeAction[] = []
|
||||
export let refActions: RefAction[] = []
|
||||
|
||||
export let editorAttributes: Record<string, string> = {}
|
||||
@ -263,25 +248,8 @@
|
||||
editor.setEditable(editable, true)
|
||||
}
|
||||
|
||||
$: showTextStyleToolbar =
|
||||
((editable && textFormatCategories.length > 0) || textNodeActions.length > 0) && canShowPopups
|
||||
|
||||
$: tippyOptions = {
|
||||
zIndex: 100000,
|
||||
popperOptions: {
|
||||
modifiers: [
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary,
|
||||
padding: 8,
|
||||
altAxis: true,
|
||||
tether: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
// TODO: should be inside the editor
|
||||
$: showToolbar = canShowPopups
|
||||
|
||||
const optionalExtensions: AnyExtension[] = []
|
||||
|
||||
@ -438,29 +406,22 @@
|
||||
objectClass,
|
||||
objectSpace,
|
||||
history: false,
|
||||
submit: false
|
||||
submit: false,
|
||||
toolbar: {
|
||||
element: textToolbarElement,
|
||||
boundary,
|
||||
isHidden: () => !showToolbar
|
||||
},
|
||||
image: {
|
||||
toolbar: {
|
||||
element: imageToolbarElement,
|
||||
boundary,
|
||||
isHidden: () => !showToolbar
|
||||
}
|
||||
}
|
||||
}),
|
||||
...optionalExtensions,
|
||||
Placeholder.configure({ placeholder: placeHolderStr }),
|
||||
InlineStyleToolbarExtension.configure({
|
||||
tippyOptions,
|
||||
element: textToolbarElement,
|
||||
isSupported: () => showTextStyleToolbar
|
||||
}),
|
||||
InlinePopupExtension.configure({
|
||||
pluginKey: 'show-image-actions-popup',
|
||||
element: imageToolbarElement,
|
||||
tippyOptions: {
|
||||
...tippyOptions,
|
||||
appendTo: () => boundary ?? element
|
||||
},
|
||||
shouldShow: ({ editor }) => {
|
||||
if (!editable || !canShowPopups) {
|
||||
return false
|
||||
}
|
||||
return editor.isActive('image')
|
||||
}
|
||||
}),
|
||||
Collaboration.configure({
|
||||
document: ydoc,
|
||||
field
|
||||
@ -544,29 +505,22 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="text-editor-toolbar buttons-group xsmall-gap mb-4"
|
||||
bind:this={textToolbarElement}
|
||||
style="visibility: hidden;"
|
||||
>
|
||||
{#if showTextStyleToolbar}
|
||||
<TextEditorStyleToolbar
|
||||
{editor}
|
||||
formatButtonSize={buttonSize}
|
||||
{textFormatCategories}
|
||||
{textNodeActions}
|
||||
on:focus={handleFocus}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<TextEditorToolbar
|
||||
bind:toolbar={textToolbarElement}
|
||||
visible={showToolbar}
|
||||
{editor}
|
||||
formatButtonSize={buttonSize}
|
||||
on:focus={handleFocus}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="text-editor-toolbar buttons-group xsmall-gap mb-4"
|
||||
bind:this={imageToolbarElement}
|
||||
style="visibility: hidden;"
|
||||
>
|
||||
<ImageStyleToolbar {editor} formatButtonSize={buttonSize} on:focus={handleFocus} />
|
||||
</div>
|
||||
<TextEditorToolbar
|
||||
bind:toolbar={imageToolbarElement}
|
||||
kind="image"
|
||||
visible={showToolbar}
|
||||
{editor}
|
||||
formatButtonSize={buttonSize}
|
||||
on:focus={handleFocus}
|
||||
/>
|
||||
|
||||
<div class="textInput">
|
||||
<div class="select-text" class:hidden={loading} style="width: 100%;" bind:this={element} />
|
||||
@ -609,15 +563,6 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.text-editor-toolbar {
|
||||
margin: -0.5rem -0.25rem 0.5rem;
|
||||
padding: 0.375rem;
|
||||
background-color: var(--theme-comp-header-color);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--button-shadow);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.textInput {
|
||||
flex-grow: 1;
|
||||
gap: 1rem;
|
||||
|
@ -18,18 +18,11 @@
|
||||
import { type Space, type Class, type CollaborativeDoc, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { AnySvelteComponent, IconSize, registerFocus } from '@hcengineering/ui'
|
||||
import { AnyExtension, Editor, FocusPosition, getMarkRange } from '@tiptap/core'
|
||||
import { TextSelection } from '@tiptap/pm/state'
|
||||
import textEditor, {
|
||||
CollaborationUser,
|
||||
TextEditorCommandHandler,
|
||||
TextFormatCategory,
|
||||
TextNodeAction
|
||||
} from '@hcengineering/text-editor'
|
||||
import { AnyExtension, FocusPosition } from '@tiptap/core'
|
||||
import textEditor, { CollaborationUser, TextEditorCommandHandler } from '@hcengineering/text-editor'
|
||||
|
||||
import CollaborativeTextEditor from './CollaborativeTextEditor.svelte'
|
||||
import { FileAttachFunction } from './extension/types'
|
||||
import { NodeUuidExtension, nodeElementQuerySelector } from './extension/nodeUuid'
|
||||
|
||||
export let collaborativeDoc: CollaborativeDoc
|
||||
export let initialCollaborativeDoc: CollaborativeDoc | undefined = undefined
|
||||
@ -49,7 +42,6 @@
|
||||
export let placeholder: IntlString = textEditor.string.EditorPlaceholder
|
||||
|
||||
export let overflow: 'auto' | 'none' = 'auto'
|
||||
export let textNodeActions: TextNodeAction[] = []
|
||||
export let editorAttributes: Record<string, string> = {}
|
||||
export let onExtensions: () => AnyExtension[] = () => []
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
@ -59,79 +51,12 @@
|
||||
|
||||
let element: HTMLElement
|
||||
|
||||
let editor: Editor | undefined
|
||||
let collaborativeEditor: CollaborativeTextEditor
|
||||
|
||||
export function commands (): TextEditorCommandHandler | undefined {
|
||||
return collaborativeEditor?.commands()
|
||||
}
|
||||
|
||||
// TODO Not collaborative
|
||||
export function getNodeElement (uuid: string): Element | null {
|
||||
if (editor === undefined || uuid === '') {
|
||||
return null
|
||||
}
|
||||
|
||||
return editor.view.dom.querySelector(nodeElementQuerySelector(uuid))
|
||||
}
|
||||
|
||||
// TODO Not collaborative
|
||||
export function selectNode (uuid: string): void {
|
||||
if (editor === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const { doc, schema, tr } = editor.view.state
|
||||
let foundNode = false
|
||||
doc.descendants((node, pos) => {
|
||||
if (foundNode) {
|
||||
return false
|
||||
}
|
||||
|
||||
const nodeUuidMark = node.marks.find(
|
||||
(mark) => mark.type.name === NodeUuidExtension.name && mark.attrs[NodeUuidExtension.name] === uuid
|
||||
)
|
||||
|
||||
if (nodeUuidMark === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
foundNode = true
|
||||
|
||||
// the first pos does not contain the mark, so we need to add 1 (pos + 1) to get the correct range
|
||||
const range = getMarkRange(doc.resolve(pos + 1), schema.marks[NodeUuidExtension.name])
|
||||
|
||||
if (range === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
const [$start, $end] = [doc.resolve(range.from), doc.resolve(range.to)]
|
||||
editor?.view.dispatch(tr.setSelection(new TextSelection($start, $end)))
|
||||
focus()
|
||||
})
|
||||
}
|
||||
|
||||
// TODO Not collaborative
|
||||
export function selectRange (from: number, to: number): void {
|
||||
if (editor === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const { doc, tr } = editor.view.state
|
||||
const [$start, $end] = [doc.resolve(from), doc.resolve(to)]
|
||||
editor.view.dispatch(tr.setSelection(new TextSelection($start, $end)))
|
||||
focus()
|
||||
}
|
||||
|
||||
// TODO Not collaborative
|
||||
export function setNodeUuid (nodeId: string): boolean {
|
||||
if (editor === undefined || editor.view.state.selection.empty || nodeId === '') {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.chain().setNodeUuid(nodeId).run()
|
||||
}
|
||||
|
||||
export function focus (position?: FocusPosition): void {
|
||||
collaborativeEditor?.focus(position)
|
||||
}
|
||||
@ -178,28 +103,14 @@
|
||||
{overflow}
|
||||
{boundary}
|
||||
{attachFile}
|
||||
textFormatCategories={readonly
|
||||
? []
|
||||
: [
|
||||
TextFormatCategory.Heading,
|
||||
TextFormatCategory.TextDecoration,
|
||||
TextFormatCategory.Link,
|
||||
TextFormatCategory.List,
|
||||
TextFormatCategory.Quote,
|
||||
TextFormatCategory.Code,
|
||||
TextFormatCategory.Table
|
||||
]}
|
||||
extensions={[...onExtensions()]}
|
||||
{textNodeActions}
|
||||
{canShowPopups}
|
||||
{editorAttributes}
|
||||
on:editor
|
||||
on:update
|
||||
on:open-document
|
||||
on:blur
|
||||
on:focus={handleFocus}
|
||||
on:editor={(evt) => {
|
||||
editor = evt.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -1,134 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2023, 2024 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 { createEventDispatcher } from 'svelte'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { FilePreviewPopup, getFileUrl } from '@hcengineering/presentation'
|
||||
import { IconExpand, IconMoreH, IconSize, SelectPopup, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||
import { Editor } from '@tiptap/core'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
import IconAlignCenter from './icons/AlignCenter.svelte'
|
||||
import IconAlignLeft from './icons/AlignLeft.svelte'
|
||||
import IconAlignRight from './icons/AlignRight.svelte'
|
||||
import IconScaleOut from './icons/ScaleOut.svelte'
|
||||
import StyleButton from './StyleButton.svelte'
|
||||
|
||||
export let formatButtonSize: IconSize = 'small'
|
||||
export let editor: Editor
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getImageAlignmentToggler (align: 'center' | 'left' | 'right') {
|
||||
return () => {
|
||||
editor.commands.setImageAlignment({ align })
|
||||
dispatch('focus')
|
||||
}
|
||||
}
|
||||
|
||||
function openImage (): void {
|
||||
const attributes = editor.getAttributes('image')
|
||||
const fileId = attributes['file-id'] ?? attributes.src
|
||||
const fileName = attributes.alt ?? ''
|
||||
showPopup(FilePreviewPopup, { file: fileId, name: fileName, fullSize: true, showIcon: false }, 'centered', () => {
|
||||
dispatch('focus')
|
||||
})
|
||||
}
|
||||
|
||||
function openOriginalImage (): void {
|
||||
const attributes = editor.getAttributes('image')
|
||||
const fileId = attributes['file-id'] ?? attributes.src
|
||||
const url = getFileUrl(fileId)
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function moreOptions (event: MouseEvent): void {
|
||||
const widthActions = ['25%', '50%', '75%', '100%', textEditor.string.Unset].map((it) => {
|
||||
return {
|
||||
id: `#imageWidth${it}`,
|
||||
label: it === textEditor.string.Unset ? it : getEmbeddedLabel(it),
|
||||
action: () =>
|
||||
editor.commands.setImageSize({ width: it === textEditor.string.Unset ? undefined : it, height: undefined }),
|
||||
category: {
|
||||
label: textEditor.string.Width
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const actions = [...widthActions]
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: actions
|
||||
},
|
||||
getEventPositionElement(event),
|
||||
(val) => {
|
||||
if (val !== undefined) {
|
||||
const op = actions.find((it) => it.id === val)
|
||||
if (op !== undefined) {
|
||||
op.action()
|
||||
dispatch('focus')
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
{#if editor.isActive('image')}
|
||||
<StyleButton
|
||||
icon={IconAlignLeft}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('image', { align: 'left' })}
|
||||
showTooltip={{ label: textEditor.string.AlignLeft }}
|
||||
on:click={getImageAlignmentToggler('left')}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={IconAlignCenter}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('image', { align: 'center' })}
|
||||
showTooltip={{ label: textEditor.string.AlignCenter }}
|
||||
on:click={getImageAlignmentToggler('center')}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={IconAlignRight}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('image', { align: 'right' })}
|
||||
showTooltip={{ label: textEditor.string.AlignRight }}
|
||||
on:click={getImageAlignmentToggler('right')}
|
||||
/>
|
||||
<div class="buttons-divider" />
|
||||
<StyleButton
|
||||
icon={IconScaleOut}
|
||||
size={formatButtonSize}
|
||||
on:click={openImage}
|
||||
showTooltip={{ label: textEditor.string.ViewImage }}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={IconExpand}
|
||||
size={formatButtonSize}
|
||||
on:click={openOriginalImage}
|
||||
showTooltip={{ label: textEditor.string.ViewOriginal }}
|
||||
/>
|
||||
<div class="buttons-divider" />
|
||||
<StyleButton
|
||||
icon={IconMoreH}
|
||||
size={formatButtonSize}
|
||||
on:click={moreOptions}
|
||||
showTooltip={{ label: textEditor.string.MoreActions }}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
@ -180,13 +180,6 @@
|
||||
{onPaste}
|
||||
on:update
|
||||
placeholder={placeholder ?? textEditor.string.EditorPlaceholder}
|
||||
textFormatCategories={[
|
||||
TextFormatCategory.TextDecoration,
|
||||
TextFormatCategory.Link,
|
||||
TextFormatCategory.List,
|
||||
TextFormatCategory.Quote,
|
||||
TextFormatCategory.Code
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{#if showActions || showSend}
|
||||
|
@ -39,15 +39,6 @@
|
||||
export let editorAttributes: Record<string, string> = {}
|
||||
export let extraActions: RefAction[] = []
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
export let textFormatCategories: TextFormatCategory[] = [
|
||||
TextFormatCategory.Heading,
|
||||
TextFormatCategory.TextDecoration,
|
||||
TextFormatCategory.Link,
|
||||
TextFormatCategory.List,
|
||||
TextFormatCategory.Quote,
|
||||
TextFormatCategory.Code,
|
||||
TextFormatCategory.Table
|
||||
]
|
||||
|
||||
let editor: TextEditor | undefined = undefined
|
||||
|
||||
@ -168,7 +159,6 @@
|
||||
bind:content
|
||||
{placeholder}
|
||||
{extensions}
|
||||
{textFormatCategories}
|
||||
bind:this={editor}
|
||||
on:value
|
||||
on:content={(ev) => {
|
||||
@ -187,7 +177,6 @@
|
||||
bind:content
|
||||
{placeholder}
|
||||
{extensions}
|
||||
{textFormatCategories}
|
||||
bind:this={editor}
|
||||
on:value
|
||||
on:content={(ev) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
// Copyright © 2022, 2023, 2024 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
|
||||
@ -13,27 +13,62 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Asset } from '@hcengineering/platform'
|
||||
import { AnySvelteComponent, Icon, IconSize, LabelAndProps, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import { type TextEditorAction, type ActionContext } from '@hcengineering/text-editor'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { Icon, IconSize, tooltip } from '@hcengineering/ui'
|
||||
|
||||
export let icon: Asset | AnySvelteComponent
|
||||
export let iconProps: any = undefined
|
||||
export let action: TextEditorAction
|
||||
export let size: IconSize
|
||||
export let selected: boolean = false
|
||||
export let showTooltip: LabelAndProps | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let editor: Editor
|
||||
export let actionCtx: ActionContext
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let selected: boolean = false
|
||||
$: void updateSelected(editor, action)
|
||||
|
||||
async function updateSelected (e: Editor, { isActive }: TextEditorAction): Promise<void> {
|
||||
if (isActive === undefined) {
|
||||
selected = false
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof isActive === 'string') {
|
||||
const isActiveFunc = await getResource(isActive)
|
||||
selected = await isActiveFunc(e)
|
||||
} else {
|
||||
const { name, params } = isActive
|
||||
selected = editor.isActive(name, params)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClick (event: MouseEvent): Promise<void> {
|
||||
const handler = action.action
|
||||
|
||||
if (typeof handler === 'string') {
|
||||
const actionFunc = await getResource(handler)
|
||||
await actionFunc(editor, event, actionCtx)
|
||||
} else {
|
||||
const { command, params } = handler
|
||||
|
||||
const cmd = (editor.commands as any)[command]
|
||||
if (cmd) {
|
||||
cmd(params)
|
||||
}
|
||||
}
|
||||
dispatch('focus')
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="button {size}"
|
||||
class:selected
|
||||
class:disabled
|
||||
{disabled}
|
||||
use:tooltip={showTooltip}
|
||||
use:tooltip={{ label: action.label }}
|
||||
tabindex="0"
|
||||
on:click|preventDefault|stopPropagation
|
||||
on:click|preventDefault|stopPropagation={handleClick}
|
||||
>
|
||||
<Icon {icon} {size} {iconProps} />
|
||||
<Icon icon={action.icon} {size} />
|
||||
</button>
|
||||
|
||||
<style lang="scss">
|
@ -18,7 +18,7 @@
|
||||
import { IntlString, translate } from '@hcengineering/platform'
|
||||
import { EmptyMarkup, getMarkup, markupToJSON } from '@hcengineering/text'
|
||||
import { themeStore } from '@hcengineering/ui'
|
||||
import textEditor, { TextFormatCategory } from '@hcengineering/text-editor'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
import { AnyExtension, Content, Editor, FocusPosition, mergeAttributes } from '@tiptap/core'
|
||||
import Placeholder from '@tiptap/extension-placeholder'
|
||||
import { ParseOptions } from '@tiptap/pm/model'
|
||||
@ -26,17 +26,13 @@
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { deleteAttachment } from '../command/deleteAttachment'
|
||||
import ImageStyleToolbar from './ImageStyleToolbar.svelte'
|
||||
import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte'
|
||||
import TextEditorToolbar from './TextEditorToolbar.svelte'
|
||||
import { defaultEditorAttributes } from './editor/editorProps'
|
||||
import { InlinePopupExtension } from './extension/inlinePopup'
|
||||
import { InlineStyleToolbarExtension } from './extension/inlineStyleToolbar'
|
||||
import { getEditorKit } from '../../src/kits/editor-kit'
|
||||
|
||||
export let content: Markup = EmptyMarkup
|
||||
export let placeholder: IntlString = textEditor.string.EditorPlaceholder
|
||||
export let extensions: AnyExtension[] = []
|
||||
export let textFormatCategories: TextFormatCategory[] = []
|
||||
export let supportSubmit = true
|
||||
export let editorAttributes: Record<string, string> = {}
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
@ -136,23 +132,6 @@
|
||||
let textToolbarElement: HTMLElement
|
||||
let imageToolbarElement: HTMLElement
|
||||
|
||||
$: tippyOptions = {
|
||||
zIndex: 100000,
|
||||
popperOptions: {
|
||||
modifiers: [
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary,
|
||||
padding: 8,
|
||||
altAxis: true,
|
||||
tether: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function focus (position?: FocusPosition): void {
|
||||
posFocus = position
|
||||
needFocus = true
|
||||
@ -180,33 +159,22 @@
|
||||
(await getEditorKit()).configure({
|
||||
mode: 'compact',
|
||||
file: canEmbedFiles ? {} : false,
|
||||
image: canEmbedImages ? {} : false,
|
||||
submit: supportSubmit ? { submit } : false
|
||||
image: canEmbedImages
|
||||
? {
|
||||
toolbar: {
|
||||
element: imageToolbarElement,
|
||||
boundary
|
||||
}
|
||||
}
|
||||
: false,
|
||||
submit: supportSubmit ? { submit } : false,
|
||||
toolbar: {
|
||||
element: textToolbarElement,
|
||||
boundary
|
||||
}
|
||||
}),
|
||||
Placeholder.configure({ placeholder: placeHolderStr }),
|
||||
...extensions,
|
||||
InlineStyleToolbarExtension.configure({
|
||||
tippyOptions,
|
||||
// TODO: Toolbar element is updated on every component update,
|
||||
// but extensions is created only on mount. This causes issues when
|
||||
// you're trying to use TextEditor in long-living components that
|
||||
// get updated, e.g. in QuestionCollectionItemEditor in Surveys
|
||||
element: textToolbarElement,
|
||||
isSupported: () => true
|
||||
}),
|
||||
InlinePopupExtension.configure({
|
||||
pluginKey: 'show-image-actions-popup',
|
||||
// TODO: Toolbar element is updated on every component update,
|
||||
// but extensions is created only on mount. This causes issues when
|
||||
// you're trying to use TextEditor in long-living components that
|
||||
// get updated, e.g. in QuestionCollectionItemEditor in Surveys
|
||||
element: imageToolbarElement,
|
||||
tippyOptions: {
|
||||
...tippyOptions,
|
||||
appendTo: () => boundary ?? element
|
||||
},
|
||||
shouldShow: ({ editor }) => editor.isEditable && editor.isActive('image')
|
||||
})
|
||||
...extensions
|
||||
],
|
||||
parseOptions: {
|
||||
preserveWhitespace: 'full'
|
||||
@ -248,23 +216,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={textToolbarElement} class="text-editor-toolbar buttons-group xsmall-gap mb-4">
|
||||
<TextEditorStyleToolbar {editor} {textFormatCategories} on:focus={handleFocus} />
|
||||
</div>
|
||||
<TextEditorToolbar bind:toolbar={textToolbarElement} {editor} on:focus={handleFocus} />
|
||||
|
||||
<div bind:this={imageToolbarElement} class="text-editor-toolbar buttons-group xsmall-gap mb-4">
|
||||
<ImageStyleToolbar {editor} on:focus={handleFocus} />
|
||||
</div>
|
||||
{#if canEmbedImages}
|
||||
<TextEditorToolbar bind:toolbar={imageToolbarElement} kind="image" {editor} on:focus={handleFocus} />
|
||||
{/if}
|
||||
|
||||
<div class="select-text" style="width: 100%;" bind:this={element} />
|
||||
|
||||
<style lang="scss">
|
||||
.text-editor-toolbar {
|
||||
margin: -0.5rem -0.25rem 0.5rem;
|
||||
padding: 0.375rem;
|
||||
background-color: var(--theme-comp-header-color);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--theme-popup-shadow);
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,305 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { createEventDispatcher } from 'svelte'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { getEventPositionElement, IconSize, SelectPopup, showPopup } from '@hcengineering/ui'
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { Level } from '@tiptap/extension-heading'
|
||||
import textEditor, { TextFormatCategory, TextNodeAction } from '@hcengineering/text-editor'
|
||||
|
||||
import Bold from './icons/Bold.svelte'
|
||||
import Code from './icons/Code.svelte'
|
||||
import CodeBlock from './icons/CodeBlock.svelte'
|
||||
import Header1 from './icons/Header1.svelte'
|
||||
import Header2 from './icons/Header2.svelte'
|
||||
import Header3 from './icons/Header3.svelte'
|
||||
import IconTable from './icons/IconTable.svelte'
|
||||
import Italic from './icons/Italic.svelte'
|
||||
import Link from './icons/Link.svelte'
|
||||
import ListBullet from './icons/ListBullet.svelte'
|
||||
import ListNumber from './icons/ListNumber.svelte'
|
||||
import Quote from './icons/Quote.svelte'
|
||||
import RIStrikethrough from './icons/RIStrikethrough.svelte'
|
||||
import Underline from './icons/Underline.svelte'
|
||||
import AddColAfter from './icons/table/AddColAfter.svelte'
|
||||
import AddColBefore from './icons/table/AddColBefore.svelte'
|
||||
import AddRowAfter from './icons/table/AddRowAfter.svelte'
|
||||
import AddRowBefore from './icons/table/AddRowBefore.svelte'
|
||||
import DeleteCol from './icons/table/DeleteCol.svelte'
|
||||
import DeleteRow from './icons/table/DeleteRow.svelte'
|
||||
import DeleteTable from './icons/table/DeleteTable.svelte'
|
||||
import LinkPopup from './LinkPopup.svelte'
|
||||
import StyleButton from './StyleButton.svelte'
|
||||
|
||||
export let formatButtonSize: IconSize = 'small'
|
||||
export let editor: Editor
|
||||
export let textFormatCategories: TextFormatCategory[] = []
|
||||
export let textNodeActions: TextNodeAction[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getToggler (toggle: () => void) {
|
||||
return () => {
|
||||
toggle()
|
||||
dispatch('focus')
|
||||
}
|
||||
}
|
||||
|
||||
async function formatLink (): Promise<void> {
|
||||
const link = editor.getAttributes('link').href
|
||||
|
||||
showPopup(LinkPopup, { link }, undefined, undefined, (newLink) => {
|
||||
if (newLink === '') {
|
||||
editor.chain().focus().extendMarkRange('link').unsetLink().run()
|
||||
} else {
|
||||
editor.chain().focus().extendMarkRange('link').setLink({ href: newLink }).run()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getHeaderToggler (level: Level) {
|
||||
return () => {
|
||||
editor.commands.toggleHeading({ level })
|
||||
dispatch('focus')
|
||||
}
|
||||
}
|
||||
|
||||
function tableOptions (event: MouseEvent): void {
|
||||
const ops = [
|
||||
{
|
||||
id: '#addColumnBefore',
|
||||
icon: AddColBefore,
|
||||
label: textEditor.string.AddColumnBefore,
|
||||
action: () => editor.commands.addColumnBefore(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryColumn
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#addColumnAfter',
|
||||
icon: AddColAfter,
|
||||
label: textEditor.string.AddColumnAfter,
|
||||
action: () => editor.commands.addColumnAfter(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryColumn
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: '#deleteColumn',
|
||||
icon: DeleteCol,
|
||||
label: textEditor.string.DeleteColumn,
|
||||
action: () => editor.commands.deleteColumn(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryColumn
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#addRowBefore',
|
||||
icon: AddRowBefore,
|
||||
label: textEditor.string.AddRowBefore,
|
||||
action: () => editor.commands.addRowBefore(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryRow
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#addRowAfter',
|
||||
icon: AddRowAfter,
|
||||
label: textEditor.string.AddRowAfter,
|
||||
action: () => editor.commands.addRowAfter(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryRow
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#deleteRow',
|
||||
icon: DeleteRow,
|
||||
label: textEditor.string.DeleteRow,
|
||||
action: () => editor.commands.deleteRow(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryRow
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#deleteTable',
|
||||
icon: DeleteTable,
|
||||
label: textEditor.string.DeleteTable,
|
||||
action: () => editor.commands.deleteTable(),
|
||||
category: {
|
||||
label: textEditor.string.Table
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: ops
|
||||
},
|
||||
getEventPositionElement(event),
|
||||
(val) => {
|
||||
if (val !== undefined) {
|
||||
const op = ops.find((it) => it.id === val)
|
||||
if (op !== undefined) {
|
||||
op.action()
|
||||
dispatch('focus')
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
{#each textFormatCategories as category, index}
|
||||
{#if index > 0 && (category !== TextFormatCategory.Table || editor.isActive('table'))}
|
||||
<div class="buttons-divider" />
|
||||
{/if}
|
||||
{#if category === TextFormatCategory.Heading}
|
||||
<StyleButton
|
||||
icon={Header1}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('heading', { level: 1 })}
|
||||
showTooltip={{ label: getEmbeddedLabel('H1') }}
|
||||
on:click={getHeaderToggler(1)}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={Header2}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('heading', { level: 2 })}
|
||||
showTooltip={{ label: getEmbeddedLabel('H2') }}
|
||||
on:click={getHeaderToggler(2)}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={Header3}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('heading', { level: 3 })}
|
||||
showTooltip={{ label: getEmbeddedLabel('H3') }}
|
||||
on:click={getHeaderToggler(3)}
|
||||
/>
|
||||
{/if}
|
||||
{#if category === TextFormatCategory.TextDecoration}
|
||||
<StyleButton
|
||||
icon={Bold}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('bold')}
|
||||
showTooltip={{ label: textEditor.string.Bold }}
|
||||
on:click={getToggler(editor.commands.toggleBold)}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={Italic}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('italic')}
|
||||
showTooltip={{ label: textEditor.string.Italic }}
|
||||
on:click={getToggler(editor.commands.toggleItalic)}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={RIStrikethrough}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('strike')}
|
||||
showTooltip={{ label: textEditor.string.Strikethrough }}
|
||||
on:click={getToggler(editor.commands.toggleStrike)}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={Underline}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('underline')}
|
||||
showTooltip={{ label: textEditor.string.Underlined }}
|
||||
on:click={getToggler(editor.commands.toggleUnderline)}
|
||||
/>
|
||||
{/if}
|
||||
{#if category === TextFormatCategory.Link}
|
||||
<StyleButton
|
||||
icon={Link}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('link')}
|
||||
disabled={editor.view.state.selection.empty && !editor.isActive('link')}
|
||||
showTooltip={{ label: textEditor.string.Link }}
|
||||
on:click={formatLink}
|
||||
/>
|
||||
{/if}
|
||||
{#if category === TextFormatCategory.List}
|
||||
<StyleButton
|
||||
icon={ListNumber}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('orderedList')}
|
||||
showTooltip={{ label: textEditor.string.OrderedList }}
|
||||
on:click={getToggler(editor.commands.toggleOrderedList)}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={ListBullet}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('bulletList')}
|
||||
showTooltip={{ label: textEditor.string.BulletedList }}
|
||||
on:click={getToggler(editor.commands.toggleBulletList)}
|
||||
/>
|
||||
{/if}
|
||||
{#if category === TextFormatCategory.Quote}
|
||||
<StyleButton
|
||||
icon={Quote}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('blockquote')}
|
||||
showTooltip={{ label: textEditor.string.Blockquote }}
|
||||
on:click={getToggler(editor.commands.toggleBlockquote)}
|
||||
/>
|
||||
{/if}
|
||||
{#if category === TextFormatCategory.Code}
|
||||
<StyleButton
|
||||
icon={Code}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('code')}
|
||||
showTooltip={{ label: textEditor.string.Code }}
|
||||
on:click={getToggler(editor.commands.toggleCode)}
|
||||
/>
|
||||
<StyleButton
|
||||
icon={CodeBlock}
|
||||
size={formatButtonSize}
|
||||
selected={editor.isActive('codeBlock')}
|
||||
showTooltip={{ label: textEditor.string.CodeBlock }}
|
||||
on:click={getToggler(editor.commands.toggleCodeBlock)}
|
||||
/>
|
||||
{/if}
|
||||
{#if category === TextFormatCategory.Table}
|
||||
{#if editor.isActive('table')}
|
||||
<StyleButton
|
||||
icon={IconTable}
|
||||
iconProps={{ style: 'tableProps' }}
|
||||
size={formatButtonSize}
|
||||
on:click={tableOptions}
|
||||
showTooltip={{ label: textEditor.string.TableOptions }}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{#if textFormatCategories.length > 0 && textNodeActions.length > 0}
|
||||
<div class="buttons-divider" />
|
||||
{/if}
|
||||
{#if textNodeActions.length > 0}
|
||||
{#each textNodeActions as action}
|
||||
<StyleButton
|
||||
icon={action.icon}
|
||||
size={formatButtonSize}
|
||||
selected={false}
|
||||
disabled={editor.view.state.selection.empty}
|
||||
showTooltip={{ label: action.label }}
|
||||
on:click={() => {
|
||||
void action.action({ editor })
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
@ -0,0 +1,123 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022, 2023, 2024 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 { IconSize } from '@hcengineering/ui'
|
||||
import { Editor } from '@tiptap/core'
|
||||
import textEditor, {
|
||||
type TextEditorAction,
|
||||
type ActionContext,
|
||||
type TextEditorActionKind
|
||||
} from '@hcengineering/text-editor'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
import { inlineToolbarKey } from './extension/inlineToolbar'
|
||||
import TextActionButton from './TextActionButton.svelte'
|
||||
|
||||
export let formatButtonSize: IconSize = 'small'
|
||||
export let editor: Editor
|
||||
export let toolbar: HTMLElement | null
|
||||
export let visible: boolean = true
|
||||
export let kind: TextEditorActionKind = 'text'
|
||||
|
||||
const actionsQuery = createQuery()
|
||||
|
||||
$: actionCtx = editor?.extensionManager.extensions.find((ext) => ext.name === inlineToolbarKey)?.options.ctx
|
||||
|
||||
let actions: TextEditorAction[]
|
||||
$: actionsQuery.query(textEditor.class.TextEditorAction, {}, (result) => {
|
||||
actions = result.filter((action) => action.kind === kind || (kind === 'text' && action.kind === undefined))
|
||||
})
|
||||
|
||||
let visibleActions: TextEditorAction[]
|
||||
$: void getVisibleActions(editor, actions, actionCtx)
|
||||
|
||||
async function getVisibleActions (
|
||||
e: Editor | undefined,
|
||||
actions: TextEditorAction[],
|
||||
ctx: ActionContext
|
||||
): Promise<void> {
|
||||
const newVisibleActions = []
|
||||
if (e !== undefined && actions !== undefined) {
|
||||
for (const action of actions) {
|
||||
const tester = action.visibilityTester
|
||||
|
||||
if (typeof action.action !== 'string') {
|
||||
const { command } = action.action
|
||||
|
||||
if ((editor.commands as any)[command] === undefined) {
|
||||
console.error(`Command ${command} not found`)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (tester === undefined) {
|
||||
newVisibleActions.push(action)
|
||||
continue
|
||||
}
|
||||
|
||||
const testerFunc = await getResource(tester)
|
||||
if (await testerFunc(e, ctx)) {
|
||||
newVisibleActions.push(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visibleActions = newVisibleActions
|
||||
}
|
||||
|
||||
$: categories = visibleActions.reduce<[number, TextEditorAction][][]>((acc, action) => {
|
||||
const { category, index } = action
|
||||
|
||||
if (acc[category] === undefined) {
|
||||
acc[category] = []
|
||||
}
|
||||
|
||||
acc[category].push([index, action])
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
$: categories.forEach((category) => {
|
||||
category.sort((a, b) => a[0] - b[0])
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={toolbar} style="visibility: hidden;">
|
||||
{#if editor && visible && visibleActions.length > 0}
|
||||
<div class="text-editor-toolbar buttons-group xsmall-gap mb-4">
|
||||
{#each Object.values(categories) as category, index}
|
||||
{#if index > 0}
|
||||
<div class="buttons-divider" />
|
||||
{/if}
|
||||
|
||||
{#each category as [_, action]}
|
||||
<TextActionButton {action} {editor} size={formatButtonSize} {actionCtx} on:focus />
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-editor-toolbar {
|
||||
margin: -0.5rem -0.25rem 0.5rem;
|
||||
padding: 0.375rem;
|
||||
background-color: var(--theme-comp-header-color);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--button-shadow);
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
@ -12,11 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { FilePreviewPopup } from '@hcengineering/presentation'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { FilePreviewPopup, getFileUrl } from '@hcengineering/presentation'
|
||||
import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import { nodeInputRule } from '@tiptap/core'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
import { getEventPositionElement, SelectPopup, showPopup } from '@hcengineering/ui'
|
||||
import { type Editor, nodeInputRule } from '@tiptap/core'
|
||||
import { type BubbleMenuOptions } from '@tiptap/extension-bubble-menu'
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import { InlinePopupExtension } from './inlinePopup'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -26,7 +30,11 @@ export type ImageAlignment = 'center' | 'left' | 'right'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ImageOptions extends ImageNodeOptions {}
|
||||
export type ImageOptions = ImageNodeOptions & {
|
||||
toolbar?: Omit<BubbleMenuOptions, 'pluginKey'> & {
|
||||
isHidden?: () => boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ImageAlignmentOptions {
|
||||
align?: ImageAlignment
|
||||
@ -158,5 +166,94 @@ export const ImageExtension = ImageNode.extend<ImageOptions>({
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
|
||||
addExtensions () {
|
||||
return [
|
||||
InlinePopupExtension.configure({
|
||||
...this.options.toolbar,
|
||||
shouldShow: ({ editor, view, state, oldState, from, to }) => {
|
||||
if (this.options.toolbar?.isHidden?.() === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (editor.isDestroyed) {
|
||||
return false
|
||||
}
|
||||
|
||||
// For some reason shouldShow might be called after dismount and
|
||||
// after destroying the editor. We should handle this just no to have
|
||||
// any errors in runtime
|
||||
const editorElement = editor.view.dom
|
||||
if (editorElement === null || editorElement === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
// When clicking on a element inside the bubble menu the editor "blur" event
|
||||
// is called and the bubble menu item is focussed. In this case we should
|
||||
// consider the menu as part of the editor and keep showing the menu
|
||||
const isChildOfMenu = editorElement.contains(document.activeElement)
|
||||
const hasEditorFocus = view.hasFocus() || isChildOfMenu
|
||||
if (!hasEditorFocus) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.isActive('image')
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
export async function openImage (editor: Editor): Promise<void> {
|
||||
const attributes = editor.getAttributes('image')
|
||||
const fileId = attributes['file-id'] ?? attributes.src
|
||||
const fileName = attributes.alt ?? ''
|
||||
await new Promise<void>((resolve) => {
|
||||
showPopup(FilePreviewPopup, { file: fileId, name: fileName, fullSize: true, showIcon: false }, 'centered', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function expandImage (editor: Editor): Promise<void> {
|
||||
const attributes = editor.getAttributes('image')
|
||||
const fileId = attributes['file-id'] ?? attributes.src
|
||||
const url = getFileUrl(fileId)
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
export async function moreImageActions (editor: Editor, event: MouseEvent): Promise<void> {
|
||||
const widthActions = ['25%', '50%', '75%', '100%', textEditor.string.Unset].map((it) => {
|
||||
return {
|
||||
id: `#imageWidth${it}`,
|
||||
label: it === textEditor.string.Unset ? it : getEmbeddedLabel(it),
|
||||
action: () =>
|
||||
editor.commands.setImageSize({ width: it === textEditor.string.Unset ? undefined : it, height: undefined }),
|
||||
category: {
|
||||
label: textEditor.string.Width
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const actions = [...widthActions]
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: actions
|
||||
},
|
||||
getEventPositionElement(event),
|
||||
(val) => {
|
||||
if (val !== undefined) {
|
||||
const op = actions.find((it) => it.id === val)
|
||||
if (op !== undefined) {
|
||||
op.action()
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -1,14 +1,19 @@
|
||||
import { Extension, isTextSelection } from '@tiptap/core'
|
||||
import { type BubbleMenuOptions } from '@tiptap/extension-bubble-menu'
|
||||
import { PluginKey } from '@tiptap/pm/state'
|
||||
import { type ActionContext } from '@hcengineering/text-editor'
|
||||
|
||||
import { InlinePopupExtension } from './inlinePopup'
|
||||
|
||||
export const inlineToolbarKey = 'toolbar'
|
||||
|
||||
export type InlineStyleToolbarOptions = BubbleMenuOptions & {
|
||||
isSupported: () => boolean
|
||||
canShowWithoutSelection?: boolean
|
||||
isHidden?: () => boolean
|
||||
ctx?: ActionContext
|
||||
}
|
||||
|
||||
export const InlineStyleToolbarExtension = Extension.create<InlineStyleToolbarOptions>({
|
||||
export const InlineToolbarExtension = Extension.create<InlineStyleToolbarOptions>({
|
||||
name: inlineToolbarKey,
|
||||
pluginKey: new PluginKey('inline-style-toolbar'),
|
||||
addExtensions () {
|
||||
const options: InlineStyleToolbarOptions = this.options
|
||||
@ -17,7 +22,7 @@ export const InlineStyleToolbarExtension = Extension.create<InlineStyleToolbarOp
|
||||
InlinePopupExtension.configure({
|
||||
...options,
|
||||
shouldShow: ({ editor, view, state, oldState, from, to }) => {
|
||||
if (!this.options.isSupported()) {
|
||||
if (this.options.isHidden?.() === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -26,7 +31,7 @@ export const InlineStyleToolbarExtension = Extension.create<InlineStyleToolbarOp
|
||||
}
|
||||
|
||||
// For some reason shouldShow might be called after dismount and
|
||||
// after destroing the editor. We should handle this just no to have
|
||||
// after destroying the editor. We should handle this just no to have
|
||||
// any errors in runtime
|
||||
const editorElement = editor.view.dom
|
||||
if (editorElement === null || editorElement === undefined) {
|
||||
@ -51,11 +56,8 @@ export const InlineStyleToolbarExtension = Extension.create<InlineStyleToolbarOp
|
||||
// Doubleclick an empty paragraph returns a node size of 2.
|
||||
// So we check also for an empty text size.
|
||||
const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && textSelection
|
||||
if (empty || isEmptyTextBlock) {
|
||||
return this.options.canShowWithoutSelection ?? false
|
||||
}
|
||||
|
||||
return textSelection
|
||||
return textSelection && !empty && !isEmptyTextBlock
|
||||
}
|
||||
})
|
||||
]
|
@ -52,7 +52,6 @@ const generateAttributes = (uuid: string, options: NodeHighlightExtensionOptions
|
||||
}
|
||||
|
||||
const NodeHighlight = 'node-highlight'
|
||||
|
||||
const NodeHighlightMeta = 'node-highlight'
|
||||
|
||||
export interface NodeHighlightCommands<ReturnType> {
|
||||
|
@ -1,10 +1,17 @@
|
||||
import { type Command, type CommandProps, Mark, getMarkType, getMarksBetween, mergeAttributes } from '@tiptap/core'
|
||||
import {
|
||||
type CommandProps,
|
||||
type Editor,
|
||||
Mark,
|
||||
getMarkRange,
|
||||
getMarkType,
|
||||
getMarksBetween,
|
||||
mergeAttributes
|
||||
} from '@tiptap/core'
|
||||
import { type Node, type Mark as ProseMirrorMark } from '@tiptap/pm/model'
|
||||
import { type EditorState, Plugin, PluginKey } from '@tiptap/pm/state'
|
||||
import { type EditorState, Plugin, PluginKey, TextSelection } from '@tiptap/pm/state'
|
||||
|
||||
const NAME = 'node-uuid'
|
||||
|
||||
export const nodeElementQuerySelector = (nodeUuid: string): string => `span[${NAME}='${nodeUuid}']`
|
||||
export const nodeUuidName = 'node-uuid'
|
||||
export const nodeElementQuerySelector = (nodeUuid: string): string => `span[${nodeUuidName}='${nodeUuid}']`
|
||||
|
||||
export interface NodeUuidOptions {
|
||||
HTMLAttributes: Record<string, any>
|
||||
@ -12,21 +19,19 @@ export interface NodeUuidOptions {
|
||||
onNodeClicked?: (uuid: string) => void
|
||||
}
|
||||
|
||||
export interface NodeUuidCommands<ReturnType> {
|
||||
[NAME]: {
|
||||
/**
|
||||
* Add uuid mark
|
||||
*/
|
||||
setNodeUuid: (uuid: string) => ReturnType
|
||||
/**
|
||||
* Unset uuid mark
|
||||
*/
|
||||
unsetNodeUuid: () => ReturnType
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> extends NodeUuidCommands<ReturnType> {}
|
||||
interface Commands<ReturnType> {
|
||||
[nodeUuidName]: {
|
||||
/**
|
||||
* Add uuid mark
|
||||
*/
|
||||
setNodeUuid: (uuid: string) => ReturnType
|
||||
/**
|
||||
* Unset uuid mark
|
||||
*/
|
||||
unsetNodeUuid: () => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface NodeUuidStorage {
|
||||
@ -60,7 +65,50 @@ export const findNodeUuidMark = (node: Node): ProseMirrorMark | undefined => {
|
||||
return
|
||||
}
|
||||
|
||||
return node.marks.find((mark) => mark.type.name === NAME && mark.attrs[NAME])
|
||||
return node.marks.find((mark) => mark.type.name === nodeUuidName && mark.attrs[nodeUuidName])
|
||||
}
|
||||
|
||||
export function getNodeElement (editor: Editor, uuid: string): Element | null {
|
||||
if (editor === undefined || uuid === '') {
|
||||
return null
|
||||
}
|
||||
|
||||
return editor.view.dom.querySelector(nodeElementQuerySelector(uuid))
|
||||
}
|
||||
|
||||
export function selectNode (editor: Editor, uuid: string): void {
|
||||
if (editor === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const { doc, schema, tr } = editor.view.state
|
||||
let foundNode = false
|
||||
doc.descendants((node, pos) => {
|
||||
if (foundNode) {
|
||||
return false
|
||||
}
|
||||
|
||||
const nodeUuidMark = node.marks.find(
|
||||
(mark) => mark.type.name === NodeUuidExtension.name && mark.attrs[NodeUuidExtension.name] === uuid
|
||||
)
|
||||
|
||||
if (nodeUuidMark === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
foundNode = true
|
||||
|
||||
// the first pos does not contain the mark, so we need to add 1 (pos + 1) to get the correct range
|
||||
const range = getMarkRange(doc.resolve(pos + 1), schema.marks[NodeUuidExtension.name])
|
||||
|
||||
if (range == null || typeof range !== 'object') {
|
||||
return false
|
||||
}
|
||||
|
||||
const [$start, $end] = [doc.resolve(range.from), doc.resolve(range.to)]
|
||||
editor?.view.dispatch(tr.setSelection(new TextSelection($start, $end)))
|
||||
editor.commands.focus()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,7 +116,7 @@ export const findNodeUuidMark = (node: Node): ProseMirrorMark | undefined => {
|
||||
* Creates span node with attribute node-uuid
|
||||
*/
|
||||
export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
name: NAME,
|
||||
name: nodeUuidName,
|
||||
exitable: true,
|
||||
inclusive: false,
|
||||
addOptions () {
|
||||
@ -79,9 +127,9 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
|
||||
addAttributes () {
|
||||
return {
|
||||
[NAME]: {
|
||||
[nodeUuidName]: {
|
||||
default: null,
|
||||
parseHTML: (el) => (el as HTMLSpanElement).getAttribute(NAME)
|
||||
parseHTML: (el) => (el as HTMLSpanElement).getAttribute(nodeUuidName)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -89,9 +137,9 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
parseHTML () {
|
||||
return [
|
||||
{
|
||||
tag: `span[${NAME}]`,
|
||||
tag: `span[${nodeUuidName}]`,
|
||||
getAttrs: (el) => {
|
||||
const value = (el as HTMLSpanElement).getAttribute(NAME)?.trim()
|
||||
const value = (el as HTMLSpanElement).getAttribute(nodeUuidName)?.trim()
|
||||
if (value === null || value === undefined || value.length === 0) {
|
||||
return false
|
||||
}
|
||||
@ -119,12 +167,12 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
const to = Math.min(view.state.doc.content.size, pos + 1)
|
||||
const markRanges =
|
||||
getMarksBetween(from, to, view.state.doc)?.filter(
|
||||
(markRange) => markRange.mark.type.name === NAME && markRange.from <= pos && markRange.to >= pos
|
||||
(markRange) => markRange.mark.type.name === nodeUuidName && markRange.from <= pos && markRange.to >= pos
|
||||
) ?? []
|
||||
let nodeUuid: string | null = null
|
||||
|
||||
if (markRanges.length > 0) {
|
||||
nodeUuid = markRanges[0].mark.attrs[NAME]
|
||||
nodeUuid = markRanges[0].mark.attrs[nodeUuidName]
|
||||
}
|
||||
|
||||
if (nodeUuid !== null) {
|
||||
@ -144,7 +192,7 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
},
|
||||
|
||||
addCommands () {
|
||||
const result: NodeUuidCommands<Command>[typeof NAME] = {
|
||||
return {
|
||||
setNodeUuid:
|
||||
(uuid: string) =>
|
||||
({ commands, state }: CommandProps) => {
|
||||
@ -152,19 +200,17 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
if (selection.empty) {
|
||||
return false
|
||||
}
|
||||
if (doc.rangeHasMark(selection.from, selection.to, getMarkType(NAME, state.schema))) {
|
||||
if (doc.rangeHasMark(selection.from, selection.to, getMarkType(nodeUuidName, state.schema))) {
|
||||
return false
|
||||
}
|
||||
|
||||
return commands.setMark(this.name, { [NAME]: uuid })
|
||||
return commands.setMark(this.name, { [nodeUuidName]: uuid })
|
||||
},
|
||||
unsetNodeUuid:
|
||||
() =>
|
||||
({ commands }: CommandProps) =>
|
||||
commands.unsetMark(this.name)
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
addStorage () {
|
||||
@ -176,7 +222,7 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
onSelectionUpdate () {
|
||||
const activeNodeUuidMark = findSelectionNodeUuidMark(this.editor.state)
|
||||
const activeNodeUuid =
|
||||
activeNodeUuidMark !== null && activeNodeUuidMark !== undefined ? activeNodeUuidMark.attrs[NAME] : null
|
||||
activeNodeUuidMark !== null && activeNodeUuidMark !== undefined ? activeNodeUuidMark.attrs[nodeUuidName] : null
|
||||
|
||||
if (this.storage.activeNodeUuid !== activeNodeUuid) {
|
||||
this.storage.activeNodeUuid = activeNodeUuid
|
||||
|
@ -16,9 +16,19 @@
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import TiptapTable from '@tiptap/extension-table'
|
||||
import { CellSelection } from '@tiptap/pm/tables'
|
||||
import { getEventPositionElement, SelectPopup, showPopup } from '@hcengineering/ui'
|
||||
import textEditor from '@hcengineering/text-editor'
|
||||
|
||||
import { SvelteNodeViewRenderer } from '../../node-view'
|
||||
import TableNodeView from './TableNodeView.svelte'
|
||||
import { isTableSelected } from './utils'
|
||||
import AddColAfter from '../../icons/table/AddColAfter.svelte'
|
||||
import AddColBefore from '../../icons/table/AddColBefore.svelte'
|
||||
import AddRowAfter from '../../icons/table/AddRowAfter.svelte'
|
||||
import AddRowBefore from '../../icons/table/AddRowBefore.svelte'
|
||||
import DeleteCol from '../../icons/table/DeleteCol.svelte'
|
||||
import DeleteRow from '../../icons/table/DeleteRow.svelte'
|
||||
import DeleteTable from '../../icons/table/DeleteTable.svelte'
|
||||
|
||||
export const Table = TiptapTable.extend({
|
||||
addKeyboardShortcuts () {
|
||||
@ -46,3 +56,95 @@ function handleDelete (editor: Editor): boolean {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export async function openTableOptions (editor: Editor, event: MouseEvent): Promise<void> {
|
||||
const ops = [
|
||||
{
|
||||
id: '#addColumnBefore',
|
||||
icon: AddColBefore,
|
||||
label: textEditor.string.AddColumnBefore,
|
||||
action: () => editor.commands.addColumnBefore(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryColumn
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#addColumnAfter',
|
||||
icon: AddColAfter,
|
||||
label: textEditor.string.AddColumnAfter,
|
||||
action: () => editor.commands.addColumnAfter(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryColumn
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: '#deleteColumn',
|
||||
icon: DeleteCol,
|
||||
label: textEditor.string.DeleteColumn,
|
||||
action: () => editor.commands.deleteColumn(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryColumn
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#addRowBefore',
|
||||
icon: AddRowBefore,
|
||||
label: textEditor.string.AddRowBefore,
|
||||
action: () => editor.commands.addRowBefore(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryRow
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#addRowAfter',
|
||||
icon: AddRowAfter,
|
||||
label: textEditor.string.AddRowAfter,
|
||||
action: () => editor.commands.addRowAfter(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryRow
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#deleteRow',
|
||||
icon: DeleteRow,
|
||||
label: textEditor.string.DeleteRow,
|
||||
action: () => editor.commands.deleteRow(),
|
||||
category: {
|
||||
label: textEditor.string.CategoryRow
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '#deleteTable',
|
||||
icon: DeleteTable,
|
||||
label: textEditor.string.DeleteTable,
|
||||
action: () => editor.commands.deleteTable(),
|
||||
category: {
|
||||
label: textEditor.string.Table
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: ops
|
||||
},
|
||||
getEventPositionElement(event),
|
||||
(val) => {
|
||||
if (val !== undefined) {
|
||||
const op = ops.find((it) => it.id === val)
|
||||
if (op !== undefined) {
|
||||
op.action()
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export async function isEditableTableActive (editor: Editor): Promise<boolean> {
|
||||
return editor.isEditable && editor.isActive('table')
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M15 5C15 4.44772 15.4477 4 16 4C16.5523 4 17 4.44772 17 5V27C17 27.5523 16.5523 28 16 28C15.4477 28 15 27.5523 15 27V24H7C6.44772 24 6 23.5523 6 23C6 22.4477 6.44772 22 7 22H15V17H9C8.44772 17 8 16.5523 8 16C8 15.4477 8.44772 15 9 15H15V10H5C4.44772 10 4 9.55228 4 9C4 8.44772 4.44772 8 5 8H15V5Z"
|
||||
/>
|
||||
<path d="M19 24H25C25.5523 24 26 23.5523 26 23C26 22.4477 25.5523 22 25 22H19V24Z" />
|
||||
<path d="M19 17H22C22.5523 17 23 16.5523 23 16C23 15.4477 22.5523 15 22 15H19V17Z" />
|
||||
<path d="M19 10H27C27.5523 10 28 9.55228 28 9C28 8.44772 27.5523 8 27 8H19V10Z" />
|
||||
</svg>
|
@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5 4C4.44772 4 4 4.44772 4 5V27C4 27.5523 4.44772 28 5 28C5.55228 28 6 27.5523 6 27V5C6 4.44772 5.55228 4 5 4Z"
|
||||
/>
|
||||
<path
|
||||
d="M27 8C27.5523 8 28 8.44772 28 9C28 9.55228 27.5523 10 27 10H11C10.4477 10 10 9.55228 10 9C10 8.44772 10.4477 8 11 8H27Z"
|
||||
/>
|
||||
<path
|
||||
d="M22 15C22.5523 15 23 15.4477 23 16C23 16.5523 22.5523 17 22 17H11C10.4477 17 10 16.5523 10 16C10 15.4477 10.4477 15 11 15H22Z"
|
||||
/>
|
||||
<path
|
||||
d="M28 23C28 22.4477 27.5523 22 27 22H11C10.4477 22 10 22.4477 10 23C10 23.5523 10.4477 24 11 24H27C27.5523 24 28 23.5523 28 23Z"
|
||||
/>
|
||||
</svg>
|
@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M27 4C26.4477 4 26 4.44772 26 5V27C26 27.5523 26.4477 28 27 28C27.5523 28 28 27.5523 28 27V5C28 4.44772 27.5523 4 27 4Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 8C4.44772 8 4 8.44772 4 9C4 9.55228 4.44772 10 5 10H21C21.5523 10 22 9.55228 22 9C22 8.44772 21.5523 8 21 8H5Z"
|
||||
/>
|
||||
<path
|
||||
d="M10 15C9.44772 15 9 15.4477 9 16C9 16.5523 9.44772 17 10 17H21C21.5523 17 22 16.5523 22 16C22 15.4477 21.5523 15 21 15H10Z"
|
||||
/>
|
||||
<path
|
||||
d="M4 23C4 22.4477 4.44772 22 5 22H21C21.5523 22 22 22.4477 22 23C22 23.5523 21.5523 24 21 24H5C4.44772 24 4 23.5523 4 23Z"
|
||||
/>
|
||||
</svg>
|
@ -1,10 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M18.25 25H10C9.44772 25 9 24.5523 9 24V8C9 7.44772 9.44772 7 10 7H17.5C18.5022 7.00006 19.4834 7.28695 20.3277 7.82679C21.172 8.36662 21.8442 9.13684 22.2649 10.0465C22.6855 10.9561 22.837 11.9671 22.7015 12.96C22.5659 13.953 22.149 14.8864 21.5 15.65C22.3477 16.328 22.9645 17.252 23.2653 18.295C23.5662 19.3379 23.5364 20.4485 23.18 21.4738C22.8236 22.4991 22.1581 23.3887 21.2753 24.0202C20.3924 24.6517 19.3355 24.994 18.25 25ZM12 22H18.23C18.5255 22 18.8181 21.9418 19.091 21.8287C19.364 21.7157 19.6121 21.5499 19.821 21.341C20.0299 21.1321 20.1957 20.884 20.3087 20.611C20.4218 20.3381 20.48 20.0455 20.48 19.75C20.48 19.4545 20.4218 19.1619 20.3087 18.889C20.1957 18.616 20.0299 18.3679 19.821 18.159C19.6121 17.9501 19.364 17.7843 19.091 17.6713C18.8181 17.5582 18.5255 17.5 18.23 17.5H12V22ZM12 14.5H17.5C17.7955 14.5 18.0881 14.4418 18.361 14.3287C18.634 14.2157 18.8821 14.0499 19.091 13.841C19.2999 13.6321 19.4657 13.384 19.5787 13.111C19.6918 12.8381 19.75 12.5455 19.75 12.25C19.75 11.9545 19.6918 11.6619 19.5787 11.389C19.4657 11.116 19.2999 10.8679 19.091 10.659C18.8821 10.4501 18.634 10.2843 18.361 10.1713C18.0881 10.0582 17.7955 10 17.5 10H12V14.5Z"
|
||||
/>
|
||||
</svg>
|
@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8 8C8 7.44772 7.55228 7 7 7C6.44772 7 6 7.44772 6 8V24C6 24.5523 6.44772 25 7 25C7.55228 25 8 24.5523 8 24V17H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V8C19 7.44772 18.5523 7 18 7C17.4477 7 17 7.44772 17 8V15H8V8Z"
|
||||
/>
|
||||
<path
|
||||
d="M25.8751 15.5C25.8751 15.0709 25.5639 14.7051 25.1403 14.6363C24.7166 14.5676 24.3057 14.8162 24.17 15.2233L24.104 15.4213C23.8842 16.0806 23.451 16.6478 22.8728 17.0333L22.5147 17.272C22.1126 17.54 22.004 18.0833 22.272 18.4854C22.5401 18.8875 23.0834 18.9961 23.4855 18.7281L23.8435 18.4894C23.94 18.425 24.17 16.1562 24.1251 18.2868V23.125H23.0001C22.5168 23.125 22.1251 23.5168 22.1251 24C22.1251 24.4833 22.5168 24.875 23.0001 24.875H27.0001C27.4833 24.875 27.8751 24.4833 27.8751 24C27.8751 23.5168 27.4833 23.125 27.0001 23.125H25.8751V15.5Z"
|
||||
/>
|
||||
</svg>
|
@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8 8C8 7.44772 7.55228 7 7 7C6.44772 7 6 7.44772 6 8V24C6 24.5523 6.44772 25 7 25C7.55228 25 8 24.5523 8 24V17H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V8C19 7.44772 18.5523 7 18 7C17.4477 7 17 7.44772 17 8V15H8V8Z"
|
||||
/>
|
||||
<path
|
||||
d="M25.0002 14.625C23.6175 14.6239 22.6106 15.4018 22.1701 16.7233C22.0173 17.1817 22.2651 17.6773 22.7235 17.8301C23.182 17.9829 23.6775 17.7351 23.8303 17.2767C24.0381 16.6534 24.3614 16.375 25.0002 16.375C25.6752 16.375 26.1033 16.8525 26.1252 17.5065C26.1228 18.4508 25.739 19.0073 25 19.5C23.5 20.5 22.1252 21.5 22.1252 24C22.1252 24.4832 22.517 24.875 23.0002 24.875H27.5002C27.9835 24.875 28.3752 24.4832 28.3752 24C28.3752 23.5167 27.9829 23.125 27.4997 23.125H24.0953C24.0953 22 26 21 27 20C27.509 19.4273 27.8752 18.4765 27.8752 17.5C27.8525 17.0167 27.7507 16.5443 27.5329 16.1087C27.0474 15.1378 26.0693 14.625 25.0002 14.625Z"
|
||||
/>
|
||||
</svg>
|
@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8 8C8 7.44772 7.55228 7 7 7C6.44772 7 6 7.44772 6 8V24C6 24.5523 6.44772 25 7 25C7.55228 25 8 24.5523 8 24V17H17V24C17 24.5523 17.4477 25 18 25C18.5523 25 19 24.5523 19 24V8C19 7.44772 18.5523 7 18 7C17.4477 7 17 7.44772 17 8V15H8V8Z"
|
||||
/>
|
||||
<path
|
||||
d="M24.5983 16.8163C24.7929 16.7503 25.0009 16.7332 25.2037 16.7667C25.4065 16.8002 25.5979 16.8832 25.761 17.0083C25.9241 17.1334 26.0538 17.2969 26.1387 17.4841C26.2235 17.6714 26.2608 17.8766 26.2474 18.0818C26.234 18.2869 26.1701 18.4855 26.0616 18.6601C25.953 18.8347 25.8031 18.9798 25.6251 19.0825C25.6021 19.0958 25.5799 19.11 25.5585 19.125H25.0001C24.5168 19.125 24.1251 19.5168 24.1251 20C24.1251 20.4832 24.5168 20.875 25.0001 20.875H25.5586C25.5799 20.89 25.6021 20.9042 25.6252 20.9175C25.8032 21.0202 25.9531 21.1653 26.0617 21.3399C26.1702 21.5145 26.234 21.7131 26.2475 21.9182C26.2609 22.1234 26.2236 22.3286 26.1387 22.5159C26.0539 22.7031 25.9242 22.8665 25.7611 22.9917C25.598 23.1168 25.4066 23.1998 25.2038 23.2333C25.001 23.2668 24.793 23.2497 24.5984 23.1837C24.4037 23.1176 24.2283 23.0045 24.0878 22.8545C23.9473 22.7045 23.8459 22.5221 23.7927 22.3235C23.6677 21.8567 23.1879 21.5797 22.7211 21.7048C22.2543 21.8299 21.9773 22.3097 22.1024 22.7765C22.2301 23.253 22.4733 23.6907 22.8106 24.0508C23.1478 24.4109 23.5687 24.6822 24.0358 24.8408C24.503 24.9994 25.0021 25.0403 25.4888 24.9599C25.9756 24.8796 26.435 24.6804 26.8264 24.3801C27.2178 24.0797 27.5291 23.6875 27.7327 23.2381C27.9364 22.7887 28.026 22.2961 27.9937 21.8038C27.9615 21.3115 27.8083 20.8347 27.5478 20.4158C27.4558 20.2679 27.3515 20.1289 27.2362 20C27.3514 19.8711 27.4557 19.732 27.5477 19.5842C27.8082 19.1652 27.9614 18.6885 27.9937 18.1962C28.0259 17.7039 27.9363 17.2113 27.7327 16.7619C27.5291 16.3125 27.2178 15.9203 26.8264 15.6199C26.435 15.3196 25.9755 15.1204 25.4888 15.0401C25.002 14.9597 24.5029 15.0006 24.0358 15.1592C23.5686 15.3178 23.1477 15.5891 22.8105 15.9492C22.4732 16.3093 22.23 16.747 22.1023 17.2235C21.9772 17.6903 22.2542 18.1701 22.721 18.2952C23.1878 18.4203 23.6676 18.1433 23.7927 17.6765C23.8459 17.4779 23.9472 17.2955 24.0877 17.1455C24.2283 16.9955 24.4036 16.8824 24.5983 16.8163Z"
|
||||
/>
|
||||
</svg>
|
@ -1,10 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M24 9C24.5523 9 25 8.55228 25 8C25 7.44772 24.5523 7 24 7H13C12.4477 7 12 7.44772 12 8C12 8.55228 12.4477 9 13 9H17.14L12.77 23H8C7.44771 23 7 23.4477 7 24C7 24.5523 7.44772 25 8 25H19C19.5523 25 20 24.5523 20 24C20 23.4477 19.5523 23 19 23H14.86L19.23 9H24Z"
|
||||
/>
|
||||
</svg>
|
@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M29.25 6.75997C28.6926 6.20061 28.0302 5.75679 27.3009 5.45396C26.5716 5.15112 25.7897 4.99524 25 4.99524C24.2103 4.99524 23.4284 5.15112 22.6991 5.45396C22.278 5.62881 21.8792 5.85065 21.5101 6.1146C21.0614 6.43545 21.0693 7.07931 21.4594 7.46935C21.8499 7.85988 22.4827 7.84994 22.9575 7.5679C23.1218 7.47029 23.2933 7.38434 23.4707 7.31086C23.9571 7.10938 24.4785 7.00567 25.005 7.00567C25.5315 7.00567 26.0528 7.10938 26.5393 7.31086C27.0257 7.51235 27.4677 7.80767 27.84 8.17997C28.2123 8.55227 28.5076 8.99425 28.7091 9.48068C28.9106 9.96711 29.0143 10.4885 29.0143 11.015C29.0143 11.5415 28.9106 12.0628 28.7091 12.5493C28.5076 13.0357 28.2123 13.4777 27.84 13.85L19.84 21.85C19.0894 22.6019 18.0709 23.0248 17.0085 23.0257C15.9461 23.0267 14.9269 22.6055 14.175 21.855C13.4231 21.1044 13.0001 20.0859 12.9992 19.0235C12.9983 17.9611 13.4194 16.9419 14.17 16.19L14.8803 15.4746C15.2675 15.0846 15.2659 14.4537 14.8787 14.0637C14.4886 13.6709 13.8519 13.6681 13.4604 14.0596L12.75 14.77C12.1906 15.3274 11.7468 15.9897 11.444 16.7191C11.1411 17.4484 10.9852 18.2303 10.9852 19.02C10.9852 19.8097 11.1411 20.5916 11.444 21.3209C11.7468 22.0502 12.1906 22.7125 12.75 23.27C13.8815 24.387 15.41 25.0092 17 25C17.7927 25.0032 18.5782 24.8494 19.3111 24.5473C20.044 24.2452 20.7098 23.8009 21.27 23.24L29.27 15.24C30.3909 14.1123 31.0184 12.5858 31.0147 10.9958C31.0109 9.40582 30.3762 7.88232 29.25 6.75997Z"
|
||||
/>
|
||||
<path
|
||||
d="M4.18997 24.82C3.81656 24.4483 3.52026 24.0065 3.31807 23.52C3.11589 23.0335 3.01181 22.5118 3.01181 21.985C3.01181 21.4581 3.11589 20.9365 3.31807 20.4499C3.52026 19.9634 3.81656 19.5216 4.18997 19.15L12.19 11.15C12.5616 10.7766 13.0034 10.4803 13.4899 10.2781C13.9765 10.0759 14.4981 9.97181 15.025 9.97181C15.5518 9.97181 16.0735 10.0759 16.56 10.2781C17.0465 10.4803 17.4883 10.7766 17.86 11.15C18.231 11.5246 18.5231 11.9698 18.7189 12.4594C18.9147 12.9489 19.0103 13.4728 19 14C19.003 14.5288 18.9012 15.0529 18.7004 15.5421C18.4995 16.0313 18.2037 16.4758 17.83 16.85L16.4072 18.2929C16.0213 18.6842 16.0289 19.3189 16.4175 19.7075C16.808 20.098 17.4466 20.1034 17.8371 19.7129L19.25 18.3C20.3785 17.1715 21.0124 15.6409 21.0124 14.045C21.0124 12.449 20.3785 10.9185 19.25 9.78997C18.1215 8.66147 16.5909 8.02749 14.995 8.02749C13.399 8.02749 11.8685 8.66147 10.74 9.78997L2.73997 17.79C2.17911 18.3476 1.73401 19.0106 1.43029 19.7408C1.12657 20.471 0.970215 21.2541 0.970215 22.045C0.970215 22.8358 1.12657 23.6189 1.43029 24.3492C1.73401 25.0794 2.17911 25.7424 2.73997 26.3C3.87879 27.4084 5.41087 28.0198 6.99997 28C8.26594 28.0012 9.49169 27.6069 10.5118 26.885C10.964 26.5651 10.961 25.921 10.5693 25.5293C10.1781 25.1381 9.54699 25.1518 9.0717 25.4348C8.90786 25.5324 8.73689 25.6184 8.56 25.6919C8.07349 25.8941 7.55182 25.9981 7.02497 25.9981C6.49812 25.9981 5.97645 25.8941 5.48994 25.6919C5.00342 25.4897 4.56164 25.1934 4.18997 24.82Z"
|
||||
/>
|
||||
</svg>
|
@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 9C5.10457 9 6 8.10457 6 7C6 5.89543 5.10457 5 4 5C2.89543 5 2 5.89543 2 7C2 8.10457 2.89543 9 4 9Z" />
|
||||
<path
|
||||
d="M9 7C9 6.44772 9.44772 6 10 6H29C29.5523 6 30 6.44772 30 7C30 7.55228 29.5523 8 29 8H10C9.44772 8 9 7.55228 9 7Z"
|
||||
/>
|
||||
<path
|
||||
d="M9 16C9 15.4477 9.44772 15 10 15H29C29.5523 15 30 15.4477 30 16C30 16.5523 29.5523 17 29 17H10C9.44772 17 9 16.5523 9 16Z"
|
||||
/>
|
||||
<path
|
||||
d="M10 24C9.44772 24 9 24.4477 9 25C9 25.5523 9.44772 26 10 26H29C29.5523 26 30 25.5523 30 25C30 24.4477 29.5523 24 29 24H10Z"
|
||||
/>
|
||||
<path
|
||||
d="M6 16C6 17.1046 5.10457 18 4 18C2.89543 18 2 17.1046 2 16C2 14.8954 2.89543 14 4 14C5.10457 14 6 14.8954 6 16Z"
|
||||
/>
|
||||
<path
|
||||
d="M4 27C5.10457 27 6 26.1046 6 25C6 23.8954 5.10457 23 4 23C2.89543 23 2 23.8954 2 25C2 26.1046 2.89543 27 4 27Z"
|
||||
/>
|
||||
</svg>
|
@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11 7C11 6.44772 11.4477 6 12 6H29C29.5523 6 30 6.44772 30 7C30 7.55228 29.5523 8 29 8H12C11.4477 8 11 7.55228 11 7Z"
|
||||
/>
|
||||
<path
|
||||
d="M11 16C11 15.4477 11.4477 15 12 15H29C29.5523 15 30 15.4477 30 16C30 16.5523 29.5523 17 29 17H12C11.4477 17 11 16.5523 11 16Z"
|
||||
/>
|
||||
<path
|
||||
d="M11 25C11 24.4477 11.4477 24 12 24H29C29.5523 24 30 24.4477 30 25C30 25.5523 29.5523 26 29 26H12C11.4477 26 11 25.5523 11 25Z"
|
||||
/>
|
||||
<path
|
||||
d="M4.87524 18C3.49245 17.999 2.48564 18.7768 2.04514 20.0983C1.89232 20.5568 2.14009 21.0523 2.59854 21.2051C3.05699 21.3579 3.55252 21.1102 3.70534 20.6517C3.91309 20.0284 4.23643 19.75 4.87524 19.75C5.55025 19.75 5.97826 20.2275 6.00023 20.8815C5.99778 21.8258 5.61399 22.3824 4.875 22.875C3.375 23.875 2.00024 24.875 2.00024 27.375C2.00024 27.8582 2.39199 28.25 2.87524 28.25H7.3752C7.85845 28.25 8.25024 27.8582 8.25024 27.375C8.25024 26.8918 7.85794 26.5 7.37469 26.5H3.97031C3.97031 25.375 5.875 24.375 6.875 23.375C7.38403 22.8024 7.75024 21.8515 7.75024 20.875C7.72751 20.3917 7.6257 19.9194 7.40786 19.4837C6.92243 18.5128 5.94432 18 4.87524 18Z"
|
||||
/>
|
||||
<path
|
||||
d="M5.75009 4.87508C5.75009 4.44593 5.43886 4.08012 5.01525 4.01138C4.59164 3.94264 4.1807 4.19125 4.04499 4.59838L3.979 4.79638C3.75924 5.45564 3.32601 6.02285 2.7478 6.40832L2.38973 6.64704C1.98764 6.91509 1.87899 7.45835 2.14705 7.86044C2.41511 8.26253 2.95837 8.37118 3.36046 8.10312L3.71853 7.86441C3.81501 7.80009 4.04499 5.53131 4.00009 7.66187V12.5001H2.87509C2.39184 12.5001 2.00009 12.8918 2.00009 13.3751C2.00009 13.8583 2.39184 14.2501 2.87509 14.2501H6.87509C7.35834 14.2501 7.75009 13.8583 7.75009 13.3751C7.75009 12.8918 7.35834 12.5001 6.87509 12.5001H5.75009V4.87508Z"
|
||||
/>
|
||||
</svg>
|
@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10.1,4.7C9.9,4.4,9.7,4.4,9.4,4.6C5.7,7,3.6,9.8,3.2,13c-0.7,5,3.8,7.4,6.1,5.2c2.3-2.2,0.9-5-0.7-5.8 C6.9,11.7,5.9,12,6,11c0.2-1,2.5-3.8,4.6-5.2c0.1-0.1,0.2-0.3,0.1-0.5C10.6,5.2,10.4,5,10.1,4.7z"
|
||||
/>
|
||||
<path
|
||||
d="M20.5,5.8c0.1-0.1,0.2-0.3,0.1-0.5c-0.1-0.1-0.3-0.3-0.5-0.7c-0.2-0.3-0.4-0.3-0.7-0.1C15.6,7,13.5,9.8,13.1,13 c-0.7,5,3.8,7.4,6.1,5.2c2.3-2.2,0.9-5-0.7-5.8c-1.6-0.8-2.6-0.5-2.5-1.5C16.1,10,18.5,7.1,20.5,5.8z"
|
||||
/>
|
||||
</svg>
|
@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M20 4V6H24.586L18.293 12.2929C17.9024 12.6834 17.9024 13.3166 18.293 13.7071C18.6835 14.0976 19.3167 14.0976 19.7072 13.7071L26 7.414V12H28V4H20Z"
|
||||
/>
|
||||
<path
|
||||
d="M13.7073 19.7071C14.0978 19.3166 14.0978 18.6834 13.7073 18.2929C13.3167 17.9024 12.6836 17.9024 12.293 18.2929L6 24.586V20H4V28H12V26H7.414L13.7073 19.7071Z"
|
||||
/>
|
||||
<path
|
||||
d="M12 4V6H7.414L13.707 12.2929C14.0976 12.6834 14.0976 13.3166 13.707 13.7071C13.3165 14.0976 12.6833 14.0976 12.2928 13.7071L6 7.414V12H4V4H12Z"
|
||||
/>
|
||||
<path
|
||||
d="M18.2927 19.7071C17.9022 19.3166 17.9022 18.6834 18.2927 18.2929C18.6833 17.9024 19.3164 17.9024 19.707 18.2929L26 24.586V20H28V28H20V26H24.586L18.2927 19.7071Z"
|
||||
/>
|
||||
</svg>
|
@ -1,15 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="17.3,6.3 17.3,6.3 17.3,6.3" />
|
||||
<polygon points="17.3,6.3 17.3,6.3 17.3,6.3" />
|
||||
<path
|
||||
d="M8.2,9.6c0.3-0.2,0.4-0.7,0.2-1C7.5,7.2,7.5,5.7,8.1,4.7C8.7,3.6,10,2.8,12,2.8c2,0,3.3,0.9,4.1,1.8 C16.6,5,16.9,5.4,17,5.7c0.1,0.2,0.2,0.3,0.2,0.4c0,0,0,0.1,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0v0l0,0c0.1,0.4,0.6,0.6,1,0.4 c0.4-0.1,0.6-0.6,0.4-1l-0.6,0.2c0.6-0.2,0.6-0.2,0.6-0.2c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0 c0,0,0-0.1-0.1-0.2c-0.1-0.1-0.1-0.3-0.3-0.5c-0.2-0.4-0.6-1-1.1-1.5c-1-1.1-2.7-2.2-5.2-2.2c-2.5,0-4.3,1.1-5.2,2.7 C5.9,5.6,6,7.6,7.2,9.4C7.4,9.8,7.9,9.9,8.2,9.6z"
|
||||
/>
|
||||
<path
|
||||
d="M21.5,11.2h-19c-0.4,0-0.8,0.3-0.8,0.8s0.3,0.8,0.8,0.8h9.4c2,0.8,3.2,1.8,3.8,2.8c0,0.1,0.1,0.1,0.1,0.2 c0.4,0.8,0.5,1.6,0.4,2.3c-0.3,1.8-2,3.3-4.1,3.3c-3.5,0-5.3-3.6-5.3-3.6c0,0,0,0,0-0.1c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1 c0,0-0.1-0.1-0.1-0.1c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0s-0.1,0-0.1,0c0,0,0,0-0.1,0c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0c0,0,0,0,0,0 c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0.1-0.1,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.1,0,0.2c0,0,0,0,0,0.1 l0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0.1,0.1,0.2 c0,0.1,0.1,0.3,0.2,0.5c0.2,0.4,0.5,1,1,1.6c1,1.2,2.7,2.3,5.4,2.3c2.2,0,4.2-1.1,5.1-2.9c0.7-1.3,0.8-2.7,0.3-4.1 c-0.1-0.2-0.1-0.4-0.2-0.5c0,0,0-0.1-0.1-0.1c-0.1-0.2-0.2-0.3-0.3-0.5c0,0,0-0.1-0.1-0.1c0,0,0,0,0,0c-0.4-0.6-1-1.3-1.8-1.8h6.5 c0.4,0,0.8-0.3,0.8-0.8S21.9,11.2,21.5,11.2z"
|
||||
/>
|
||||
</svg>
|
@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
export let fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M10 17V8C10 7.44772 9.55228 7 9 7C8.44772 7 8 7.44772 8 8V17C8 21.4183 11.5817 25 16 25H17V24.9381C20.9463 24.446 24 21.0796 24 17V8C24 7.44772 23.5523 7 23 7C22.4477 7 22 7.44772 22 8V17C22 20.3137 19.3137 23 16 23C12.6863 23 10 20.3137 10 17Z"
|
||||
/>
|
||||
<path
|
||||
d="M8 27C7.44772 27 7 27.4477 7 28C7 28.5523 7.44772 29 8 29H24C24.5523 29 25 28.5523 25 28C25 27.4477 24.5523 27 24 27H8Z"
|
||||
/>
|
||||
</svg>
|
@ -15,7 +15,13 @@
|
||||
//
|
||||
|
||||
import { type Resources } from '@hcengineering/platform'
|
||||
import { formatLink } from './kits/default-kit'
|
||||
import { isEditable } from './kits/editor-kit'
|
||||
import { openTableOptions, isEditableTableActive } from './components/extension/table/table'
|
||||
import { openImage, expandImage, moreImageActions } from './components/extension/imageExt'
|
||||
|
||||
export * from '@hcengineering/presentation/src/types'
|
||||
export type { EditorKitOptions } from './kits/editor-kit'
|
||||
export { default as Collaboration } from './components/Collaboration.svelte'
|
||||
export { default as CollaborationDiffViewer } from './components/CollaborationDiffViewer.svelte'
|
||||
export { default as CollaborativeAttributeBox } from './components/CollaborativeAttributeBox.svelte'
|
||||
@ -26,12 +32,12 @@ export { default as FullDescriptionBox } from './components/FullDescriptionBox.s
|
||||
export { default as MarkupDiffViewer } from './components/MarkupDiffViewer.svelte'
|
||||
export { default as ReferenceInput } from './components/ReferenceInput.svelte'
|
||||
export { default as StringDiffViewer } from './components/StringDiffViewer.svelte'
|
||||
export { default as StyleButton } from './components/StyleButton.svelte'
|
||||
export { default as StyleButton } from './components/TextActionButton.svelte'
|
||||
export { default as StyledTextArea } from './components/StyledTextArea.svelte'
|
||||
export { default as StyledTextBox } from './components/StyledTextBox.svelte'
|
||||
export { default as StyledTextEditor } from './components/StyledTextEditor.svelte'
|
||||
export { default as TextEditor } from './components/TextEditor.svelte'
|
||||
export { default as TextEditorStyleToolbar } from './components/TextEditorStyleToolbar.svelte'
|
||||
export { default as TextEditorToolbar } from './components/TextEditorToolbar.svelte'
|
||||
export { default as AttachIcon } from './components/icons/Attach.svelte'
|
||||
export { default as TableIcon } from './components/icons/Table.svelte'
|
||||
export { default as TableOfContents } from './components/toc/TableOfContents.svelte'
|
||||
@ -55,15 +61,16 @@ export {
|
||||
} from './components/extension/nodeHighlight'
|
||||
export {
|
||||
NodeUuidExtension,
|
||||
type NodeUuidCommands,
|
||||
type NodeUuidOptions,
|
||||
type NodeUuidStorage
|
||||
type NodeUuidStorage,
|
||||
getNodeElement,
|
||||
selectNode,
|
||||
nodeUuidName
|
||||
} from './components/extension/nodeUuid'
|
||||
export { InlinePopupExtension } from './components/extension/inlinePopup'
|
||||
export { InlineStyleToolbarExtension, type InlineStyleToolbarOptions } from './components/extension/inlineStyleToolbar'
|
||||
export { InlineToolbarExtension, type InlineStyleToolbarOptions } from './components/extension/inlineToolbar'
|
||||
export { ImageExtension, type ImageOptions } from './components/extension/imageExt'
|
||||
export { ImageUploadExtension, type ImageUploadOptions } from './components/extension/imageUploadExt'
|
||||
|
||||
export * from './command/deleteAttachment'
|
||||
export {
|
||||
TiptapCollabProvider,
|
||||
@ -73,6 +80,13 @@ export {
|
||||
export { formatCollaborativeDocumentId, formatPlatformDocumentId } from './provider/utils'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
// component: {
|
||||
// }
|
||||
function: {
|
||||
FormatLink: formatLink,
|
||||
OpenTableOptions: openTableOptions,
|
||||
OpenImage: openImage,
|
||||
ExpandImage: expandImage,
|
||||
MoreImageActions: moreImageActions,
|
||||
IsEditableTableActive: isEditableTableActive,
|
||||
IsEditable: isEditable
|
||||
}
|
||||
})
|
||||
|
@ -14,7 +14,8 @@
|
||||
//
|
||||
|
||||
import { codeBlockOptions, codeOptions } from '@hcengineering/text'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import { type Editor, Extension } from '@tiptap/core'
|
||||
import type { CodeOptions } from '@tiptap/extension-code'
|
||||
import type { CodeBlockOptions } from '@tiptap/extension-code-block'
|
||||
import type { HardBreakOptions } from '@tiptap/extension-hard-break'
|
||||
@ -25,6 +26,8 @@ import Typography from '@tiptap/extension-typography'
|
||||
import Underline from '@tiptap/extension-underline'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
|
||||
import LinkPopup from '../components/LinkPopup.svelte'
|
||||
|
||||
export interface DefaultKitOptions {
|
||||
codeBlock?: Partial<CodeBlockOptions> | false
|
||||
code?: Partial<CodeOptions> | false
|
||||
@ -64,3 +67,15 @@ export const DefaultKit = Extension.create<DefaultKitOptions>({
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
export async function formatLink (editor: Editor): Promise<void> {
|
||||
const link = editor.getAttributes('link').href
|
||||
|
||||
showPopup(LinkPopup, { link }, undefined, undefined, (newLink) => {
|
||||
if (newLink === '') {
|
||||
editor.chain().focus().extendMarkRange('link').unsetLink().run()
|
||||
} else {
|
||||
editor.chain().focus().extendMarkRange('link').setLink({ href: newLink }).run()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -14,11 +14,10 @@
|
||||
//
|
||||
import { type Class, type Space, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { type AnyExtension, Extension } from '@tiptap/core'
|
||||
import { type AnyExtension, type Editor, Extension } from '@tiptap/core'
|
||||
import { type Level } from '@tiptap/extension-heading'
|
||||
import ListKeymap from '@tiptap/extension-list-keymap'
|
||||
import TableHeader from '@tiptap/extension-table-header'
|
||||
|
||||
import 'prosemirror-codemark/dist/codemark.css'
|
||||
import { getBlobRef, getClient } from '@hcengineering/presentation'
|
||||
import { CodeBlockExtension, codeBlockOptions, CodeExtension, codeOptions } from '@hcengineering/text'
|
||||
@ -32,6 +31,34 @@ import { NodeUuidExtension } from '../components/extension/nodeUuid'
|
||||
import { Table, TableCell, TableRow } from '../components/extension/table'
|
||||
import { SubmitExtension, type SubmitOptions } from '../components/extension/submit'
|
||||
import { ParagraphExtension } from '../components/extension/paragraph'
|
||||
import { InlineToolbarExtension } from '../components/extension/inlineToolbar'
|
||||
|
||||
export interface EditorKitOptions extends DefaultKitOptions {
|
||||
history?: false
|
||||
file?: Partial<FileOptions> | false
|
||||
image?:
|
||||
| (Partial<ImageOptions> & {
|
||||
toolbar?: {
|
||||
element: HTMLElement
|
||||
boundary?: HTMLElement
|
||||
isHidden?: () => boolean
|
||||
}
|
||||
})
|
||||
| false
|
||||
mode?: 'full' | 'compact'
|
||||
submit?: SubmitOptions | false
|
||||
objectId?: Ref<Doc>
|
||||
objectClass?: Ref<Class<Doc>>
|
||||
objectSpace?: Ref<Space>
|
||||
toolbar?:
|
||||
| {
|
||||
element?: HTMLElement
|
||||
boundary?: HTMLElement
|
||||
appendTo?: HTMLElement | (() => HTMLElement)
|
||||
isHidden?: () => boolean
|
||||
}
|
||||
| false
|
||||
}
|
||||
|
||||
const headingLevels: Level[] = [1, 2, 3]
|
||||
|
||||
@ -50,15 +77,31 @@ export const tableKitExtensions: KitExtension[] = [
|
||||
[40, TableCell.configure({})]
|
||||
]
|
||||
|
||||
export interface EditorKitOptions extends DefaultKitOptions {
|
||||
history?: false
|
||||
file?: Partial<FileOptions> | false
|
||||
image?: Partial<ImageOptions> | false
|
||||
mode?: 'full' | 'compact'
|
||||
submit?: SubmitOptions | false
|
||||
objectId?: Ref<Doc>
|
||||
objectClass?: Ref<Class<Doc>>
|
||||
objectSpace?: Ref<Space>
|
||||
function getTippyOptions (
|
||||
boundary?: HTMLElement,
|
||||
appendTo?: HTMLElement | (() => HTMLElement),
|
||||
placement?: string,
|
||||
offset?: number[]
|
||||
): any {
|
||||
return {
|
||||
zIndex: 100000,
|
||||
placement,
|
||||
offset,
|
||||
popperOptions: {
|
||||
modifiers: [
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary,
|
||||
padding: 8,
|
||||
altAxis: true,
|
||||
tether: false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
...(appendTo !== undefined ? { appendTo } : {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,14 +220,37 @@ async function buildEditorKit (): Promise<Extension<EditorKitOptions, any>> {
|
||||
}
|
||||
|
||||
if (this.options.image !== false) {
|
||||
const imageOptions: ImageOptions = {
|
||||
inline: true,
|
||||
loadingImgSrc:
|
||||
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgd2lkdGg9IjMycHgiIGhlaWdodD0iMzJweCIgdmlld0JveD0iMCAwIDE2IDE2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICAgIDxwYXRoIGQ9Im0gNCAxIGMgLTEuNjQ0NTMxIDAgLTMgMS4zNTU0NjkgLTMgMyB2IDEgaCAxIHYgLTEgYyAwIC0xLjEwOTM3NSAwLjg5MDYyNSAtMiAyIC0yIGggMSB2IC0xIHogbSAyIDAgdiAxIGggNCB2IC0xIHogbSA1IDAgdiAxIGggMSBjIDEuMTA5Mzc1IDAgMiAwLjg5MDYyNSAyIDIgdiAxIGggMSB2IC0xIGMgMCAtMS42NDQ1MzEgLTEuMzU1NDY5IC0zIC0zIC0zIHogbSAtNSA0IGMgLTAuNTUwNzgxIDAgLTEgMC40NDkyMTkgLTEgMSBzIDAuNDQ5MjE5IDEgMSAxIHMgMSAtMC40NDkyMTkgMSAtMSBzIC0wLjQ0OTIxOSAtMSAtMSAtMSB6IG0gLTUgMSB2IDQgaCAxIHYgLTQgeiBtIDEzIDAgdiA0IGggMSB2IC00IHogbSAtNC41IDIgbCAtMiAyIGwgLTEuNSAtMSBsIC0yIDIgdiAwLjUgYyAwIDAuNSAwLjUgMC41IDAuNSAwLjUgaCA3IHMgMC40NzI2NTYgLTAuMDM1MTU2IDAuNSAtMC41IHYgLTEgeiBtIC04LjUgMyB2IDEgYyAwIDEuNjQ0NTMxIDEuMzU1NDY5IDMgMyAzIGggMSB2IC0xIGggLTEgYyAtMS4xMDkzNzUgMCAtMiAtMC44OTA2MjUgLTIgLTIgdiAtMSB6IG0gMTMgMCB2IDEgYyAwIDEuMTA5Mzc1IC0wLjg5MDYyNSAyIC0yIDIgaCAtMSB2IDEgaCAxIGMgMS42NDQ1MzEgMCAzIC0xLjM1NTQ2OSAzIC0zIHYgLTEgeiBtIC04IDMgdiAxIGggNCB2IC0xIHogbSAwIDAiIGZpbGw9IiMyZTM0MzQiIGZpbGwtb3BhY2l0eT0iMC4zNDkwMiIvPg0KPC9zdmc+DQo=',
|
||||
getBlobRef: async (file, name, size) => await getBlobRef(undefined, file, name, size),
|
||||
HTMLAttributes: this.options.image?.HTMLAttributes ?? {},
|
||||
...this.options.image
|
||||
}
|
||||
|
||||
if (this.options.image?.toolbar !== undefined) {
|
||||
imageOptions.toolbar = {
|
||||
...this.options.image?.toolbar,
|
||||
tippyOptions: getTippyOptions(this.options.image?.toolbar?.boundary)
|
||||
}
|
||||
}
|
||||
|
||||
staticKitExtensions.push([800, ImageExtension.configure(imageOptions)])
|
||||
}
|
||||
|
||||
if (this.options.toolbar !== false) {
|
||||
staticKitExtensions.push([
|
||||
800,
|
||||
ImageExtension.configure({
|
||||
inline: true,
|
||||
loadingImgSrc:
|
||||
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgd2lkdGg9IjMycHgiIGhlaWdodD0iMzJweCIgdmlld0JveD0iMCAwIDE2IDE2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICAgIDxwYXRoIGQ9Im0gNCAxIGMgLTEuNjQ0NTMxIDAgLTMgMS4zNTU0NjkgLTMgMyB2IDEgaCAxIHYgLTEgYyAwIC0xLjEwOTM3NSAwLjg5MDYyNSAtMiAyIC0yIGggMSB2IC0xIHogbSAyIDAgdiAxIGggNCB2IC0xIHogbSA1IDAgdiAxIGggMSBjIDEuMTA5Mzc1IDAgMiAwLjg5MDYyNSAyIDIgdiAxIGggMSB2IC0xIGMgMCAtMS42NDQ1MzEgLTEuMzU1NDY5IC0zIC0zIC0zIHogbSAtNSA0IGMgLTAuNTUwNzgxIDAgLTEgMC40NDkyMTkgLTEgMSBzIDAuNDQ5MjE5IDEgMSAxIHMgMSAtMC40NDkyMTkgMSAtMSBzIC0wLjQ0OTIxOSAtMSAtMSAtMSB6IG0gLTUgMSB2IDQgaCAxIHYgLTQgeiBtIDEzIDAgdiA0IGggMSB2IC00IHogbSAtNC41IDIgbCAtMiAyIGwgLTEuNSAtMSBsIC0yIDIgdiAwLjUgYyAwIDAuNSAwLjUgMC41IDAuNSAwLjUgaCA3IHMgMC40NzI2NTYgLTAuMDM1MTU2IDAuNSAtMC41IHYgLTEgeiBtIC04LjUgMyB2IDEgYyAwIDEuNjQ0NTMxIDEuMzU1NDY5IDMgMyAzIGggMSB2IC0xIGggLTEgYyAtMS4xMDkzNzUgMCAtMiAtMC44OTA2MjUgLTIgLTIgdiAtMSB6IG0gMTMgMCB2IDEgYyAwIDEuMTA5Mzc1IC0wLjg5MDYyNSAyIC0yIDIgaCAtMSB2IDEgaCAxIGMgMS42NDQ1MzEgMCAzIC0xLjM1NTQ2OSAzIC0zIHYgLTEgeiBtIC04IDMgdiAxIGggNCB2IC0xIHogbSAwIDAiIGZpbGw9IiMyZTM0MzQiIGZpbGwtb3BhY2l0eT0iMC4zNDkwMiIvPg0KPC9zdmc+DQo=',
|
||||
getBlobRef: async (file, name, size) => await getBlobRef(undefined, file, name, size),
|
||||
...this.options.image
|
||||
900,
|
||||
InlineToolbarExtension.configure({
|
||||
tippyOptions: getTippyOptions(this.options.toolbar?.boundary, this.options.toolbar?.appendTo),
|
||||
element: this.options.toolbar?.element,
|
||||
isHidden: this.options.toolbar?.isHidden,
|
||||
ctx: {
|
||||
objectId: this.options.objectId,
|
||||
objectClass: this.options.objectClass,
|
||||
objectSpace: this.options.objectSpace
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
@ -203,3 +269,7 @@ async function buildEditorKit (): Promise<Extension<EditorKitOptions, any>> {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function isEditable (editor: Editor): Promise<boolean> {
|
||||
return editor.isEditable
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
//
|
||||
|
||||
import { type Class, type Ref } from '@hcengineering/core'
|
||||
import { type IntlString, type Metadata, type Plugin, plugin } from '@hcengineering/platform'
|
||||
import { type TextEditorExtensionFactory, type RefInputActionItem } from './types'
|
||||
import { type Asset, type IntlString, type Metadata, type Plugin, plugin } from '@hcengineering/platform'
|
||||
|
||||
import { type TextEditorExtensionFactory, type RefInputActionItem, TextEditorAction } from './types'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -26,7 +27,8 @@ export const textEditorId = 'text-editor' as Plugin
|
||||
export default plugin(textEditorId, {
|
||||
class: {
|
||||
RefInputActionItem: '' as Ref<Class<RefInputActionItem>>,
|
||||
TextEditorExtensionFactory: '' as Ref<Class<TextEditorExtensionFactory>>
|
||||
TextEditorExtensionFactory: '' as Ref<Class<TextEditorExtensionFactory>>,
|
||||
TextEditorAction: '' as Ref<Class<TextEditorAction>>
|
||||
},
|
||||
metadata: {
|
||||
CollaboratorUrl: '' as Metadata<string>
|
||||
@ -84,5 +86,27 @@ export default plugin(textEditorId, {
|
||||
Image: '' as IntlString,
|
||||
SeparatorLine: '' as IntlString,
|
||||
TodoList: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
Header1: '' as Asset,
|
||||
Header2: '' as Asset,
|
||||
Header3: '' as Asset,
|
||||
Underline: '' as Asset,
|
||||
Strikethrough: '' as Asset,
|
||||
Bold: '' as Asset,
|
||||
Italic: '' as Asset,
|
||||
Link: '' as Asset,
|
||||
ListNumber: '' as Asset,
|
||||
ListBullet: '' as Asset,
|
||||
Quote: '' as Asset,
|
||||
Code: '' as Asset,
|
||||
CodeBlock: '' as Asset,
|
||||
TableProps: '' as Asset,
|
||||
AlignLeft: '' as Asset,
|
||||
AlignCenter: '' as Asset,
|
||||
AlignRight: '' as Asset,
|
||||
MoreH: '' as Asset,
|
||||
Expand: '' as Asset,
|
||||
ScaleOut: '' as Asset
|
||||
}
|
||||
})
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import { type Account, type Doc, type Markup, type Ref } from '@hcengineering/core'
|
||||
import { type Class, type Space, type Account, type Doc, type Markup, type Ref } from '@hcengineering/core'
|
||||
import type { AnySvelteComponent } from '@hcengineering/ui/src/types'
|
||||
import { type RelativePosition } from 'yjs'
|
||||
import { type AnyExtension, type Content, type Editor, type SingleCommands } from '@tiptap/core'
|
||||
import { type ParseOptions } from '@tiptap/pm/model'
|
||||
|
||||
export type { AnyExtension } from '@tiptap/core'
|
||||
export type { AnyExtension, Editor } from '@tiptap/core'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -73,13 +73,6 @@ export interface RefAction {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface TextNodeAction {
|
||||
id: string
|
||||
label?: IntlString
|
||||
icon: Asset | AnySvelteComponent
|
||||
action: (params: { editor: Editor }) => Promise<void> | void
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -102,6 +95,15 @@ export interface TextEditorCommandProps {
|
||||
*/
|
||||
export type TextEditorCommand = (props: TextEditorCommandProps) => boolean
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ActionContext {
|
||||
objectId?: Ref<Doc>
|
||||
objectClass?: Ref<Class<Doc>>
|
||||
objectSpace?: Ref<Space>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -145,3 +147,50 @@ export interface TextEditorExtensionFactory extends Doc {
|
||||
index: number
|
||||
create: Resource<ExtensionCreator>
|
||||
}
|
||||
|
||||
/**
|
||||
* Action handler for text editor action
|
||||
*/
|
||||
export type TextActionFunction = (editor: Editor, event: MouseEvent, ctx: ActionContext) => Promise<void>
|
||||
|
||||
/**
|
||||
* Handler to determine whether the text action is visible
|
||||
*/
|
||||
export type TextActionVisibleFunction = (editor: Editor, ctx: ActionContext) => Promise<boolean>
|
||||
|
||||
/**
|
||||
* Handler to determine whether the text action is active
|
||||
*/
|
||||
export type TextActionActiveFunction = (editor: Editor) => Promise<boolean>
|
||||
|
||||
/**
|
||||
* Describes toggle handler for a text action
|
||||
*/
|
||||
export interface TogglerDescriptor {
|
||||
command: keyof SingleCommands
|
||||
params?: any
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes isActive handler for a text action
|
||||
*/
|
||||
export interface ActiveDescriptor {
|
||||
name: string
|
||||
params?: any
|
||||
}
|
||||
|
||||
export type TextEditorActionKind = 'text' | 'image'
|
||||
|
||||
/**
|
||||
* Defines a text action for text action editor
|
||||
*/
|
||||
export interface TextEditorAction extends Doc {
|
||||
kind?: TextEditorActionKind
|
||||
action: TogglerDescriptor | Resource<TextActionFunction>
|
||||
visibilityTester?: Resource<TextActionVisibleFunction>
|
||||
icon: Asset
|
||||
isActive?: ActiveDescriptor | Resource<TextActionActiveFunction>
|
||||
label: IntlString
|
||||
category: number
|
||||
index: number
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user