mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-08 21:27:45 +03:00
QMS: Update inline comments extensions (#3814)
* update documents inline comments extensions Signed-off-by: Anna No <anna.no@xored.com> * update qms documents inline comments Signed-off-by: Anna No <anna.no@xored.com> * qms: update document inline comments extensions Signed-off-by: Anna No <anna.no@xored.com> * qms: move highlight to prose mirror decorations Signed-off-by: Anna No <anna.no@xored.com> * fix formatting issues Signed-off-by: Anna No <anna.no@xored.com> * fix formatting issues Signed-off-by: Anna No <anna.no@xored.com> * fix formatting issues Signed-off-by: Anna No <anna.no@xored.com> * fix formatting issues Signed-off-by: Anna No <anna.no@xored.com> --------- Signed-off-by: Anna No <anna.no@xored.com>
This commit is contained in:
parent
8805a588bc
commit
4e08b75040
@ -35,7 +35,7 @@
|
||||
import { calculateDecorations } from './diff/decorations'
|
||||
import { defaultEditorAttributes } from './editor/editorProps'
|
||||
import { completionConfig, defaultExtensions } from './extensions'
|
||||
import { InlineStyleToolbar } from './extension/inlineStyleToolbar'
|
||||
import { InlineStyleToolbarExtension } from './extension/inlineStyleToolbar'
|
||||
import { NodeUuidExtension } from './extension/nodeUuid'
|
||||
import StyleButton from './StyleButton.svelte'
|
||||
import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte'
|
||||
@ -47,7 +47,6 @@
|
||||
export let token: string
|
||||
export let collaboratorURL: string
|
||||
|
||||
export let isFormatting = true
|
||||
export let buttonSize: IconSize = 'small'
|
||||
export let focusable: boolean = false
|
||||
export let placeholder: IntlString = textEditorPlugin.string.EditorPlaceholder
|
||||
@ -90,7 +89,6 @@
|
||||
|
||||
let editor: Editor
|
||||
let inlineToolbar: HTMLElement
|
||||
let showInlineToolbar = false
|
||||
|
||||
let placeHolderStr: string = ''
|
||||
|
||||
@ -136,7 +134,6 @@
|
||||
}
|
||||
|
||||
const [$start, $end] = [doc.resolve(range.from), doc.resolve(range.to)]
|
||||
|
||||
editor.view.dispatch(tr.setSelection(new TextSelection($start, $end)))
|
||||
needFocus = true
|
||||
})
|
||||
@ -146,6 +143,22 @@
|
||||
provider.copyContent(documentId, snapshotId)
|
||||
}
|
||||
|
||||
export function unregisterPlugin (nameOrPluginKey: string | PluginKey) {
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
|
||||
editor.unregisterPlugin(nameOrPluginKey)
|
||||
}
|
||||
|
||||
export function registerPlugin (plugin: Plugin) {
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
|
||||
editor.registerPlugin(plugin)
|
||||
}
|
||||
|
||||
let needFocus = false
|
||||
|
||||
let focused = false
|
||||
@ -201,6 +214,7 @@
|
||||
})
|
||||
|
||||
$: updateEditor(editor, field, comparedVersion)
|
||||
$: if (editor) dispatch('editor', editor)
|
||||
|
||||
onMount(() => {
|
||||
ph.then(() => {
|
||||
@ -211,10 +225,10 @@
|
||||
extensions: [
|
||||
...defaultExtensions,
|
||||
Placeholder.configure({ placeholder: placeHolderStr }),
|
||||
InlineStyleToolbar.configure({
|
||||
InlineStyleToolbarExtension.configure({
|
||||
element: inlineToolbar,
|
||||
getEditorElement: () => element,
|
||||
isShown: () => !readonly && showInlineToolbar
|
||||
isSupported: () => !readonly,
|
||||
isSelectionOnly: () => false
|
||||
}),
|
||||
Collaboration.configure({
|
||||
document: ydoc,
|
||||
@ -247,22 +261,14 @@
|
||||
onFocus: () => {
|
||||
focused = true
|
||||
},
|
||||
onUpdate: ({ editor, transaction }) => {
|
||||
showInlineToolbar = false
|
||||
|
||||
onUpdate: ({ transaction }) => {
|
||||
// ignore non-document changes
|
||||
if (!transaction.docChanged) return
|
||||
|
||||
// TODO this is heavy and should be replaced with more lightweight event
|
||||
dispatch('content', editor.getHTML())
|
||||
|
||||
// ignore non-local changes
|
||||
if (isChangeOrigin(transaction)) return
|
||||
|
||||
dispatch('update')
|
||||
},
|
||||
onSelectionUpdate: () => {
|
||||
showInlineToolbar = false
|
||||
}
|
||||
})
|
||||
|
||||
@ -283,16 +289,11 @@
|
||||
}
|
||||
})
|
||||
|
||||
function onEditorClick () {
|
||||
if (!editor.isEmpty) {
|
||||
showInlineToolbar = true
|
||||
}
|
||||
}
|
||||
|
||||
let showDiff = true
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<slot {editor} />
|
||||
|
||||
{#if visible}
|
||||
{#if comparedVersion !== undefined || $$slots.tools}
|
||||
<div class="ref-container" class:autoOverflow>
|
||||
@ -336,7 +337,7 @@
|
||||
needFocus = true
|
||||
}}
|
||||
on:action={(event) => {
|
||||
dispatch('action', { action: event.detail, editor })
|
||||
dispatch('action', event.detail)
|
||||
needFocus = true
|
||||
}}
|
||||
/>
|
||||
@ -344,7 +345,7 @@
|
||||
|
||||
<div class="ref-container" class:autoOverflow>
|
||||
<div class="textInput" class:focusable>
|
||||
<div class="select-text" style="width: 100%;" on:mousedown={onEditorClick} bind:this={element} />
|
||||
<div class="select-text" style="width: 100%;" bind:this={element} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -28,7 +28,7 @@
|
||||
import { themeStore } from '@hcengineering/ui'
|
||||
import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte'
|
||||
import { TextFormatCategory } from '../types'
|
||||
import { InlineStyleToolbar } from './extension/inlineStyleToolbar'
|
||||
import { InlineStyleToolbarExtension } from './extension/inlineStyleToolbar'
|
||||
import { defaultEditorAttributes } from './editor/editorProps'
|
||||
|
||||
export let content: string = ''
|
||||
@ -77,7 +77,6 @@
|
||||
let needFocus = false
|
||||
let focused = false
|
||||
let posFocus: FocusPosition | undefined = undefined
|
||||
let showContextMenu = false
|
||||
let textEditorToolbar: HTMLElement
|
||||
|
||||
export function focus (position?: FocusPosition): void {
|
||||
@ -137,10 +136,10 @@
|
||||
...(supportSubmit ? [Handle] : []), // order important
|
||||
Placeholder.configure({ placeholder: placeHolderStr }),
|
||||
...extensions,
|
||||
InlineStyleToolbar.configure({
|
||||
InlineStyleToolbarExtension.configure({
|
||||
element: textEditorToolbar,
|
||||
getEditorElement: () => element,
|
||||
isShown: () => showContextMenu
|
||||
isSupported: () => true,
|
||||
isSelectionOnly: () => false
|
||||
})
|
||||
],
|
||||
parseOptions: {
|
||||
@ -160,12 +159,8 @@
|
||||
},
|
||||
onUpdate: () => {
|
||||
content = editor.getHTML()
|
||||
showContextMenu = false
|
||||
dispatch('value', content)
|
||||
dispatch('update', content)
|
||||
},
|
||||
onSelectionUpdate: () => {
|
||||
showContextMenu = false
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -177,12 +172,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
function onEditorClick () {
|
||||
if (!editor.isEmpty) {
|
||||
showContextMenu = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -209,7 +198,7 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="select-text" style="width: 100%;" on:mousedown={onEditorClick} bind:this={element} />
|
||||
<div class="select-text" style="width: 100%;" bind:this={element} />
|
||||
|
||||
<style lang="scss">
|
||||
.formatPanel {
|
||||
|
@ -330,7 +330,7 @@
|
||||
disabled={textEditor.view.state.selection.empty}
|
||||
showTooltip={{ label: action.label }}
|
||||
on:click={() => {
|
||||
dispatch('action', action.id)
|
||||
dispatch('action', { action: action.id, editor: textEditor })
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
|
16
packages/text-editor/src/components/extension/inlinePopup.ts
Normal file
16
packages/text-editor/src/components/extension/inlinePopup.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Extension } from '@tiptap/core'
|
||||
import BubbleMenu, { BubbleMenuOptions } from '@tiptap/extension-bubble-menu'
|
||||
|
||||
export const InlinePopupExtension: Extension<BubbleMenuOptions> = BubbleMenu.extend({
|
||||
addOptions () {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
pluginKey: 'inline-popup',
|
||||
element: null,
|
||||
tippyOptions: {
|
||||
maxWidth: '38rem',
|
||||
appendTo: () => document.body
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
@ -1,48 +1,86 @@
|
||||
import { Extension, isTextSelection } from '@tiptap/core'
|
||||
import BubbleMenu, { BubbleMenuOptions } from '@tiptap/extension-bubble-menu'
|
||||
import { Editor, Extension, isTextSelection } from '@tiptap/core'
|
||||
import { BubbleMenuOptions } from '@tiptap/extension-bubble-menu'
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { InlinePopupExtension } from './inlinePopup'
|
||||
|
||||
type InlineStyleToolbarOptions = BubbleMenuOptions & {
|
||||
getEditorElement: () => HTMLElement | null | undefined
|
||||
isShown?: () => boolean
|
||||
export type InlineStyleToolbarOptions = BubbleMenuOptions & {
|
||||
isSupported: () => boolean
|
||||
isSelectionOnly?: () => boolean
|
||||
}
|
||||
|
||||
export const InlineStyleToolbar = Extension.create<InlineStyleToolbarOptions>({
|
||||
defaultOptions: {
|
||||
pluginKey: 'inline-style-toolbar',
|
||||
element: null,
|
||||
tippyOptions: {
|
||||
maxWidth: '38rem',
|
||||
appendTo: () => document.body
|
||||
},
|
||||
getEditorElement: () => null
|
||||
export interface InlineStyleToolbarStorage {
|
||||
isShown: boolean
|
||||
}
|
||||
|
||||
const handleFocus = (editor: Editor, options: InlineStyleToolbarOptions, storage: InlineStyleToolbarStorage): void => {
|
||||
if (!options.isSupported()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (editor.isEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
if (options.isSelectionOnly?.() === true && editor.view.state.selection.empty) {
|
||||
return
|
||||
}
|
||||
|
||||
storage.isShown = true
|
||||
}
|
||||
|
||||
export const InlineStyleToolbarExtension = Extension.create<InlineStyleToolbarOptions, InlineStyleToolbarStorage>({
|
||||
pluginKey: new PluginKey('inline-style-toolbar'),
|
||||
addProseMirrorPlugins () {
|
||||
const options = this.options
|
||||
const storage = this.storage
|
||||
const editor = this.editor
|
||||
|
||||
const plugins = [
|
||||
...(this.parent?.() ?? []),
|
||||
new Plugin({
|
||||
key: new PluginKey('inline-style-toolbar-click-plugin'),
|
||||
props: {
|
||||
handleClick () {
|
||||
handleFocus(editor, options, storage)
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
return plugins
|
||||
},
|
||||
addStorage () {
|
||||
return {
|
||||
isShown: false
|
||||
}
|
||||
},
|
||||
addExtensions () {
|
||||
const options: InlineStyleToolbarOptions = this.options
|
||||
|
||||
return [
|
||||
BubbleMenu.configure({
|
||||
InlinePopupExtension.configure({
|
||||
...options,
|
||||
// to override shouldShow behaviour a little
|
||||
// I need to copypaste original function and make a little change
|
||||
// with showContextMenu falg
|
||||
shouldShow: ({ editor, view, state, oldState, from, to }) => {
|
||||
const editorElement = options.getEditorElement()
|
||||
if (!this.options.isSupported()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (editor.isDestroyed || !editor.isEditable) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.storage.isShown) {
|
||||
return true
|
||||
}
|
||||
|
||||
// For some reason shouldShow might be called after dismount and
|
||||
// after destroing 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
|
||||
}
|
||||
|
||||
if (!editor.isEditable) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isShown = options.isShown?.() ?? false
|
||||
if (isShown) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -67,5 +105,14 @@ export const InlineStyleToolbar = Extension.create<InlineStyleToolbarOptions>({
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
onFocus () {
|
||||
handleFocus(this.editor, this.options, this.storage)
|
||||
},
|
||||
onSelectionUpdate () {
|
||||
this.storage.isShown = false
|
||||
},
|
||||
onUpdate () {
|
||||
this.storage.isShown = false
|
||||
}
|
||||
})
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { Extension, Range, getMarkRange, mergeAttributes } from '@tiptap/core'
|
||||
import { NodeUuidExtension, NodeUuidOptions, NodeUuidStorage, findNodeUuidMark } from './nodeUuid'
|
||||
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'
|
||||
import { NodeUuidExtension, NodeUuidOptions } from './nodeUuid'
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
|
||||
export enum NodeHighlightType {
|
||||
INFO = 'info',
|
||||
WARNING = 'warning',
|
||||
ADD = 'add',
|
||||
DELETE = 'delete'
|
||||
}
|
||||
export interface NodeHighlightExtensionOptions extends NodeUuidOptions {
|
||||
getNodeHighlightType: (uuid: string) => NodeHighlightType | undefined | null
|
||||
isHighlightModeOn: () => boolean
|
||||
isAutoSelect?: () => boolean
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||
@ -17,20 +19,47 @@ function isRange (range: Range | undefined | null | void): range is Range {
|
||||
return range !== null && range !== undefined
|
||||
}
|
||||
|
||||
const generateAttributes = (uuid: string, options: NodeHighlightExtensionOptions): Record<string, any> | undefined => {
|
||||
if (!options.isHighlightModeOn()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const type = options.getNodeHighlightType(uuid)
|
||||
if (type === null || type === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const classAttrs: { class?: string } = {}
|
||||
|
||||
if (type === NodeHighlightType.WARNING) {
|
||||
classAttrs.class = 'text-editor-highlighted-node-warning'
|
||||
} else if (type === NodeHighlightType.ADD) {
|
||||
classAttrs.class = 'text-editor-highlighted-node-add'
|
||||
} else if (type === NodeHighlightType.DELETE) {
|
||||
classAttrs.class = 'text-editor-highlighted-node-delete'
|
||||
}
|
||||
|
||||
return classAttrs
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension allows to highlight nodes based on uuid
|
||||
*/
|
||||
export const NodeHighlightExtension: Extension<NodeHighlightExtensionOptions> =
|
||||
export const NodeHighlightExtension: Extension<NodeHighlightExtensionOptions, NodeUuidStorage> =
|
||||
Extension.create<NodeHighlightExtensionOptions>({
|
||||
addStorage (): NodeUuidStorage {
|
||||
return { activeNodeUuid: null }
|
||||
},
|
||||
addProseMirrorPlugins () {
|
||||
const options = this.options
|
||||
const storage: NodeUuidStorage = this.storage
|
||||
|
||||
const plugins = [
|
||||
...(this.parent?.() ?? []),
|
||||
new Plugin({
|
||||
key: new PluginKey('handle-node-highlight-click-plugin'),
|
||||
props: {
|
||||
handleClick (view, pos) {
|
||||
if (!options.isHighlightModeOn()) {
|
||||
if (!options.isHighlightModeOn() || options.isAutoSelect?.() !== true) {
|
||||
return
|
||||
}
|
||||
const { schema, doc, tr } = view.state
|
||||
@ -49,45 +78,65 @@ export const NodeHighlightExtension: Extension<NodeHighlightExtensionOptions> =
|
||||
return true
|
||||
}
|
||||
}
|
||||
}),
|
||||
new Plugin({
|
||||
key: new PluginKey('node-highlight-click-decorations-plugin'),
|
||||
props: {
|
||||
decorations (state) {
|
||||
if (!options.isHighlightModeOn()) {
|
||||
return undefined
|
||||
}
|
||||
const decorations: Decoration[] = []
|
||||
const { doc, schema } = state
|
||||
doc.descendants((node, pos) => {
|
||||
const nodeUuidMark = findNodeUuidMark(node)
|
||||
|
||||
if (nodeUuidMark !== null && nodeUuidMark !== undefined) {
|
||||
const nodeUuid = nodeUuidMark.attrs[NodeUuidExtension.name]
|
||||
const attributes = generateAttributes(nodeUuid, options)
|
||||
if (attributes === null || attributes === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// 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 (!isRange(range)) {
|
||||
return
|
||||
}
|
||||
|
||||
decorations.push(
|
||||
Decoration.inline(
|
||||
range.from,
|
||||
range.to,
|
||||
mergeAttributes(
|
||||
attributes,
|
||||
nodeUuid === storage.activeNodeUuid ? { class: 'text-editor-highlighted-node-selected' } : {}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return DecorationSet.empty.add(doc, decorations)
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
return plugins
|
||||
},
|
||||
|
||||
addExtensions () {
|
||||
const options = this.options
|
||||
|
||||
const options: NodeHighlightExtensionOptions = this.options
|
||||
const storage: NodeUuidStorage = this.storage
|
||||
return [
|
||||
NodeUuidExtension.extend({
|
||||
addOptions () {
|
||||
addOptions (): NodeUuidOptions {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
...options
|
||||
}
|
||||
},
|
||||
addAttributes () {
|
||||
return {
|
||||
[NodeUuidExtension.name]: {
|
||||
renderHTML: (attrs) => {
|
||||
// get uuid from parent mark (NodeUuidExtension) attributes
|
||||
const uuid = attrs[NodeUuidExtension.name]
|
||||
const classAttrs: { class?: string } = {}
|
||||
|
||||
if (options.isHighlightModeOn()) {
|
||||
const type = options.getNodeHighlightType(uuid)
|
||||
|
||||
if (type === NodeHighlightType.INFO) {
|
||||
classAttrs.class = 'text-editor-highlighted-node-info'
|
||||
} else if (type === NodeHighlightType.ADD) {
|
||||
classAttrs.class = 'text-editor-highlighted-node-add'
|
||||
} else if (type === NodeHighlightType.DELETE) {
|
||||
classAttrs.class = 'text-editor-highlighted-node-delete'
|
||||
}
|
||||
}
|
||||
|
||||
return mergeAttributes(attrs, classAttrs)
|
||||
}
|
||||
...options,
|
||||
onNodeSelected: (uuid: string) => {
|
||||
storage.activeNodeUuid = uuid
|
||||
options.onNodeSelected?.(uuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Mark, getMarkAttributes, mergeAttributes } from '@tiptap/core'
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { Command, CommandProps, Mark, getMarkAttributes, getMarkType, mergeAttributes } from '@tiptap/core'
|
||||
import { Node, Mark as ProseMirrorMark } from 'prosemirror-model'
|
||||
import { EditorState, Plugin, PluginKey } from 'prosemirror-state'
|
||||
|
||||
const NAME = 'node-uuid'
|
||||
|
||||
@ -14,11 +15,11 @@ export interface NodeUuidCommands<ReturnType> {
|
||||
/**
|
||||
* Add uuid mark
|
||||
*/
|
||||
setUuid: (uuid: string) => ReturnType
|
||||
setNodeUuid: (uuid: string) => ReturnType
|
||||
/**
|
||||
* Unset uuid mark
|
||||
*/
|
||||
unsetUuid: () => ReturnType
|
||||
unsetNodeUuid: () => ReturnType
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,8 +31,32 @@ export interface NodeUuidStorage {
|
||||
activeNodeUuid: string | null
|
||||
}
|
||||
|
||||
const findSelectionNodeUuidMark = (state: EditorState): ProseMirrorMark | undefined => {
|
||||
if (state.selection === null || state.selection === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let nodeUuidMark: ProseMirrorMark | undefined
|
||||
state.doc.nodesBetween(state.selection.from, state.selection.to, (node) => {
|
||||
if (nodeUuidMark !== null || nodeUuidMark !== undefined) {
|
||||
return false
|
||||
}
|
||||
nodeUuidMark = findNodeUuidMark(node)
|
||||
})
|
||||
|
||||
return nodeUuidMark
|
||||
}
|
||||
|
||||
export const findNodeUuidMark = (node: Node): ProseMirrorMark | undefined => {
|
||||
if (node === null || node === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return node.marks.find((mark) => mark.type.name === NAME && mark.attrs[NAME])
|
||||
}
|
||||
|
||||
/**
|
||||
* This mark allows to add node uuid to the selected text
|
||||
* This extension allows to add node uuid to the selected text
|
||||
* Creates span node with attribute node-uuid
|
||||
*/
|
||||
export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
@ -73,20 +98,23 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
|
||||
addProseMirrorPlugins () {
|
||||
const options = this.options
|
||||
const storage: NodeUuidStorage = this.storage
|
||||
const plugins = [
|
||||
...(this.parent?.() ?? []),
|
||||
new Plugin({
|
||||
key: new PluginKey('handle-node-uuid-click-plugin'),
|
||||
props: {
|
||||
handleClick (view) {
|
||||
const { schema } = view.state
|
||||
|
||||
const attrs = getMarkAttributes(view.state, schema.marks[NAME])
|
||||
const attrs = getMarkAttributes(view.state, view.state.schema.marks[NAME])
|
||||
const nodeUuid = attrs?.[NAME]
|
||||
|
||||
if (nodeUuid !== null || nodeUuid !== undefined) {
|
||||
options.onNodeClicked?.(nodeUuid)
|
||||
}
|
||||
|
||||
if (storage.activeNodeUuid !== nodeUuid) {
|
||||
storage.activeNodeUuid = nodeUuid
|
||||
options.onNodeSelected?.(storage.activeNodeUuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -96,16 +124,27 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
},
|
||||
|
||||
addCommands () {
|
||||
return {
|
||||
setUuid:
|
||||
const result: NodeUuidCommands<Command>[typeof NAME] = {
|
||||
setNodeUuid:
|
||||
(uuid: string) =>
|
||||
({ commands }) =>
|
||||
commands.setMark(this.name, { [NAME]: uuid }),
|
||||
unsetUuid:
|
||||
({ commands, state }: CommandProps) => {
|
||||
const { doc, selection } = state
|
||||
if (selection.empty) {
|
||||
return false
|
||||
}
|
||||
if (doc.rangeHasMark(selection.from, selection.to, getMarkType(NAME, state.schema))) {
|
||||
return false
|
||||
}
|
||||
|
||||
return commands.setMark(this.name, { [NAME]: uuid })
|
||||
},
|
||||
unsetNodeUuid:
|
||||
() =>
|
||||
({ commands }) =>
|
||||
({ commands }: CommandProps) =>
|
||||
commands.unsetMark(this.name)
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
addStorage () {
|
||||
@ -115,19 +154,13 @@ export const NodeUuidExtension = Mark.create<NodeUuidOptions, NodeUuidStorage>({
|
||||
},
|
||||
|
||||
onSelectionUpdate () {
|
||||
const { $head } = this.editor.state.selection
|
||||
const activeNodeUuidMark = findSelectionNodeUuidMark(this.editor.state)
|
||||
const activeNodeUuid =
|
||||
activeNodeUuidMark !== null && activeNodeUuidMark !== undefined ? activeNodeUuidMark.attrs[NAME] : null
|
||||
|
||||
const marks = $head.marks()
|
||||
this.storage.activeNodeUuid = null
|
||||
if (marks.length > 0) {
|
||||
const nodeUuidMark = this.editor.schema.marks[NAME]
|
||||
const activeNodeUuidMark = marks.find((mark) => mark.type === nodeUuidMark)
|
||||
|
||||
if (activeNodeUuidMark !== undefined && activeNodeUuidMark !== null) {
|
||||
this.storage.activeNodeUuid = activeNodeUuidMark.attrs[NAME]
|
||||
}
|
||||
if (this.storage.activeNodeUuid !== activeNodeUuid) {
|
||||
this.storage.activeNodeUuid = activeNodeUuid
|
||||
this.options.onNodeSelected?.(this.storage.activeNodeUuid)
|
||||
}
|
||||
|
||||
this.options.onNodeSelected?.(this.storage.activeNodeUuid)
|
||||
}
|
||||
})
|
||||
|
@ -27,6 +27,7 @@ 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 } from './plugin'
|
||||
export * from './types'
|
||||
|
||||
@ -41,5 +42,11 @@ export {
|
||||
NodeHighlightType
|
||||
} from './components/extension/nodeHighlight'
|
||||
export { NodeUuidCommands, NodeUuidExtension, NodeUuidOptions, NodeUuidStorage } from './components/extension/nodeUuid'
|
||||
export { InlinePopupExtension } from './components/extension/inlinePopup'
|
||||
export {
|
||||
InlineStyleToolbarExtension,
|
||||
InlineStyleToolbarOptions,
|
||||
InlineStyleToolbarStorage
|
||||
} from './components/extension/inlineStyleToolbar'
|
||||
|
||||
export { textEditorId }
|
||||
|
@ -73,8 +73,9 @@
|
||||
--highlight-red-hover: #ff967e;
|
||||
--highlight-red-press: #f96f50bd;
|
||||
|
||||
--text-editor-highlighted-node-info-background-color: #F2D7AE;
|
||||
--text-editor-highlighted-node-info-border-color: #DE9B35;
|
||||
--text-editor-highlighted-node-warning-active-background-color: #F2D7AE;
|
||||
--text-editor-highlighted-node-warning-background-color: #F8EBD7;
|
||||
--text-editor-highlighted-node-warning-border-color: #DE9B35;
|
||||
|
||||
--text-editor-highlighted-node-add-background-color: #DAEDDC;
|
||||
--text-editor-highlighted-node-add-font-color: #1C4220;
|
||||
|
@ -108,9 +108,13 @@
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.text-editor-highlighted-node-info {
|
||||
background-color: var(--text-editor-highlighted-node-info-background-color);
|
||||
border-bottom: 0.0625rem solid var(--text-editor-highlighted-node-info-border-color);
|
||||
.text-editor-highlighted-node-warning {
|
||||
background-color: var(--text-editor-highlighted-node-warning-background-color);
|
||||
border-bottom: 0.0625rem solid var(--text-editor-highlighted-node-warning-border-color);
|
||||
|
||||
&.text-editor-highlighted-node-selected, &:hover {
|
||||
background-color: var(--text-editor-highlighted-node-warning-active-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
.text-editor-highlighted-node-delete {
|
||||
|
Loading…
Reference in New Issue
Block a user