Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 26 additions & 25 deletions packages/text-editor/src/components/CollaboratorEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -90,7 +89,6 @@

let editor: Editor
let inlineToolbar: HTMLElement
let showInlineToolbar = false

let placeHolderStr: string = ''

Expand Down Expand Up @@ -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
})
Expand All @@ -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
Expand Down Expand Up @@ -201,6 +214,7 @@
})

$: updateEditor(editor, field, comparedVersion)
$: if (editor) dispatch('editor', editor)

onMount(() => {
ph.then(() => {
Expand All @@ -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,
Expand Down Expand Up @@ -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
}
})

Expand All @@ -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>
Expand Down Expand Up @@ -336,15 +337,15 @@
needFocus = true
}}
on:action={(event) => {
dispatch('action', { action: event.detail, editor })
dispatch('action', event.detail)
needFocus = true
}}
/>
</div>

<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}
Expand Down
21 changes: 5 additions & 16 deletions packages/text-editor/src/components/TextEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ''
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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: {
Expand All @@ -160,12 +159,8 @@
},
onUpdate: () => {
content = editor.getHTML()
showContextMenu = false
dispatch('value', content)
dispatch('update', content)
},
onSelectionUpdate: () => {
showContextMenu = false
}
})
})
Expand All @@ -177,12 +172,6 @@
}
})

function onEditorClick () {
if (!editor.isEmpty) {
showContextMenu = true
}
}

/**
* @public
*/
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
16 changes: 16 additions & 0 deletions packages/text-editor/src/components/extension/inlinePopup.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
}
})
99 changes: 73 additions & 26 deletions packages/text-editor/src/components/extension/inlineStyleToolbar.ts
Original file line number Diff line number Diff line change
@@ -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()
// 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
if (editorElement === null || editorElement === undefined) {
if (!this.options.isSupported()) {
return false
}

if (!editor.isEditable) {
if (editor.isDestroyed || !editor.isEditable) {
return false
}

const isShown = options.isShown?.() ?? false
if (isShown) {
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
}

// 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
Expand All @@ -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
}
})
Loading