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>2020-07-15 21:09:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-15 21:09:09 +0300
commitda1962d9ac710f95d350d2645c87f5a663123cf2 (patch)
tree1725ade126a9b4ae0148cd100cee94c44f9ce9f3 /app/assets/javascripts
parente69e3f1eb695b4e852c56e7ddf8c52915ae2631b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/code_navigation/components/popover.vue95
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue2
-rw-r--r--app/assets/javascripts/issuables_list/components/issuables_list_app.vue4
-rw-r--r--app/assets/javascripts/issuables_list/index.js2
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js13
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js2
-rw-r--r--app/assets/javascripts/projects/components/remove_modal.vue108
-rw-r--r--app/assets/javascripts/projects/project_remove_modal.js24
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js10
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js56
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js9
12 files changed, 265 insertions, 69 deletions
diff --git a/app/assets/javascripts/code_navigation/components/popover.vue b/app/assets/javascripts/code_navigation/components/popover.vue
index 4cc5a5cd6a2..b7fa3242fbf 100644
--- a/app/assets/javascripts/code_navigation/components/popover.vue
+++ b/app/assets/javascripts/code_navigation/components/popover.vue
@@ -1,10 +1,14 @@
<script>
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlTabs, GlTab, GlLink, GlBadge } from '@gitlab/ui';
import DocLine from './doc_line.vue';
export default {
components: {
GlButton,
+ GlTabs,
+ GlTab,
+ GlLink,
+ GlBadge,
DocLine,
},
props: {
@@ -54,6 +58,9 @@ export default {
isDefinitionCurrentBlob() {
return this.data.definition_path.indexOf(this.blobPath) === 0;
},
+ references() {
+ return this.data.references || [];
+ },
},
watch: {
position: {
@@ -82,37 +89,61 @@ export default {
class="popover code-navigation-popover popover-font-size-normal gl-popover bs-popover-bottom show"
>
<div :style="{ left: `${offsetLeft}px` }" class="arrow"></div>
- <div class="overflow-auto code-navigation-popover-container">
- <div
- v-for="(hover, index) in data.hover"
- :key="index"
- :class="{ 'border-bottom': index !== data.hover.length - 1 }"
- >
- <pre
- v-if="hover.language"
- ref="code-output"
- :class="$options.colorScheme"
- class="border-0 bg-transparent m-0 code highlight text-wrap"
- ><doc-line v-for="(tokens, tokenIndex) in hover.tokens" :key="tokenIndex" :language="hover.language" :tokens="tokens"/></pre>
- <p v-else ref="doc-output" class="p-3 m-0 gl-font-base">
- {{ hover.value }}
+ <gl-tabs nav-class="gl-hidden" content-class="gl-py-0">
+ <gl-tab :title="__('Definition')">
+ <div class="overflow-auto code-navigation-popover-container">
+ <div
+ v-for="(hover, index) in data.hover"
+ :key="index"
+ :class="{ 'border-bottom': index !== data.hover.length - 1 }"
+ >
+ <pre
+ v-if="hover.language"
+ ref="code-output"
+ :class="$options.colorScheme"
+ class="border-0 bg-transparent m-0 code highlight text-wrap"
+ ><doc-line v-for="(tokens, tokenIndex) in hover.tokens" :key="tokenIndex" :language="hover.language" :tokens="tokens"/></pre>
+ <p v-else ref="doc-output" class="p-3 m-0">
+ {{ hover.value }}
+ </p>
+ </div>
+ </div>
+ <div v-if="definitionPath || isCurrentDefinition" class="popover-body border-top">
+ <span v-if="isCurrentDefinition" class="gl-font-weight-bold gl-font-base">
+ {{ s__('CodeIntelligence|This is the definition') }}
+ </span>
+ <gl-button
+ v-else
+ :href="definitionPath"
+ :target="isDefinitionCurrentBlob ? null : '_blank'"
+ class="w-100"
+ variant="default"
+ data-testid="go-to-definition-btn"
+ >
+ {{ __('Go to definition') }}
+ </gl-button>
+ </div>
+ </gl-tab>
+ <gl-tab data-testid="references-tab" class="py-2">
+ <template #title>
+ {{ __('References') }}
+ <gl-badge size="sm" class="gl-tab-counter-badge">{{ references.length }}</gl-badge>
+ </template>
+ <template v-if="references.length">
+ <div v-for="(reference, index) in references" :key="index" class="gl-dropdown-item">
+ <gl-link
+ :href="`${definitionPathPrefix}/${reference.path}`"
+ class="dropdown-item"
+ data-testid="reference-link"
+ >
+ {{ reference.path }}
+ </gl-link>
+ </div>
+ </template>
+ <p v-else class="gl-my-4 gl-px-4">
+ {{ s__('CodeNavigation|No references found') }}
</p>
- </div>
- </div>
- <div v-if="definitionPath || isCurrentDefinition" class="popover-body border-top">
- <span v-if="isCurrentDefinition" class="gl-font-weight-bold gl-font-base">
- {{ s__('CodeIntelligence|This is the definition') }}
- </span>
- <gl-button
- v-else
- :href="definitionPath"
- :target="isDefinitionCurrentBlob ? null : '_blank'"
- class="w-100"
- variant="default"
- data-testid="go-to-definition-btn"
- >
- {{ __('Go to definition') }}
- </gl-button>
- </div>
+ </gl-tab>
+ </gl-tabs>
</div>
</template>
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 8d603e3ea44..12e2ce88b3c 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -53,7 +53,7 @@ export default {
return this.type === 'jira';
},
showJiraIssuesFields() {
- return this.isJira && this.glFeatures.jiraIntegration;
+ return this.isJira && this.glFeatures.jiraIssuesIntegration;
},
},
};
diff --git a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
index e1a40323f5d..f4787f5c1d4 100644
--- a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
+++ b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue
@@ -293,10 +293,10 @@ export default {
this.filters = filters;
},
refetchIssuables() {
- const ignored = ['utf8', 'state'];
+ const ignored = ['utf8'];
const params = omit(this.filters, ignored);
- historyPushState(setUrlParams(params, window.location.href, true));
+ historyPushState(setUrlParams(params, window.location.href, true, true));
this.fetchIssuables();
},
handleFilter(filters) {
diff --git a/app/assets/javascripts/issuables_list/index.js b/app/assets/javascripts/issuables_list/index.js
index 6b0c56c8dbd..40252c10d5f 100644
--- a/app/assets/javascripts/issuables_list/index.js
+++ b/app/assets/javascripts/issuables_list/index.js
@@ -36,7 +36,7 @@ function mountIssuableListRootApp() {
}
function mountIssuablesListApp() {
- if (!gon.features?.vueIssuablesList && !gon.features?.jiraIntegration) {
+ if (!gon.features?.vueIssuablesList && !gon.features?.jiraIssuesIntegration) {
return;
}
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 0472b8cf51f..c6c34b831ee 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -344,9 +344,15 @@ export function objectToQuery(obj) {
* @param {Object} params The query params to be set/updated
* @param {String} url The url to be operated on
* @param {Boolean} clearParams Indicates whether existing query params should be removed or not
+ * @param {Boolean} railsArraySyntax When enabled, changes the array syntax from `keys=` to `keys[]=` according to Rails conventions
* @returns {String} A copy of the original with the updated query params
*/
-export const setUrlParams = (params, url = window.location.href, clearParams = false) => {
+export const setUrlParams = (
+ params,
+ url = window.location.href,
+ clearParams = false,
+ railsArraySyntax = false,
+) => {
const urlObj = new URL(url);
const queryString = urlObj.search;
const searchParams = clearParams ? new URLSearchParams('') : new URLSearchParams(queryString);
@@ -355,11 +361,12 @@ export const setUrlParams = (params, url = window.location.href, clearParams = f
if (params[key] === null || params[key] === undefined) {
searchParams.delete(key);
} else if (Array.isArray(params[key])) {
+ const keyName = railsArraySyntax ? `${key}[]` : key;
params[key].forEach((val, idx) => {
if (idx === 0) {
- searchParams.set(key, val);
+ searchParams.set(keyName, val);
} else {
- searchParams.append(key, val);
+ searchParams.append(keyName, val);
}
});
} else {
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 9fb07917f9b..63762e414df 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -7,11 +7,13 @@ import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initFilePickers from '~/file_pickers';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
+import initProjectRemoveModal from '~/projects/project_remove_modal';
document.addEventListener('DOMContentLoaded', () => {
initFilePickers();
initConfirmDangerModal();
initSettingsPanels();
+ initProjectRemoveModal();
mountBadgeSettings(PROJECT_BADGE);
initProjectLoadingSpinner();
diff --git a/app/assets/javascripts/projects/components/remove_modal.vue b/app/assets/javascripts/projects/components/remove_modal.vue
new file mode 100644
index 00000000000..37f58efcb30
--- /dev/null
+++ b/app/assets/javascripts/projects/components/remove_modal.vue
@@ -0,0 +1,108 @@
+<script>
+import { GlModal, GlModalDirective, GlSprintf, GlFormInput, GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+import { rstrip } from '~/lib/utils/common_utils';
+import csrf from '~/lib/utils/csrf';
+
+export default {
+ components: {
+ GlModal,
+ GlSprintf,
+ GlFormInput,
+ GlButton,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ props: {
+ confirmPhrase: {
+ type: String,
+ required: true,
+ },
+ warningMessage: {
+ type: String,
+ required: true,
+ },
+ formPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ userInput: null,
+ };
+ },
+ computed: {
+ buttonDisabled() {
+ return rstrip(this.userInput) !== this.confirmPhrase;
+ },
+ csrfToken() {
+ return csrf.token;
+ },
+ },
+ methods: {
+ submitForm() {
+ this.$refs.form.submit();
+ },
+ },
+ strings: {
+ removeProject: __('Remove project'),
+ title: __('Confirmation required'),
+ confirm: __('Confirm'),
+ dataLoss: __(
+ 'This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.',
+ ),
+ confirmText: __('Please type %{phrase_code} to proceed or close this modal to cancel.'),
+ },
+ modalId: 'remove-project-modal',
+};
+</script>
+
+<template>
+ <form ref="form" :action="formPath" method="post">
+ <input type="hidden" name="_method" value="delete" />
+ <input :value="csrfToken" type="hidden" name="authenticity_token" />
+ <gl-button v-gl-modal="$options.modalId" category="primary" variant="danger">{{
+ $options.strings.removeProject
+ }}</gl-button>
+ <gl-modal
+ ref="removeModal"
+ :modal-id="$options.modalId"
+ size="sm"
+ ok-variant="danger"
+ footer-class="bg-gray-light gl-p-5"
+ >
+ <template #modal-title>{{ $options.strings.title }}</template>
+ <template #modal-footer>
+ <div class="gl-w-full gl-display-flex gl-just-content-start gl-m-0">
+ <gl-button
+ :disabled="buttonDisabled"
+ category="primary"
+ variant="danger"
+ @click="submitForm"
+ >
+ {{ $options.strings.confirm }}
+ </gl-button>
+ </div>
+ </template>
+ <div>
+ <p class="gl-text-red-500 gl-font-weight-bold">{{ warningMessage }}</p>
+ <p class="gl-mb-0">{{ $options.strings.dataLoss }}</p>
+ <p>
+ <gl-sprintf :message="$options.strings.confirmText">
+ <template #phrase_code>
+ <code>{{ confirmPhrase }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <gl-form-input
+ id="confirm_name_input"
+ v-model="userInput"
+ name="confirm_name_input"
+ type="text"
+ />
+ </div>
+ </gl-modal>
+ </form>
+</template>
diff --git a/app/assets/javascripts/projects/project_remove_modal.js b/app/assets/javascripts/projects/project_remove_modal.js
new file mode 100644
index 00000000000..dbdad1bf6f1
--- /dev/null
+++ b/app/assets/javascripts/projects/project_remove_modal.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import RemoveProjectModal from './components/remove_modal.vue';
+
+export default (selector = '#js-confirm-project-remove') => {
+ const el = document.querySelector(selector);
+
+ if (!el) return;
+
+ const { formPath, confirmPhrase, warningMessage } = el.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(RemoveProjectModal, {
+ props: {
+ confirmPhrase,
+ warningMessage,
+ formPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
index 7bb6c275832..34cb74efabe 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js
@@ -1,4 +1,4 @@
-import renderHtml from './renderers/render_html';
+import renderBlockHtml from './renderers/render_html_block';
import renderKramdownList from './renderers/render_kramdown_list';
import renderKramdownText from './renderers/render_kramdown_text';
import renderIdentifierParagraph from './renderers/render_identifier_paragraph';
@@ -6,7 +6,7 @@ import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text';
import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline';
const htmlInlineRenderers = [renderFontAwesomeHtmlInline];
-const htmlRenderers = [renderHtml];
+const htmlBlockRenderers = [renderBlockHtml];
const listRenderers = [renderKramdownList];
const paragraphRenderers = [renderIdentifierParagraph];
const textRenderers = [renderKramdownText, renderEmbeddedRubyText];
@@ -32,9 +32,9 @@ const buildCustomHTMLRenderer = (
) => {
const defaults = {
htmlBlock(node, context) {
- const allHtmlRenderers = [...customRenderers.list, ...htmlRenderers];
+ const allHtmlBlockRenderers = [...customRenderers.htmlBlock, ...htmlBlockRenderers];
- return executeRenderer(allHtmlRenderers, node, context);
+ return executeRenderer(allHtmlBlockRenderers, node, context);
},
htmlInline(node, context) {
const allHtmlInlineRenderers = [...customRenderers.htmlInline, ...htmlInlineRenderers];
@@ -47,7 +47,7 @@ const buildCustomHTMLRenderer = (
return executeRenderer(allListRenderers, node, context);
},
paragraph(node, context) {
- const allParagraphRenderers = [...customRenderers.list, ...paragraphRenderers];
+ const allParagraphRenderers = [...customRenderers.paragraph, ...paragraphRenderers];
return executeRenderer(allParagraphRenderers, node, context);
},
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js
index c81478a8405..6937d2acb47 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js
@@ -4,25 +4,36 @@ const buildToken = (type, tagName, props) => {
const TAG_TYPES = {
block: 'div',
- inline: 'span',
+ inline: 'a',
};
-export const buildUneditableOpenTokens = (token, type = TAG_TYPES.block) => {
- return [
- buildToken('openTag', type, {
- attributes: { contenteditable: false },
- classNames: [
- 'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
- ],
- }),
- token,
- ];
+// Open helpers (singular and multiple)
+
+const buildUneditableOpenToken = (tagType = TAG_TYPES.block) =>
+ buildToken('openTag', tagType, {
+ attributes: { contenteditable: false },
+ classNames: [
+ 'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
+ ],
+ });
+
+export const buildUneditableOpenTokens = (token, tagType = TAG_TYPES.block) => {
+ return [buildUneditableOpenToken(tagType), token];
+};
+
+// Close helpers (singular and multiple)
+
+export const buildUneditableCloseToken = (tagType = TAG_TYPES.block) =>
+ buildToken('closeTag', tagType);
+
+export const buildUneditableCloseTokens = (token, tagType = TAG_TYPES.block) => {
+ return [token, buildUneditableCloseToken(tagType)];
};
-export const buildUneditableCloseToken = (type = TAG_TYPES.block) => buildToken('closeTag', type);
+// Complete helpers (open plus close)
-export const buildUneditableCloseTokens = (token, type = TAG_TYPES.block) => {
- return [token, buildUneditableCloseToken(type)];
+export const buildUneditableTokens = token => {
+ return [...buildUneditableOpenTokens(token), buildUneditableCloseToken()];
};
export const buildUneditableInlineTokens = token => {
@@ -32,6 +43,19 @@ export const buildUneditableInlineTokens = token => {
];
};
-export const buildUneditableTokens = token => {
- return [...buildUneditableOpenTokens(token), buildUneditableCloseToken()];
+export const buildUneditableHtmlAsTextTokens = node => {
+ /*
+ Toast UI internally appends ' data-tomark-pass ' attribute flags so it can target certain
+ nested nodes for internal use during Markdown <=> WYSIWYG conversions. In our case, we want
+ to prevent HTML being rendered completely in WYSIWYG mode and thus we use a `text` vs. `html`
+ type when building the token. However, in doing so, we need to strip out the ` data-tomark-pass `
+ to prevent their persistence within the `text` content as the user did not intend these as edits.
+
+ https://github.com/nhn/tui.editor/blob/cc54ec224fc3a4b6e5a2b19a71650959f41adc0e/apps/editor/src/js/convertor.js#L72
+ */
+ const regex = / data-tomark-pass /gm;
+ const content = node.literal.replace(regex, '');
+ const htmlAsTextToken = buildToken('text', null, { content });
+
+ return [buildUneditableOpenToken(), htmlAsTextToken, buildUneditableCloseToken()];
};
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html.js
deleted file mode 100644
index a3b467851dc..00000000000
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { buildUneditableTokens } from './build_uneditable_token';
-
-const canRender = ({ type }) => {
- return type === 'htmlBlock';
-};
-
-const render = (_, { origin }) => buildUneditableTokens(origin());
-
-export default { canRender, render };
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js
new file mode 100644
index 00000000000..b179ca61dba
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js
@@ -0,0 +1,9 @@
+import { buildUneditableHtmlAsTextTokens } from './build_uneditable_token';
+
+const canRender = ({ type }) => {
+ return type === 'htmlBlock';
+};
+
+const render = node => buildUneditableHtmlAsTextTokens(node);
+
+export default { canRender, render };