Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dotCMS/core#23977 Block editor freeze scroll on tippy menus show #24180

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 jupms whe the scroll is hidden */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo in the comment "jumps" @rjvelazco

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @manuelrojas!

scrollbar-gutter: stable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL!

}
.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 @@ -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 @@ -143,7 +143,7 @@ export class BubbleFormView extends BubbleMenuView {

this.tippy = tippy(editorElement.parentElement, {
...this.tippyOptions,
duration: 250,
duration: [250, 0],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rafa, these values can be a constant? const DURATION = 250.

Copy link
Contributor Author

@rjvelazco rjvelazco Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, @oidacra!

content: this.element,
interactive: true,
maxWidth: 'none',
Expand Down Expand Up @@ -186,7 +186,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)) {
oidacra marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

Expand All @@ -201,6 +201,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 @@ -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,7 +128,7 @@ export class BubbleLinkFormView {

this.tippy = tippy(editorElement.parentElement, {
...this.tippyOptions,
duration: 250,
duration: [250, 0],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as bubble form.

getReferenceClientRect: () => this.setTippyPosition(),
content: this.element,
interactive: true,
Expand All @@ -138,6 +143,9 @@ export class BubbleLinkFormView {
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';
2 changes: 1 addition & 1 deletion dotCMS/src/main/webapp/html/dotcms-block-editor.js

Large diffs are not rendered by default.