diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-31 12:09:32 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-31 12:09:32 +0300 |
commit | 5025412fc4ab16cc7049a38d43fdc2e4095a1f87 (patch) | |
tree | ecec75618d069e02ba0ebcf36db6630150a9d073 /app/assets | |
parent | 853c0c530b624a2f94ce85acbbdffc70510bdba3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
8 files changed, 122 insertions, 77 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index a08cf48c327..ee5c0fe5ef3 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -15,7 +15,7 @@ $.fn.renderGFM = function renderGFM() { syntaxHighlight(this.find('.js-syntax-highlight').get()); renderKroki(this.find('.js-render-kroki[hidden]').get()); renderMath(this.find('.js-render-math')); - renderSandboxedMermaid(this.find('.js-render-mermaid').get()); + renderSandboxedMermaid(this.find('.js-render-mermaid')); renderJSONTable( Array.from(this.find('[lang="json"][data-lang-params="table"]').get()).map((e) => e.parentNode), ); diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js index 031b03e0d59..077e96b2fee 100644 --- a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js @@ -1,4 +1,5 @@ -import { countBy } from 'lodash'; +import $ from 'jquery'; +import { once, countBy } from 'lodash'; import { __ } from '~/locale'; import { getBaseURL, @@ -7,8 +8,7 @@ import { joinPaths, } from '~/lib/utils/url_utility'; import { darkModeEnabled } from '~/lib/utils/color_utils'; -import { setAttributes, isElementVisible } from '~/lib/utils/dom_utils'; -import { createAlert, VARIANT_WARNING } from '~/flash'; +import { setAttributes } from '~/lib/utils/dom_utils'; import { unrestrictedPages } from './constants'; // Renders diagrams and flowcharts from text using Mermaid in any element with the @@ -27,30 +27,17 @@ import { unrestrictedPages } from './constants'; const SANDBOX_FRAME_PATH = '/-/sandbox/mermaid'; // This is an arbitrary number; Can be iterated upon when suitable. -export const MAX_CHAR_LIMIT = 2000; +const MAX_CHAR_LIMIT = 2000; // Max # of mermaid blocks that can be rendered in a page. -export const MAX_MERMAID_BLOCK_LIMIT = 50; +const MAX_MERMAID_BLOCK_LIMIT = 50; // Max # of `&` allowed in Chaining of links syntax const MAX_CHAINING_OF_LINKS_LIMIT = 30; - export const BUFFER_IFRAME_HEIGHT = 10; export const SANDBOX_ATTRIBUTES = 'allow-scripts allow-popups'; - -const ALERT_CONTAINER_CLASS = 'mermaid-alert-container'; -export const LAZY_ALERT_SHOWN_CLASS = 'lazy-alert-shown'; - // Keep a map of mermaid blocks we've already rendered. const elsProcessingMap = new WeakMap(); let renderedMermaidBlocks = 0; -/** - * Determines whether a given Mermaid diagram is visible. - * - * @param {Element} el The Mermaid DOM node - * @returns - */ -const isVisibleMermaid = (el) => el.closest('details') === null && isElementVisible(el); - function shouldLazyLoadMermaidBlock(source) { /** * If source contains `&`, which means that it might @@ -117,8 +104,8 @@ function renderMermaidEl(el, source) { ); } -function renderMermaids(els) { - if (!els.length) return; +function renderMermaids($els) { + if (!$els.length) return; const pageName = document.querySelector('body').dataset.page; @@ -127,7 +114,7 @@ function renderMermaids(els) { let renderedChars = 0; - els.forEach((el) => { + $els.each((i, el) => { // Skipping all the elements which we've already queued in requestIdleCallback if (elsProcessingMap.has(el)) { return; @@ -146,29 +133,33 @@ function renderMermaids(els) { renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT || shouldLazyLoadMermaidBlock(source)) ) { - const parent = el.parentNode; - - if (!parent.classList.contains(LAZY_ALERT_SHOWN_CLASS)) { - const alertContainer = document.createElement('div'); - alertContainer.classList.add(ALERT_CONTAINER_CLASS); - alertContainer.classList.add('gl-mb-5'); - parent.after(alertContainer); - createAlert({ - message: __( - 'Warning: Displaying this diagram might cause performance issues on this page.', - ), - variant: VARIANT_WARNING, - parent: parent.parentNode, - containerSelector: `.${ALERT_CONTAINER_CLASS}`, - primaryButton: { - text: __('Display'), - clickHandler: () => { - alertContainer.remove(); - renderMermaidEl(el, source); - }, - }, - }); - parent.classList.add(LAZY_ALERT_SHOWN_CLASS); + const html = ` + <div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert"> + <div> + <div> + <div class="js-warning-text"></div> + <div class="gl-alert-actions"> + <button type="button" class="js-lazy-render-mermaid btn gl-alert-action btn-confirm btn-md gl-button">Display</button> + </div> + </div> + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + </div> + `; + + const $parent = $(el).parent(); + + if (!$parent.hasClass('lazy-alert-shown')) { + $parent.after(html); + $parent + .siblings() + .find('.js-warning-text') + .text( + __('Warning: Displaying this diagram might cause performance issues on this page.'), + ); + $parent.addClass('lazy-alert-shown'); } return; @@ -185,33 +176,37 @@ function renderMermaids(els) { }); } -export default function renderMermaid(els) { - if (!els.length) return; +const hookLazyRenderMermaidEvent = once(() => { + $(document.body).on('click', '.js-lazy-render-mermaid', function eventHandler() { + const parent = $(this).closest('.js-lazy-render-mermaid-container'); + const pre = parent.prev(); - const visibleMermaids = []; - const hiddenMermaids = []; + const el = pre.find('.js-render-mermaid'); - for (const el of els) { - if (isVisibleMermaid(el)) { - visibleMermaids.push(el); - } else { - hiddenMermaids.push(el); - } - } + parent.remove(); + + // sandbox update + const element = el.get(0); + const { source } = fixElementSource(element); + + renderMermaidEl(element, source); + }); +}); + +export default function renderMermaid($els) { + if (!$els.length) return; + + const visibleMermaids = $els.filter(function filter() { + return $(this).closest('details').length === 0 && $(this).is(':visible'); + }); renderMermaids(visibleMermaids); - hiddenMermaids.forEach((el) => { - el.closest('details').addEventListener( - 'toggle', - ({ target: details }) => { - if (details.open) { - renderMermaids([...details.querySelectorAll('.js-render-mermaid')]); - } - }, - { - once: true, - }, - ); + $els.closest('details').one('toggle', function toggle() { + if (this.open) { + renderMermaids($(this).find('.js-render-mermaid')); + } }); + + hookLazyRenderMermaidEvent(); } diff --git a/app/assets/javascripts/related_issues/components/related_issues_block.vue b/app/assets/javascripts/related_issues/components/related_issues_block.vue index 1ab41ee2f0a..c3726ebc14a 100644 --- a/app/assets/javascripts/related_issues/components/related_issues_block.vue +++ b/app/assets/javascripts/related_issues/components/related_issues_block.vue @@ -95,6 +95,16 @@ export default { required: false, default: true, }, + hasError: { + type: Boolean, + required: false, + default: false, + }, + itemAddFailureMessage: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -233,7 +243,7 @@ export default { <div v-if="isFormVisible" class="js-add-related-issues-form-area card-body bordered-box bg-white" - :class="{ 'gl-mb-5': shouldShowTokenBody }" + :class="{ 'gl-mb-5': shouldShowTokenBody, 'gl-show-field-errors': hasError }" > <add-issuable-form :show-categorized-issues="showCategorizedIssues" @@ -245,6 +255,8 @@ export default { :auto-complete-epics="autoCompleteEpics" :auto-complete-issues="autoCompleteIssues" :path-id-separator="pathIdSeparator" + :has-error="hasError" + :item-add-failure-message="itemAddFailureMessage" @pendingIssuableRemoveRequest="$emit('pendingIssuableRemoveRequest', $event)" @addIssuableFormInput="$emit('addIssuableFormInput', $event)" @addIssuableFormBlur="$emit('addIssuableFormBlur', $event)" diff --git a/app/assets/javascripts/related_issues/components/related_issues_root.vue b/app/assets/javascripts/related_issues/components/related_issues_root.vue index 38e1d6e9d4f..795eb3b0083 100644 --- a/app/assets/javascripts/related_issues/components/related_issues_root.vue +++ b/app/assets/javascripts/related_issues/components/related_issues_root.vue @@ -107,6 +107,8 @@ export default { isSubmitting: false, isFormVisible: false, inputValue: '', + hasError: false, + errorMessage: null, }; }, computed: { @@ -170,11 +172,11 @@ export default { this.isFormVisible = false; }) .catch(({ response }) => { - let errorMessage = addRelatedIssueErrorMap[this.issuableType]; + this.hasError = true; + this.errorMessage = addRelatedIssueErrorMap[this.issuableType]; if (response && response.data && response.data.message) { - errorMessage = response.data.message; + this.errorMessage = response.data.message; } - createAlert({ message: errorMessage }); }) .finally(() => { this.isSubmitting = false; @@ -266,6 +268,8 @@ export default { :issuable-type="issuableType" :path-id-separator="pathIdSeparator" :show-categorized-issues="showCategorizedIssues" + :has-error="hasError" + :item-add-failure-message="errorMessage" @saveReorder="saveIssueOrder" @toggleAddRelatedIssuesForm="onToggleAddRelatedIssuesForm" @addIssuableFormInput="onInput" diff --git a/app/assets/javascripts/webhooks/components/form_url_app.vue b/app/assets/javascripts/webhooks/components/form_url_app.vue index 5ec16d4ba15..45526ff9080 100644 --- a/app/assets/javascripts/webhooks/components/form_url_app.vue +++ b/app/assets/javascripts/webhooks/components/form_url_app.vue @@ -1,5 +1,5 @@ <script> -import { isEmpty } from 'lodash'; +import { cloneDeep, isEmpty } from 'lodash'; import { GlFormGroup, GlFormInput, GlFormRadio, GlFormRadioGroup, GlLink } from '@gitlab/ui'; import { __, s__ } from '~/locale'; @@ -30,7 +30,7 @@ export default { return { maskEnabled: !isEmpty(this.initialUrlVariables), url: this.initialUrl, - items: isEmpty(this.initialUrlVariables) ? [{}] : this.initialUrlVariables, + items: this.getInitialItems(), }; }, computed: { @@ -54,6 +54,16 @@ export default { }, }, methods: { + getInitialItems() { + return isEmpty(this.initialUrlVariables) ? [{}] : cloneDeep(this.initialUrlVariables); + }, + isEditingItem(key) { + if (isEmpty(this.initialUrlVariables)) { + return false; + } + + return this.initialUrlVariables.some((item) => item.key === key); + }, onItemInput({ index, key, value }) { this.$set(this.items, index, { key, value }); }, @@ -112,6 +122,7 @@ export default { :index="index" :item-key="key" :item-value="value" + :is-editing="isEditingItem(key)" @input="onItemInput" @remove="removeItem" /> diff --git a/app/assets/javascripts/webhooks/components/form_url_mask_item.vue b/app/assets/javascripts/webhooks/components/form_url_mask_item.vue index 3b75f9b6c0d..aa5d9ce57f4 100644 --- a/app/assets/javascripts/webhooks/components/form_url_mask_item.vue +++ b/app/assets/javascripts/webhooks/components/form_url_mask_item.vue @@ -1,6 +1,7 @@ <script> import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui'; import { s__ } from '~/locale'; +import { MASK_ITEM_VALUE_HIDDEN } from '../constants'; export default { components: { @@ -24,6 +25,11 @@ export default { required: false, default: null, }, + isEditing: { + type: Boolean, + required: false, + default: false, + }, }, computed: { keyInputId() { @@ -32,6 +38,9 @@ export default { valueInputId() { return this.inputId('value'); }, + displayValue() { + return this.isEditing ? MASK_ITEM_VALUE_HIDDEN : this.itemValue; + }, }, methods: { inputId(type) { @@ -68,7 +77,8 @@ export default { <gl-form-input :id="valueInputId" :name="inputName('value')" - :value="itemValue" + :value="displayValue" + :disabled="isEditing" @input="onValueInput" /> </gl-form-group> @@ -82,9 +92,15 @@ export default { :id="keyInputId" :name="inputName('key')" :value="itemKey" + :disabled="isEditing" @input="onKeyInput" /> </gl-form-group> - <gl-button icon="remove" :aria-label="__('Remove')" @click="onRemoveClick" /> + <gl-button + icon="remove" + :aria-label="__('Remove')" + :disabled="isEditing" + @click="onRemoveClick" + /> </div> </template> diff --git a/app/assets/javascripts/webhooks/constants.js b/app/assets/javascripts/webhooks/constants.js index abef16545bc..6710a418117 100644 --- a/app/assets/javascripts/webhooks/constants.js +++ b/app/assets/javascripts/webhooks/constants.js @@ -15,3 +15,5 @@ export const descriptionText = { ), [BRANCH_FILTER_REGEX]: s__('Webhooks|Regex such as %{REGEX_CODE} is supported.'), }; + +export const MASK_ITEM_VALUE_HIDDEN = '************'; diff --git a/app/assets/javascripts/webhooks/index.js b/app/assets/javascripts/webhooks/index.js index 1b2b33e44c1..d90680a9bac 100644 --- a/app/assets/javascripts/webhooks/index.js +++ b/app/assets/javascripts/webhooks/index.js @@ -10,6 +10,11 @@ export default () => { const { url: initialUrl, urlVariables } = el.dataset; + // Convert the array of 'key' strings to array of { key } objects + const initialUrlVariables = urlVariables + ? JSON.parse(urlVariables)?.map((key) => ({ key })) + : undefined; + return new Vue({ el, name: 'WebhookFormRoot', @@ -17,7 +22,7 @@ export default () => { return createElement(FormUrlApp, { props: { initialUrl, - initialUrlVariables: urlVariables ? JSON.parse(urlVariables) : undefined, + initialUrlVariables, }, }); }, |