From 36a59d088eca61b834191dacea009677a96c052f Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 19 May 2022 07:33:21 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-0-stable-ee --- .../editor/components/source_editor_toolbar.vue | 6 +- .../components/source_editor_toolbar_button.vue | 74 ++++---------- .../components/source_editor_toolbar_graphql.js | 53 ++++++++++ app/assets/javascripts/editor/constants.js | 5 +- .../extensions/source_editor_extension_base.js | 4 +- .../source_editor_markdown_livepreview_ext.js | 108 ++++++++++++++------- .../editor/extensions/source_editor_toolbar_ext.js | 98 +++++++++++++++++++ .../editor/graphql/add_items.mutation.graphql | 3 + .../editor/graphql/get_item.query.graphql | 9 -- .../editor/graphql/remove_items.mutation.graphql | 3 + .../javascripts/editor/graphql/typedefs.graphql | 23 +++++ .../editor/graphql/update_item.mutation.graphql | 2 +- app/assets/javascripts/editor/schema/ci.json | 21 +--- 13 files changed, 285 insertions(+), 124 deletions(-) create mode 100644 app/assets/javascripts/editor/components/source_editor_toolbar_graphql.js create mode 100644 app/assets/javascripts/editor/extensions/source_editor_toolbar_ext.js create mode 100644 app/assets/javascripts/editor/graphql/add_items.mutation.graphql delete mode 100644 app/assets/javascripts/editor/graphql/get_item.query.graphql create mode 100644 app/assets/javascripts/editor/graphql/remove_items.mutation.graphql create mode 100644 app/assets/javascripts/editor/graphql/typedefs.graphql (limited to 'app/assets/javascripts/editor') 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" > - + 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 @@ 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_extension_base.js b/app/assets/javascripts/editor/extensions/source_editor_extension_base.js index 3aa19df964c..0590bb7455a 100644 --- a/app/assets/javascripts/editor/extensions/source_editor_extension_base.js +++ b/app/assets/javascripts/editor/extensions/source_editor_extension_base.js @@ -5,7 +5,7 @@ import { EXTENSION_BASE_LINE_NUMBERS_CLASS, } from '../constants'; -const hashRegexp = new RegExp('#?L', 'g'); +const hashRegexp = /#?L/g; const createAnchor = (href) => { const fragment = new DocumentFragment(); @@ -64,7 +64,7 @@ export class SourceEditorExtension { const [start, end] = bounds && Array.isArray(bounds) ? bounds - : window.location.hash?.replace(hashRegexp, '').split('-'); + : window.location.hash.replace(hashRegexp, '').split('-'); let startLine = start ? parseInt(start, 10) : null; let endLine = end ? parseInt(end, 10) : startLine; if (endLine < startLine) { 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 } diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index fe3229ac91b..1352211b927 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -170,23 +170,6 @@ } ] }, - "cobertura": { - "description": "Path for file(s) that should be parsed as Cobertura XML coverage report", - "oneOf": [ - { - "type": "string", - "description": "Path to a single XML file" - }, - { - "type": "array", - "description": "A list of paths to XML files that will automatically be merged into one report", - "items": { - "type": "string" - }, - "minItems": 1 - } - ] - }, "coverage_report": { "type": "object", "description": "Used to collect coverage reports from the job.", @@ -1093,8 +1076,8 @@ "description": "The name of a job to execute when the environment is about to be stopped." }, "action": { - "enum": ["start", "prepare", "stop"], - "description": "Specifies what this job will do. 'start' (default) indicates the job will start the deployment. 'prepare' indicates this will not affect the deployment. 'stop' indicates this will stop the deployment.", + "enum": ["start", "prepare", "stop", "verify", "access"], + "description": "Specifies what this job will do. 'start' (default) indicates the job will start the deployment. 'prepare'/'verify'/'access' indicates this will not affect the deployment. 'stop' indicates this will stop the deployment.", "default": "start" }, "auto_stop_in": { -- cgit v1.2.1