Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-13 18:07:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-13 18:07:43 +0300
commit7eca3f56625526ffa7f263c1fef0fcea34de8ca6 (patch)
treefec87c2a902e3c44f89963f4b28e6de32c0806f3 /app/assets/javascripts/editor
parent988424215cf104d9ee24bb1751141424cffb32d1 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/editor')
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar.vue6
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar_button.vue74
-rw-r--r--app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js53
-rw-r--r--app/assets/javascripts/editor/constants.js5
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js108
-rw-r--r--app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js98
-rw-r--r--app/assets/javascripts/editor/graphql/add_items.mutation.graphql3
-rw-r--r--app/assets/javascripts/editor/graphql/get_item.query.graphql9
-rw-r--r--app/assets/javascripts/editor/graphql/remove_items.mutation.graphql3
-rw-r--r--app/assets/javascripts/editor/graphql/typedefs.graphql23
-rw-r--r--app/assets/javascripts/editor/graphql/update_item.mutation.graphql2
11 files changed, 281 insertions, 103 deletions
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar.vue b/app/assets/javascripts/editor/components/source_editor_toolbar.vue
index 1427f2df461..2c177634bbe 100644
--- a/app/assets/javascripts/editor/components/source_editor_toolbar.vue
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar.vue
@@ -55,8 +55,8 @@ export default {
id="se-toolbar"
class="gl-py-3 gl-px-5 gl-bg-white gl-border-t gl-border-b gl-display-flex gl-justify-content-space-between gl-align-items-center"
>
- <template v-for="group in $options.groups">
- <gl-button-group v-if="hasGroupItems(group)" :key="group">
+ <div v-for="group in $options.groups" :key="group">
+ <gl-button-group v-if="hasGroupItems(group)">
<template v-for="item in getGroupItems(group)">
<source-editor-toolbar-button
:key="item.id"
@@ -65,6 +65,6 @@ export default {
/>
</template>
</gl-button-group>
- </template>
+ </div>
</section>
</template>
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue b/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
index 2595d67af34..194b482c12e 100644
--- a/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar_button.vue
@@ -1,7 +1,5 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import updateToolbarItemMutation from '~/editor/graphql/update_item.mutation.graphql';
-import getToolbarItemQuery from '~/editor/graphql/get_item.query.graphql';
export default {
name: 'SourceEditorToolbarButton',
@@ -20,70 +18,40 @@ export default {
},
},
},
- data() {
- return {
- buttonItem: this.button,
- };
- },
- apollo: {
- buttonItem: {
- query: getToolbarItemQuery,
- variables() {
- return {
- id: this.button.id,
- };
- },
- update({ item }) {
- return item;
- },
- skip() {
- return !this.button.id;
- },
- },
- },
computed: {
icon() {
- return this.buttonItem.selected
- ? this.buttonItem.selectedIcon || this.buttonItem.icon
- : this.buttonItem.icon;
+ return this.button.selected ? this.button.selectedIcon || this.button.icon : this.button.icon;
},
label() {
- return this.buttonItem.selected
- ? this.buttonItem.selectedLabel || this.buttonItem.label
- : this.buttonItem.label;
+ return this.button.selected
+ ? this.button.selectedLabel || this.button.label
+ : this.button.label;
+ },
+ showButton() {
+ return Object.entries(this.button).length > 0;
},
},
methods: {
clickHandler() {
- if (this.buttonItem.onClick) {
- this.buttonItem.onClick();
+ if (this.button.onClick) {
+ this.button.onClick();
}
- this.$apollo.mutate({
- mutation: updateToolbarItemMutation,
- variables: {
- id: this.buttonItem.id,
- propsToUpdate: {
- selected: !this.buttonItem.selected,
- },
- },
- });
this.$emit('click');
},
},
};
</script>
<template>
- <div>
- <gl-button
- v-gl-tooltip.hover
- :category="buttonItem.category"
- :variant="buttonItem.variant"
- type="button"
- :selected="buttonItem.selected"
- :icon="icon"
- :title="label"
- :aria-label="label"
- @click="clickHandler"
- />
- </div>
+ <gl-button
+ v-if="showButton"
+ v-gl-tooltip.hover
+ :category="button.category"
+ :variant="button.variant"
+ type="button"
+ :selected="button.selected"
+ :icon="icon"
+ :title="label"
+ :aria-label="label"
+ @click="clickHandler"
+ />
</template>
diff --git a/app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js b/app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js
new file mode 100644
index 00000000000..603ba26f22e
--- /dev/null
+++ b/app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js
@@ -0,0 +1,53 @@
+import produce from 'immer';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import typeDefs from '~/editor/graphql/typedefs.graphql';
+import getToolbarItemsQuery from '~/editor/graphql/get_items.query.graphql';
+import createDefaultClient from '~/lib/graphql';
+
+Vue.use(VueApollo);
+
+const resolvers = {
+ Mutation: {
+ addToolbarItems: (_, { items = [] }, { cache }) => {
+ const itemsSourceData = cache.readQuery({ query: getToolbarItemsQuery });
+ const data = produce(itemsSourceData, (draftData) => {
+ const existingNodes = draftData?.items?.nodes || [];
+ draftData.items = {
+ nodes: Array.isArray(items) ? [...existingNodes, ...items] : [...existingNodes, items],
+ };
+ });
+ cache.writeQuery({ query: getToolbarItemsQuery, data });
+ },
+
+ removeToolbarItems: (_, { ids }, { cache }) => {
+ const sourceData = cache.readQuery({ query: getToolbarItemsQuery });
+ const {
+ items: { nodes },
+ } = sourceData;
+ const data = produce(sourceData, (draftData) => {
+ draftData.items.nodes = nodes.filter((item) => !ids.includes(item.id));
+ });
+ cache.writeQuery({ query: getToolbarItemsQuery, data });
+ },
+
+ updateToolbarItem: (_, { id, propsToUpdate }, { cache }) => {
+ const itemSourceData = cache.readQuery({ query: getToolbarItemsQuery });
+ const data = produce(itemSourceData, (draftData) => {
+ const existingNodes = draftData?.items?.nodes || [];
+ draftData.items = {
+ nodes: existingNodes.map((item) => {
+ return item.id === id ? { ...item, ...propsToUpdate } : item;
+ }),
+ };
+ });
+ cache.writeQuery({ query: getToolbarItemsQuery, data });
+ },
+ },
+};
+
+const defaultClient = createDefaultClient(resolvers, { typeDefs });
+
+export const apolloProvider = new VueApollo({
+ defaultClient,
+});
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index 361122d8890..83cfdd25757 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -1,5 +1,5 @@
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
export const URI_PREFIX = 'gitlab';
export const CONTENT_UPDATE_DEBOUNCE = DEFAULT_DEBOUNCE_AND_THROTTLE_MS;
@@ -57,5 +57,8 @@ export const EXTENSION_CI_SCHEMA_FILE_NAME_MATCH = '.gitlab-ci.yml';
export const EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS = 'md';
export const EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS = 'source-editor-preview';
export const EXTENSION_MARKDOWN_PREVIEW_ACTION_ID = 'markdown-preview';
+export const EXTENSION_MARKDOWN_PREVIEW_HIDE_ACTION_ID = 'markdown-preview-hide';
export const EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH = 0.5; // 50% of the width
export const EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY = 250; // ms
+export const EXTENSION_MARKDOWN_PREVIEW_LABEL = __('Preview Markdown');
+export const EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL = __('Hide Live Preview');
diff --git a/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js b/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
index 9d53268c340..11cc85c659d 100644
--- a/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
+++ b/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js
@@ -3,14 +3,17 @@ import { BLOB_PREVIEW_ERROR } from '~/blob_edit/constants';
import createFlash from '~/flash';
import { sanitize } from '~/lib/dompurify';
import axios from '~/lib/utils/axios_utils';
-import { __ } from '~/locale';
import syntaxHighlight from '~/syntax_highlight';
import {
EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS,
EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ EXTENSION_MARKDOWN_PREVIEW_HIDE_ACTION_ID,
EXTENSION_MARKDOWN_PREVIEW_PANEL_WIDTH,
EXTENSION_MARKDOWN_PREVIEW_PANEL_PARENT_CLASS,
EXTENSION_MARKDOWN_PREVIEW_UPDATE_DELAY,
+ EXTENSION_MARKDOWN_PREVIEW_LABEL,
+ EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL,
+ EDITOR_TOOLBAR_RIGHT_GROUP,
} from '../constants';
const fetchPreview = (text, previewMarkdownPath) => {
@@ -41,31 +44,58 @@ export class EditorMarkdownPreviewExtension {
onSetup(instance, setupOptions) {
this.preview = {
el: undefined,
- action: undefined,
+ actions: {
+ preview: undefined,
+ hide: undefined,
+ },
shown: false,
modelChangeListener: undefined,
path: setupOptions.previewMarkdownPath,
+ actionShowPreviewCondition: instance.createContextKey('toggleLivePreview', true),
};
+ this.toolbarButtons = [];
+
this.setupPreviewAction(instance);
+ if (instance.toolbar) {
+ this.setupToolbar(instance);
+ }
+ }
- instance.getModel().onDidChangeLanguage(({ newLanguage, oldLanguage } = {}) => {
- if (newLanguage === 'markdown' && oldLanguage !== newLanguage) {
- instance.setupPreviewAction();
- } else {
- instance.cleanup();
- }
- });
+ onBeforeUnuse(instance) {
+ this.cleanup(instance);
+ const ids = this.toolbarButtons.map((item) => item.id);
+ if (instance.toolbar) {
+ instance.toolbar.removeItems(ids);
+ }
+ }
- instance.onDidChangeModel(() => {
- const model = instance.getModel();
- if (model) {
- const { language } = model.getLanguageIdentifier();
- instance.cleanup();
- if (language === 'markdown') {
- instance.setupPreviewAction();
- }
- }
- });
+ cleanup(instance) {
+ if (this.preview.modelChangeListener) {
+ this.preview.modelChangeListener.dispose();
+ }
+ this.preview.actions.preview.dispose();
+ this.preview.actions.hide.dispose();
+ if (this.preview.shown) {
+ this.togglePreviewPanel(instance);
+ this.togglePreviewLayout(instance);
+ }
+ this.preview.shown = false;
+ }
+
+ setupToolbar(instance) {
+ this.toolbarButtons = [
+ {
+ id: EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ label: EXTENSION_MARKDOWN_PREVIEW_LABEL,
+ icon: 'live-preview',
+ selected: false,
+ group: EDITOR_TOOLBAR_RIGHT_GROUP,
+ category: 'primary',
+ selectedLabel: EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL,
+ onClick: () => instance.togglePreview(),
+ },
+ ];
+ instance.toolbar.addItems(this.toolbarButtons);
}
togglePreviewLayout(instance) {
@@ -103,22 +133,33 @@ export class EditorMarkdownPreviewExtension {
setupPreviewAction(instance) {
if (instance.getAction(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID)) return;
-
- this.preview.action = instance.addAction({
- id: EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
- label: __('Preview Markdown'),
+ const actionBasis = {
keybindings: [
// eslint-disable-next-line no-bitwise,no-undef
monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_P),
],
contextMenuGroupId: 'navigation',
contextMenuOrder: 1.5,
-
// Method that will be executed when the action is triggered.
// @param ed The editor instance is passed in as a convenience
run(inst) {
inst.togglePreview();
},
+ };
+
+ this.preview.actions.preview = instance.addAction({
+ ...actionBasis,
+ id: EXTENSION_MARKDOWN_PREVIEW_ACTION_ID,
+ label: EXTENSION_MARKDOWN_PREVIEW_LABEL,
+
+ precondition: 'toggleLivePreview',
+ });
+ this.preview.actions.hide = instance.addAction({
+ ...actionBasis,
+ id: EXTENSION_MARKDOWN_PREVIEW_HIDE_ACTION_ID,
+ label: EXTENSION_MARKDOWN_HIDE_PREVIEW_LABEL,
+
+ precondition: '!toggleLivePreview',
});
}
@@ -126,18 +167,6 @@ export class EditorMarkdownPreviewExtension {
return {
markdownPreview: this.preview,
- cleanup: (instance) => {
- if (this.preview.modelChangeListener) {
- this.preview.modelChangeListener.dispose();
- }
- this.preview.action.dispose();
- if (this.preview.shown) {
- this.togglePreviewPanel(instance);
- this.togglePreviewLayout(instance);
- }
- this.preview.shown = false;
- },
-
fetchPreview: (instance) => this.fetchPreview(instance),
setupPreviewAction: (instance) => this.setupPreviewAction(instance),
@@ -149,6 +178,8 @@ export class EditorMarkdownPreviewExtension {
this.togglePreviewLayout(instance);
this.togglePreviewPanel(instance);
+ this.preview.actionShowPreviewCondition.set(!this.preview.actionShowPreviewCondition.get());
+
if (!this.preview?.shown) {
this.preview.modelChangeListener = instance.onDidChangeModelContent(
debounce(
@@ -161,6 +192,11 @@ export class EditorMarkdownPreviewExtension {
}
this.preview.shown = !this.preview?.shown;
+ if (instance.toolbar) {
+ instance.toolbar.updateItem(EXTENSION_MARKDOWN_PREVIEW_ACTION_ID, {
+ selected: this.preview.shown,
+ });
+ }
},
};
}
diff --git a/app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js b/app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js
new file mode 100644
index 00000000000..9655c8ae76a
--- /dev/null
+++ b/app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js
@@ -0,0 +1,98 @@
+import Vue from 'vue';
+import getToolbarItemsQuery from '~/editor/graphql/get_items.query.graphql';
+import removeToolbarItemsMutation from '~/editor/graphql/remove_items.mutation.graphql';
+import updateToolbarItemMutation from '~/editor/graphql/update_item.mutation.graphql';
+import addToolbarItemsMutation from '~/editor/graphql/add_items.mutation.graphql';
+import SourceEditorToolbar from '~/editor/components/source_editor_toolbar.vue';
+import { apolloProvider } from '~/editor/components/source_editor_toolbar_graphql';
+
+const client = apolloProvider.defaultClient;
+
+export class ToolbarExtension {
+ /**
+ * A required getter returning the extension's name
+ * We have to provide it for every extension instead of relying on the built-in
+ * `name` prop because the prop does not survive the webpack's minification
+ * and the name mangling.
+ * @returns {string}
+ */
+ static get extensionName() {
+ return 'ToolbarExtension';
+ }
+ /**
+ * THE LIFE-CYCLE CALLBACKS
+ */
+
+ /**
+ * Is called before the extension gets used by an instance,
+ * Use `onSetup` to setup Monaco directly:
+ * actions, keystrokes, update options, etc.
+ * Is called only once before the extension gets registered
+ *
+ * @param { Object } [instance] The Source Editor instance
+ * @param { Object } [setupOptions] The setupOptions object
+ */
+ // eslint-disable-next-line class-methods-use-this
+ onSetup(instance, setupOptions) {
+ const el = setupOptions?.el || document.getElementById('editor-toolbar');
+ ToolbarExtension.setupVue(el);
+ }
+
+ static setupVue(el) {
+ client.cache.writeQuery({ query: getToolbarItemsQuery, data: { items: { nodes: [] } } });
+ const ToolbarComponent = Vue.extend(SourceEditorToolbar);
+
+ const toolbar = new ToolbarComponent({
+ el,
+ apolloProvider,
+ });
+ toolbar.$mount();
+ }
+
+ /**
+ * The public API of the extension: these are the methods that will be exposed
+ * to the end user
+ * @returns {Object}
+ */
+ // eslint-disable-next-line class-methods-use-this
+ provides() {
+ return {
+ toolbar: {
+ getItem: (id) => {
+ const items = client.readQuery({ query: getToolbarItemsQuery })?.items?.nodes || [];
+ return items.find((item) => item.id === id);
+ },
+ getAllItems: () => {
+ return client.readQuery({ query: getToolbarItemsQuery })?.items?.nodes || [];
+ },
+ addItems: (items = []) => {
+ return client.mutate({
+ mutation: addToolbarItemsMutation,
+ variables: {
+ items,
+ },
+ });
+ },
+ removeItems: (ids = []) => {
+ client.mutate({
+ mutation: removeToolbarItemsMutation,
+ variables: {
+ ids,
+ },
+ });
+ },
+ updateItem: (id = '', propsToUpdate = {}) => {
+ if (id) {
+ client.mutate({
+ mutation: updateToolbarItemMutation,
+ variables: {
+ id,
+ propsToUpdate,
+ },
+ });
+ }
+ },
+ },
+ };
+ }
+}
diff --git a/app/assets/javascripts/editor/graphql/add_items.mutation.graphql b/app/assets/javascripts/editor/graphql/add_items.mutation.graphql
new file mode 100644
index 00000000000..13afcc04a48
--- /dev/null
+++ b/app/assets/javascripts/editor/graphql/add_items.mutation.graphql
@@ -0,0 +1,3 @@
+mutation addItems($items: [Item]) {
+ addToolbarItems(items: $items) @client
+}
diff --git a/app/assets/javascripts/editor/graphql/get_item.query.graphql b/app/assets/javascripts/editor/graphql/get_item.query.graphql
deleted file mode 100644
index 7c8bc09f7b0..00000000000
--- a/app/assets/javascripts/editor/graphql/get_item.query.graphql
+++ /dev/null
@@ -1,9 +0,0 @@
-query ToolbarItem($id: String!) {
- item(id: $id) @client {
- id
- label
- icon
- selected
- group
- }
-}
diff --git a/app/assets/javascripts/editor/graphql/remove_items.mutation.graphql b/app/assets/javascripts/editor/graphql/remove_items.mutation.graphql
new file mode 100644
index 00000000000..627f105b0ec
--- /dev/null
+++ b/app/assets/javascripts/editor/graphql/remove_items.mutation.graphql
@@ -0,0 +1,3 @@
+mutation removeToolbarItems($ids: [ID!]) {
+ removeToolbarItems(ids: $ids) @client
+}
diff --git a/app/assets/javascripts/editor/graphql/typedefs.graphql b/app/assets/javascripts/editor/graphql/typedefs.graphql
new file mode 100644
index 00000000000..2433ebf6c66
--- /dev/null
+++ b/app/assets/javascripts/editor/graphql/typedefs.graphql
@@ -0,0 +1,23 @@
+type Item {
+ id: ID!
+ label: String!
+ icon: String
+ selected: Boolean
+ group: Int!
+ category: String
+ selectedLabel: String
+}
+
+type Items {
+ nodes: [Item]!
+}
+
+extend type Query {
+ items: Items
+}
+
+extend type Mutation {
+ updateToolbarItem(id: ID!, propsToUpdate: Item!): LocalErrors
+ removeToolbarItems(ids: [ID!]): LocalErrors
+ addToolbarItems(items: [Item]): LocalErrors
+}
diff --git a/app/assets/javascripts/editor/graphql/update_item.mutation.graphql b/app/assets/javascripts/editor/graphql/update_item.mutation.graphql
index f8424c65181..05c18988c87 100644
--- a/app/assets/javascripts/editor/graphql/update_item.mutation.graphql
+++ b/app/assets/javascripts/editor/graphql/update_item.mutation.graphql
@@ -1,3 +1,3 @@
-mutation updateItem($id: String!, $propsToUpdate: Item!) {
+mutation updateItem($id: ID!, $propsToUpdate: Item!) {
updateToolbarItem(id: $id, propsToUpdate: $propsToUpdate) @client
}