Skip to content

Commit

Permalink
#23977 Block editor freeze scroll on tippy menus show (#24180)
Browse files Browse the repository at this point in the history
* dev: freeze scroll on show forms #23977

* clean up

* refactor

* clean up v2

* feedback

* clean up
  • Loading branch information
rjvelazco committed Feb 24, 2023
1 parent feeb5c5 commit 1f40bf3
Show file tree
Hide file tree
Showing 16 changed files with 142 additions and 48 deletions.
Expand Up @@ -3,6 +3,7 @@
<tiptap-editor
[(ngModel)]="content"
[editor]="editor"
[ngClass]="{ 'overflow-hidden': freezeScroll }"
(keyup)="subject.next()"
></tiptap-editor>
</div>
Expand Down
Expand Up @@ -260,4 +260,12 @@
margin-bottom: $spacing-3;
}
}

tiptap-editor {
/* To avoid jumps in the content when the scroll is hidden */
scrollbar-gutter: stable;
}
.overflow-hidden {
overflow-y: hidden;
}
}
Expand Up @@ -39,7 +39,9 @@ import {
DragHandler,
DotFloatingButton,
BubbleAssetFormExtension,
ImageUpload
ImageUpload,
FreezeScroll,
FREEZE_SCROLL_KEY
} from '../../extensions';
import { ContentletBlock, ImageNode, VideoNode } from '../../nodes';
import {
Expand Down Expand Up @@ -94,6 +96,7 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy {

editor: Editor;
subject = new Subject();
freezeScroll = true;

private _allowedBlocks: string[] = ['paragraph']; //paragraph should be always.
private _customNodes: Map<string, AnyExtension> = new Map([
Expand Down Expand Up @@ -154,6 +157,10 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy {
this.editor.on('update', ({ editor }) => {
this.valueChange.emit(JSON.stringify(editor.getJSON()));
});

this.editor.on('transaction', ({ editor }) => {
this.freezeScroll = FREEZE_SCROLL_KEY.getState(editor.view.state)?.freezeScroll;
});
});
}

Expand Down Expand Up @@ -298,6 +305,7 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy {
BubbleAssetFormExtension(this.viewContainerRef),
DotTableHeaderExtension(),
TableRow,
FreezeScroll,
CharacterCount
];
}
Expand Down
Expand Up @@ -84,16 +84,13 @@ function execCommand({
range: Range;
props: SuggestionsCommandProps;
}) {
const { type, payload } = props;
const whatToDo = {
dotContent: () => {
editor
.chain()
.addContentletBlock({ range, payload: props.payload })
.addNextLine()
.run();
editor.chain().addContentletBlock({ range, payload }).addNextLine().run();
},
heading: () => {
editor.chain().addHeading({ range, type: props.type }).run();
editor.chain().addHeading({ range, type }).run();
},
table: () => {
editor.commands
Expand Down Expand Up @@ -165,8 +162,8 @@ function execCommand({
video: () => editor.commands.openAssetForm({ type: 'video' })
};

whatToDo[props.type.name]
? whatToDo[props.type.name]()
whatToDo[type.name]
? whatToDo[type.name]()
: editor.chain().setTextSelection(range).focus().run();
}

Expand Down Expand Up @@ -194,12 +191,14 @@ export const ActionsMenu = (viewContainerRef: ViewContainerRef) => {
open: false
});
editor.view.dispatch(transaction);
editor.commands.freezeScroll(false);
}
});
}
}

function onBeforeStart({ editor }): void {
editor.commands.freezeScroll(true);
const isTableCell =
findParentNode(editor.view.state.selection.$from, [NodeTypes.TABLE_CELL])?.type.name ===
NodeTypes.TABLE_CELL;
Expand Down Expand Up @@ -270,8 +269,9 @@ export const ActionsMenu = (viewContainerRef: ViewContainerRef) => {
return false;
}

function onExit() {
function onExit({ editor }): void {
myTippy?.destroy();
editor.commands.freezeScroll(false);
suggestionsComponent?.destroy();
suggestionsComponent = null;
destroy$.next(true);
Expand Down
Expand Up @@ -125,6 +125,7 @@ export const BubbleAssetFormExtension = (viewContainerRef: ViewContainerRef) =>

return true;
})
.freezeScroll(true)
.run();
},
closeAssetForm:
Expand All @@ -136,6 +137,7 @@ export const BubbleAssetFormExtension = (viewContainerRef: ViewContainerRef) =>

return true;
})
.freezeScroll(false)
.run();
},
insertAsset:
Expand Down
Expand Up @@ -5,7 +5,6 @@
(onSelect)="onSelectFile($event.files)"
chooseLabel="browse files"
mode="basic"
maxFileSize="1000000"
></p-fileUpload>

<ng-template #preview>
Expand Down
Expand Up @@ -68,6 +68,7 @@ export const BubbleFormExtension = (viewContainerRef: ViewContainerRef) => {

return true;
})
.freezeScroll(true)
.run();

return formValue$;
Expand All @@ -83,6 +84,7 @@ export const BubbleFormExtension = (viewContainerRef: ViewContainerRef) => {

return true;
})
.freezeScroll(false)
.run();
},
updateValue:
Expand Down
Expand Up @@ -11,6 +11,7 @@ import { takeUntil } from 'rxjs/operators';
import { Editor, posToDOMRect } from '@tiptap/core';
import { BubbleMenuView } from '@tiptap/extension-bubble-menu';

import { BASIC_TIPPY_OPTIONS } from '../../../shared';
import { getNodePosition } from '../../bubble-menu/utils';
import { BubbleFormComponent } from '../bubble-form.component';
import { BUBBLE_FORM_PLUGIN_KEY } from '../bubble-form.extension';
Expand Down Expand Up @@ -143,22 +144,8 @@ export class BubbleFormView extends BubbleMenuView {

this.tippy = tippy(editorElement.parentElement, {
...this.tippyOptions,
duration: 250,
...BASIC_TIPPY_OPTIONS,
content: this.element,
interactive: true,
maxWidth: 'none',
trigger: 'manual',
placement: 'bottom-start',
hideOnClick: 'toggle',
popperOptions: {
modifiers: [
{
name: 'flip',
options: { fallbackPlacements: ['top-start'] }
}
]
},

onShow: () => {
requestAnimationFrame(() => {
this.component.instance.inputs.first.nativeElement.focus();
Expand Down Expand Up @@ -186,7 +173,7 @@ export class BubbleFormView extends BubbleMenuView {
}

private hanlderScroll(e: Event) {
if (this.tippy?.popper && this.tippy?.popper.contains(e.target as HTMLElement)) {
if (!this.shouldHideOnScroll(e.target as HTMLElement)) {
return true;
}

Expand All @@ -201,6 +188,10 @@ export class BubbleFormView extends BubbleMenuView {

return domRect || getNodePosition(node, type);
}

private shouldHideOnScroll(node: HTMLElement): boolean {
return this.tippy?.state.isMounted && this.tippy?.popper.contains(node);
}
}

export const bubbleFormPlugin = (options: BubbleFormProps) => {
Expand Down
Expand Up @@ -50,6 +50,7 @@ export const BubbleLinkFormExtension = (viewContainerRef: ViewContainerRef) => {

return true;
})
.freezeScroll(true)
.run();
},
closeLinkForm:
Expand All @@ -66,6 +67,7 @@ export const BubbleLinkFormExtension = (viewContainerRef: ViewContainerRef) => {

return true;
})
.freezeScroll(false)
.run();
}
};
Expand Down
Expand Up @@ -11,7 +11,7 @@ import { takeUntil } from 'rxjs/operators';
import { Editor, posToDOMRect } from '@tiptap/core';

import { ImageNode } from '../../../nodes';
import { getPosAtDocCoords } from '../../../shared';
import { BASIC_TIPPY_OPTIONS, getPosAtDocCoords } from '../../../shared';
import { isValidURL } from '../../bubble-menu/utils';
import { BubbleLinkFormComponent, NodeProps } from '../bubble-link-form.component';
import { LINK_FORM_PLUGIN_KEY } from '../bubble-link-form.extension';
Expand Down Expand Up @@ -104,6 +104,11 @@ export class BubbleLinkFormView {
const { state } = this.editor;
const { to } = state.selection;
const { openOnClick } = LINK_FORM_PLUGIN_KEY.getState(state);
const pluginState = this.pluginKey.getState(state);

if (!pluginState.isOpen) {
return;
}

if (openOnClick) {
this.editor.commands.closeLinkForm();
Expand All @@ -123,21 +128,11 @@ export class BubbleLinkFormView {

this.tippy = tippy(editorElement.parentElement, {
...this.tippyOptions,
duration: 250,
...BASIC_TIPPY_OPTIONS,
getReferenceClientRect: () => this.setTippyPosition(),
content: this.element,
interactive: true,
maxWidth: 'none',
trigger: 'manual',
placement: 'bottom-start',
hideOnClick: 'toggle',
popperOptions: {
modifiers: [
{
name: 'flip',
options: { fallbackPlacements: ['top-start'] }
}
]
onHide: () => {
this.editor.commands.closeLinkForm();
}
});
}
Expand Down
Expand Up @@ -498,9 +498,11 @@ export class DotBubbleMenuPluginView extends BubbleMenuView {
this.editor.storage.bubbleMenu.changeToIsOpen = false;
this.changeTo.instance.items = [];
this.changeTo.changeDetectorRef.detectChanges();
this.editor.commands.freezeScroll(false);
},
onShow: () => {
this.editor.storage.bubbleMenu.changeToIsOpen = true;
this.editor.commands.freezeScroll(true);
this.updateChangeTo();
}
});
Expand Down
Expand Up @@ -62,9 +62,10 @@ export const DotTableCellPlugin = (options) => {
// eslint-disable-next-line
apply: () => {},
init: () => {
const component = options.viewContainerRef.createComponent(SuggestionsComponent);
const { editor, viewContainerRef } = options;
const component = viewContainerRef.createComponent(SuggestionsComponent);
const element = component.location.nativeElement;
component.instance.currentLanguage = options.editor.storage.dotConfig.lang;
component.instance.currentLanguage = editor.storage.dotConfig.lang;

const defaultTippyOptions: Partial<Props> = {
duration: 500,
Expand All @@ -74,7 +75,7 @@ export const DotTableCellPlugin = (options) => {
interactive: true
};

const { element: editorElement } = options.editor.options;
const { element: editorElement } = editor.options;
tippyCellOptions = tippy(editorElement, {
...defaultTippyOptions,
appendTo: document.body,
Expand All @@ -87,19 +88,21 @@ export const DotTableCellPlugin = (options) => {
modifiers: popperModifiers
},
onShow: () => {
editor.commands.freezeScroll(true);
const mergeCellsOption = component.instance.items.find(
(item) => item.id == 'mergeCells'
);
const splitCellsOption = component.instance.items.find(
(item) => item.id == 'splitCells'
);

mergeCellsOption.disabled = !options.editor.can().mergeCells();
splitCellsOption.disabled = !options.editor.can().splitCell();
mergeCellsOption.disabled = !editor.can().mergeCells();
splitCellsOption.disabled = !editor.can().splitCell();
setTimeout(() => {
component.changeDetectorRef.detectChanges();
});
}
},
onHide: () => editor.commands.freezeScroll(false)
});

component.instance.items = getCellsOptions(options.editor, tippyCellOptions);
Expand Down
@@ -0,0 +1,61 @@
import { PluginKey, Plugin, Transaction, EditorState } from 'prosemirror-state';

import { Extension } from '@tiptap/core';

export const FREEZE_SCROLL_KEY = new PluginKey('freeze-scroll');

declare module '@tiptap/core' {
interface Commands<ReturnType> {
FreezeScroll: {
freezeScroll: (value: boolean) => ReturnType;
};
}
}

interface PluginState {
freezeScroll: boolean;
}

export const FreezeScroll = Extension.create({
addCommands() {
return {
freezeScroll:
(value) =>
({ chain }) => {
return chain()
.command(({ tr }) => {
tr.setMeta(FREEZE_SCROLL_KEY, { freezeScroll: value });

return true;
})
.run();
}
};
},

addProseMirrorPlugins() {
return [FreezeScrollPlugin];
}
});

export const FreezeScrollPlugin = new Plugin({
key: FREEZE_SCROLL_KEY,
state: {
init(): PluginState {
return {
freezeScroll: false
};
},
apply(transaction: Transaction, value: PluginState, oldState: EditorState): PluginState {
const { freezeScroll } = transaction.getMeta(FREEZE_SCROLL_KEY) || {};
const state = FREEZE_SCROLL_KEY?.getState(oldState);

if (typeof freezeScroll === 'boolean') {
return { freezeScroll };
}

// keep the old state in case we do not receive a new one.
return state || value;
}
}
});
1 change: 1 addition & 0 deletions core-web/libs/block-editor/src/lib/extensions/index.ts
Expand Up @@ -38,3 +38,4 @@ export * from './image-uploader/image-uploader.extension';
export * from './image-uploader/services/dot-image/dot-image.service';

export * from './asset-form/asset-form.component';
export * from './freeze-scroll/freeze-scroll.extension';

0 comments on commit 1f40bf3

Please sign in to comment.