diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/bubble_menu.vue
index 4d123cfb2b0f103251f4ef78375d7c5a402562b5..9afc2e449d5a99fce3daadb37a2874a93eec5c6d 100644
--- a/app/assets/javascripts/content_editor/components/bubble_menus/bubble_menu.vue
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/bubble_menu.vue
@@ -34,7 +34,6 @@ export default {
element: this.$el,
shouldShow: this.shouldShow,
tippyOptions: {
- ...this.tippyOptions,
onShow: (...args) => {
this.$emit('show', ...args);
this.menuVisible = true;
@@ -47,6 +46,7 @@ export default {
strategy: 'fixed',
},
maxWidth: '400px',
+ ...this.tippyOptions,
},
}),
);
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/glql_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/glql_bubble_menu.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4f3496331f126df78073e16152cc4b97d5fa79f7
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/glql_bubble_menu.vue
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+ -
+
+
+ {{ field.label }}
+
+
+
+
+
+ -
+
+
+ -
+
+ {{ __('Search') }}
+
+ -
+
+
+
+
+ {{ item.name }}
+ {{ item.username }}
+
+
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue
index 29f5adb7df6b217f0e969ceb84edd3f193e8c1d0..542fbbb6d94b8bf70dcc38cec251439694426c51 100644
--- a/app/assets/javascripts/content_editor/components/content_editor.vue
+++ b/app/assets/javascripts/content_editor/components/content_editor.vue
@@ -20,6 +20,7 @@ import LinkBubbleMenu from './bubble_menus/link_bubble_menu.vue';
import MediaBubbleMenu from './bubble_menus/media_bubble_menu.vue';
import ReferenceBubbleMenu from './bubble_menus/reference_bubble_menu.vue';
import TableBubbleMenu from './bubble_menus/table_bubble_menu.vue';
+import GlqlBubbleMenu from './bubble_menus/glql_bubble_menu.vue';
import FormattingToolbar from './formatting_toolbar.vue';
export default {
@@ -37,6 +38,7 @@ export default {
EditorStateObserver,
ReferenceBubbleMenu,
TableBubbleMenu,
+ GlqlBubbleMenu,
EditorModeSwitcher,
},
directives: {
@@ -302,6 +304,7 @@ export default {
+
+import { NodeViewWrapper } from '@tiptap/vue-2';
+import GlqlFacade from '~/glql/components/common/facade.vue';
+
+export default {
+ name: 'GlqlViewWrapper',
+ components: {
+ NodeViewWrapper,
+ GlqlFacade,
+ },
+ props: {
+ node: {
+ type: Object,
+ required: true,
+ },
+ selected: {
+ type: Boolean,
+ required: true,
+ },
+ },
+};
+
+
+
+
+
+
diff --git a/app/assets/javascripts/content_editor/extensions/glql/view.js b/app/assets/javascripts/content_editor/extensions/glql/view.js
new file mode 100644
index 0000000000000000000000000000000000000000..4dfc2b2f845af4951dec8541ddbdd31198a5abc9
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/glql/view.js
@@ -0,0 +1,42 @@
+import { Node } from '@tiptap/core';
+import { VueNodeViewRenderer } from '@tiptap/vue-2';
+import { parseYAML } from '~/glql/core/parser';
+import { PARSE_HTML_PRIORITY_HIGHEST } from '../../constants';
+import GlqlViewWrapper from '../../components/wrappers/glql/view.vue';
+
+export default Node.create({
+ name: 'glqlView',
+ group: 'block',
+ atom: true,
+ marks: '',
+
+ addAttributes() {
+ return {
+ queryYaml: { default: '' },
+ query: { default: '' },
+ config: { default: {} },
+ };
+ },
+
+ parseHTML() {
+ return [
+ {
+ priority: PARSE_HTML_PRIORITY_HIGHEST,
+ tag: 'pre[data-canonical-lang="glql"]',
+ getAttrs: (el) => ({
+ queryYaml: el.textContent,
+ ...parseYAML(el.textContent),
+ }),
+ },
+ { tag: 'glql-view' },
+ ];
+ },
+
+ renderHTML() {
+ return ['div', {}, 0];
+ },
+
+ addNodeView() {
+ return VueNodeViewRenderer(GlqlViewWrapper);
+ },
+});
diff --git a/app/assets/javascripts/content_editor/extensions/index.js b/app/assets/javascripts/content_editor/extensions/index.js
index 453ef011e32bbd6ddb1636c56a4890a9c93b3be5..f59018f8b8f171cf934c9ffd9f265b4864ac3d57 100644
--- a/app/assets/javascripts/content_editor/extensions/index.js
+++ b/app/assets/javascripts/content_editor/extensions/index.js
@@ -65,3 +65,5 @@ export { default as Text } from './text';
export { default as Time } from './time';
export { default as Video } from './video';
export { default as WordBreak } from './word_break';
+
+export { default as GlqlView } from './glql/view';
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index 9de79d050237f1ad3f247f1107c7a9ebbf6b70b4..b0a1921f56037d65bd89921cfbb45995c82725fb 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -54,6 +54,7 @@ import tableRow from './serializer/table_row';
import table from './serializer/table';
import time from './serializer/time';
import htmlNode from './serializer/html_node';
+import glqlView from './serializer/glql/view';
const LIST_TYPES = [
extensions.BulletList.name,
@@ -120,6 +121,8 @@ const defaultSerializerConfig = {
[extensions.Video.name]: video,
[extensions.WordBreak.name]: wordBreak,
...extensions.HTMLNodes.reduce((acc, { name }) => ({ ...acc, [name]: htmlNode(name) }), {}),
+
+ [extensions.GlqlView.name]: glqlView,
},
};
diff --git a/app/assets/javascripts/content_editor/services/serializer/glql/view.js b/app/assets/javascripts/content_editor/services/serializer/glql/view.js
new file mode 100644
index 0000000000000000000000000000000000000000..72bcbdcdd7618e40852f067c0e6484d5548c92ba
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/serializer/glql/view.js
@@ -0,0 +1,11 @@
+const glqlView = (state, node) => {
+ if (state.options.skipEmptyNodes && !node.childCount) return;
+
+ state.write(`\`\`\`glql\n`);
+ state.text(node.attrs.query, false);
+ state.ensureNewLine();
+ state.write('```');
+ state.closeBlock(node);
+};
+
+export default glqlView;
diff --git a/app/assets/javascripts/glql/components/common/facade.vue b/app/assets/javascripts/glql/components/common/facade.vue
index a0417322bc77c77b66bffc4cd2771ad3417fc2f9..2ac8a7838a0d71740a6f0ebab0a393e1e771928e 100644
--- a/app/assets/javascripts/glql/components/common/facade.vue
+++ b/app/assets/javascripts/glql/components/common/facade.vue
@@ -47,7 +47,11 @@ export default {
SafeHtml,
},
mixins: [InternalEvents.mixin(), glFeatureFlagsMixin()],
- inject: ['queryKey'],
+ inject: {
+ queryKey: {
+ default: '',
+ },
+ },
props: {
query: {
required: true,
@@ -120,7 +124,7 @@ export default {
async mounted() {
this.loadOnClick = this.glFeatures.glqlLoadOnClick;
- this.eventHub.$on('loadMore', this.loadMore.bind(this));
+ this.eventHub?.$on('loadMore', this.loadMore.bind(this));
},
methods: {
diff --git a/app/assets/javascripts/glql/components/common/pagination.vue b/app/assets/javascripts/glql/components/common/pagination.vue
index 9452c8fdd32b530ca19fb4b7c1315f83e587eb0f..8c44bfad097979a1c32fef8446422f11ac58fcf3 100644
--- a/app/assets/javascripts/glql/components/common/pagination.vue
+++ b/app/assets/javascripts/glql/components/common/pagination.vue
@@ -9,7 +9,11 @@ export default {
components: {
GlButton,
},
- inject: ['queryKey'],
+ inject: {
+ queryKey: {
+ default: '',
+ },
+ },
props: {
count: {
type: Number,
@@ -47,21 +51,21 @@ export default {
},
mounted() {
- this.eventHub.$on('loadMore', () => {
+ this.eventHub?.$on('loadMore', () => {
this.isLoadingMore = true;
});
- this.eventHub.$on('loadMoreComplete', () => {
+ this.eventHub?.$on('loadMoreComplete', () => {
this.isLoadingMore = false;
});
- this.eventHub.$on('loadMoreError', () => {
+ this.eventHub?.$on('loadMoreError', () => {
this.isLoadingMore = false;
});
},
methods: {
loadMore() {
- this.eventHub.$emit('loadMore', this.actualPageSize);
+ this.eventHub?.$emit('loadMore', this.actualPageSize);
},
},
};
@@ -76,6 +80,7 @@ export default {
variant="default"
:aria-label="loadMoreLabel"
:loading="isLoadingMore"
+ :disabled="!eventHub"
@click="loadMore"
>
{{ loadMoreLabel }}
diff --git a/app/assets/javascripts/glql/components/presenters/list.vue b/app/assets/javascripts/glql/components/presenters/list.vue
index 1e1d135beb96fa56946b30732d9a1e46d7f6303f..4abf5c356bed0dcb3aacc76bdcee1c5a82766fd7 100644
--- a/app/assets/javascripts/glql/components/presenters/list.vue
+++ b/app/assets/javascripts/glql/components/presenters/list.vue
@@ -11,7 +11,14 @@ export default {
GlSprintf,
GlSkeletonLoader,
},
- inject: ['presenter', 'queryKey'],
+ inject: {
+ presenter: {
+ default: null,
+ },
+ queryKey: {
+ default: '',
+ },
+ },
props: {
data: {
required: true,
@@ -51,16 +58,16 @@ export default {
},
},
mounted() {
- this.eventHub.$on('loadMore', (pageSize) => {
+ this.eventHub?.$on('loadMore', (pageSize) => {
this.pageSize = pageSize;
this.isLoadingMore = true;
});
- this.eventHub.$on('loadMoreComplete', () => {
+ this.eventHub?.$on('loadMoreComplete', () => {
this.isLoadingMore = false;
});
- this.eventHub.$on('loadMoreError', () => {
+ this.eventHub?.$on('loadMoreError', () => {
this.isLoadingMore = false;
});
},
diff --git a/app/assets/javascripts/glql/components/presenters/table.vue b/app/assets/javascripts/glql/components/presenters/table.vue
index b4ae5b3c507c8a493c28cbe678bedaf3e7e29b5b..4dc76afab7594ea4e3a3aaa18f639a12ffaa1e83 100644
--- a/app/assets/javascripts/glql/components/presenters/table.vue
+++ b/app/assets/javascripts/glql/components/presenters/table.vue
@@ -13,7 +13,14 @@ export default {
GlSkeletonLoader,
ThResizable,
},
- inject: ['presenter', 'queryKey'],
+ inject: {
+ presenter: {
+ default: null,
+ },
+ queryKey: {
+ default: '',
+ },
+ },
props: {
data: {
required: true,
@@ -44,18 +51,18 @@ export default {
};
},
mounted() {
- this.eventHub.$on('loadMore', (pageSize) => {
+ this.eventHub?.$on('loadMore', (pageSize) => {
this.pageSize = pageSize;
this.isLoadingMore = true;
});
- this.eventHub.$on('loadMoreComplete', (newData) => {
+ this.eventHub?.$on('loadMoreComplete', (newData) => {
this.items = newData.nodes.slice();
this.sorter = this.sorter.clone(this.items);
this.isLoadingMore = false;
});
- this.eventHub.$on('loadMoreError', () => {
+ this.eventHub?.$on('loadMoreError', () => {
this.isLoadingMore = false;
});
},
diff --git a/app/assets/javascripts/glql/utils/event_hub_factory.js b/app/assets/javascripts/glql/utils/event_hub_factory.js
index dc573a059604d661a46786e368667283ab469bfc..04ec6b0722a926243e568010f7bda2d2bbe72e1a 100644
--- a/app/assets/javascripts/glql/utils/event_hub_factory.js
+++ b/app/assets/javascripts/glql/utils/event_hub_factory.js
@@ -3,6 +3,8 @@ import createEventHub from '~/helpers/event_hub_factory';
const eventHubs = {};
export const eventHubByKey = (key) => {
+ if (!key) return null;
+
eventHubs[key] = eventHubs[key] || createEventHub();
return eventHubs[key];
};
diff --git a/app/assets/stylesheets/components/content_editor.scss b/app/assets/stylesheets/components/content_editor.scss
index 5b7142d5694f8267a87424932af9a292f2760a0a..d6def65666d0e055bd241114b9d0d4b923cc5699 100644
--- a/app/assets/stylesheets/components/content_editor.scss
+++ b/app/assets/stylesheets/components/content_editor.scss
@@ -326,6 +326,10 @@
@apply gl-mb-0;
}
}
+
+ .glql-view {
+ --gl-focus-ring-outer-color: var(--blue-200);
+ }
}
// Fixes a problem with the layout shifting
@@ -406,3 +410,26 @@
min-width: 320px;
}
+.glql-bubble-menu {
+ --gl-control-border-color-default: transparent;
+ --gl-control-border-color-hover: transparent;
+ --gl-control-border-color-focus: transparent;
+
+ --gl-focus-ring-inner-color: transparent;
+ --gl-focus-ring-outer-color: transparent;
+}
+
+.glql-filtered-search {
+ .gl-filtered-search-scrollable {
+ @apply gl-flex-wrap gl-gap-2;
+ }
+
+ .gl-filtered-search-scrollable-container {
+ @apply gl-px-0;
+ }
+
+ .gl-filtered-search-item {
+ @apply gl-p-0;
+ }
+}
+