Follow-up from "Add a dropdown to switch language in code blocks"
The following discussion from !69131 (merged) should be addressed:
-
@pslaughter started a discussion: (+1 comment) suggestion: Overwriting
props
in anonBeforeUpdate
without callingsetProps
, is the React equivalent of mutating props within a Vue component. Is there a different way we can establish the position of this?How about using
popperOptions
and popper modifiers?Here's a patch which attempts to do this:
diff --git a/app/assets/javascripts/content_editor/components/code_block_bubble_menu.vue b/app/assets/javascripts/content_editor/components/code_block_bubble_menu.vue index 9d42d63862f..9525b505ce4 100644 --- a/app/assets/javascripts/content_editor/components/code_block_bubble_menu.vue +++ b/app/assets/javascripts/content_editor/components/code_block_bubble_menu.vue @@ -40,6 +40,34 @@ export default { languageLoader: new CodeBlockLanguageLoader(lowlight), }; }, + computed: { + tippyOptions() { + return { + popperOptions: { + modifiers: [ + { + name: 'topLogger', + enabled: true, + phase: 'beforeWrite', + fn: ({ state }) => { + const { top, left, width } = this.getOffset(); + const { width: popperWidth } = state.elements.popper.getBoundingClientRect(); + + const newTop = top - 40; + const newLeft = left + (width / 2) - (popperWidth / 2); + + state.styles.popper.transform = ''; + state.styles.popper.top = `${newTop}px`; + state.styles.popper.left = `${newLeft}px`; + + return state; + }, + } + ] + } + } + }, + }, methods: { shouldShow: ({ editor }) => { return CODE_BLOCK_NODE_TYPES.some((type) => editor.isActive(type)); @@ -98,6 +126,29 @@ export default { } }, + getOffset() { + const { view } = this.tiptapEditor; + const { from } = this.tiptapEditor.state.selection; + + for (let { node } = view.domAtPos(from); node; node = node.parentElement) { + if (node.nodeName?.toLowerCase() === 'pre') { + return { + left: node.offsetLeft, + top: node.offsetTop, + width: node.offsetWidth, + height: node.offsetHeight, + } + } + } + + return { + left: -1000, + top: -1000, + width: 0, + height: 0, + }; + }, + deleteCodeBlock() { this.tiptapEditor.chain().focus().deleteNode(this.getCodeBlockType()).run(); }, @@ -115,7 +166,7 @@ export default { :editor="tiptapEditor" plugin-key="bubbleMenuCodeBlock" :should-show="shouldShow" - :tippy-options="{ onBeforeUpdate: tippyOnBeforeUpdate }" + :tippy-options="tippyOptions" > <editor-state-observer @transaction="getSelectedLanguage"> <gl-button-group> diff --git a/app/assets/javascripts/content_editor/services/code_block_language_loader.js b/app/assets/javascripts/content_editor/services/code_block_language_loader.js index 3c12cf614a5..bcac554bcfd 100644 --- a/app/assets/javascripts/content_editor/services/code_block_language_loader.js +++ b/app/assets/javascripts/content_editor/services/code_block_language_loader.js @@ -1,3 +1,6 @@ +import { lowlight as lowlightDefault } from 'lowlight/lib/core'; +import { memoize } from 'lodash'; + export default class CodeBlockLanguageLoader { constructor(lowlight) { this.lowlight = lowlight; @@ -33,3 +36,5 @@ export default class CodeBlockLanguageLoader { return Promise.all(loaders); } } + +export const getDefaultLanguageLoader = memoize(() => new CodeBlockLanguageLoader(lowlightDefault));