Коммит 235dc61f создал по автору GitLab Bot's avatar GitLab Bot
Просмотр файлов

Add latest changes from gitlab-org/gitlab@master

владелец 12866a39
import { __ } from '~/locale';
export const BLOB_EDITOR_ERROR = __('An error occurred while rendering the editor');
export const BLOB_PREVIEW_ERROR = __('An error occurred previewing the blob');
......@@ -3,39 +3,75 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
const monacoEnabled = window?.gon?.features?.monacoBlobs;
export default class EditBlob {
// The options object has:
// assetsPath, filePath, currentAction, projectId, isMarkdown
constructor(options) {
this.options = options;
this.configureAceEditor();
this.initModePanesAndLinks();
this.initSoftWrap();
this.initFileSelectors();
const { isMarkdown } = this.options;
Promise.resolve()
.then(() => {
return monacoEnabled ? this.configureMonacoEditor() : this.configureAceEditor();
})
.then(() => {
this.initModePanesAndLinks();
this.initFileSelectors();
this.initSoftWrap();
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
this.editor.focus();
})
.catch(() => createFlash(BLOB_EDITOR_ERROR));
}
configureMonacoEditor() {
return import(/* webpackChunkName: 'monaco_editor_lite' */ '~/editor/editor_lite').then(
EditorModule => {
const EditorLite = EditorModule.default;
const editorEl = document.getElementById('editor');
const fileNameEl =
document.getElementById('file_path') || document.getElementById('file_name');
const fileContentEl = document.getElementById('file-content');
const form = document.querySelector('.js-edit-blob-form');
this.editor = new EditorLite();
this.editor.createInstance({
el: editorEl,
blobPath: fileNameEl.value,
blobContent: editorEl.innerText,
});
fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value);
});
form.addEventListener('submit', () => {
fileContentEl.value = this.editor.getValue();
});
},
);
}
configureAceEditor() {
const { filePath, assetsPath, isMarkdown } = this.options;
const { filePath, assetsPath } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
if (isMarkdown) {
addEditorMarkdownListeners(this.editor);
}
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
this.editor.focus();
if (filePath) {
this.editor.getSession().setMode(getModeByFileExtension(filePath));
}
......@@ -81,7 +117,7 @@ export default class EditBlob {
currentPane.empty().append(data);
currentPane.renderGFM();
})
.catch(() => createFlash(__('An error occurred previewing the blob')));
.catch(() => createFlash(BLOB_PREVIEW_ERROR));
}
this.$toggleButton.show();
......@@ -90,14 +126,19 @@ export default class EditBlob {
}
initSoftWrap() {
this.isSoftWrapped = false;
this.isSoftWrapped = Boolean(monacoEnabled);
this.$toggleButton = $('.soft-wrap-toggle');
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.$toggleButton.on('click', () => this.toggleSoftWrap());
}
toggleSoftWrap() {
this.isSoftWrapped = !this.isSoftWrapped;
this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
if (monacoEnabled) {
this.editor.updateOptions({ wordWrap: this.isSoftWrapped ? 'on' : 'off' });
} else {
this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
}
}
}
import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
import { editor as monacoEditor, languages as monacoLanguages, Position, Uri } from 'monaco-editor';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import languages from '~/ide/lib/languages';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
......@@ -70,6 +70,22 @@ export default class Editor {
}
getValue() {
return this.model.getValue();
return this.instance.getValue();
}
setValue(val) {
this.instance.setValue(val);
}
focus() {
this.instance.focus();
}
navigateFileStart() {
this.instance.setPosition(new Position(1, 1));
}
updateOptions(options = {}) {
this.instance.updateOptions(options);
}
}
......@@ -9,13 +9,15 @@ export default class GLForm {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = { ...defaultAutocompleteConfig, ...enableGFM };
// Disable autocomplete for keywords which do not have dataSources available
const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
Object.keys(this.enableGFM).forEach(item => {
if (item !== 'emojis') {
this.enableGFM[item] = Boolean(dataSources[item]);
if (item !== 'emojis' && !dataSources[item]) {
this.enableGFM[item] = false;
}
});
// Before we start, we should clean up any previous data for this form
this.destroy();
// Set up the form
......
<script>
import { GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { GlButton } from '@gitlab/ui';
import { STATUS_PAGE_PUBLISHED, JOIN_ZOOM_MEETING } from '../constants';
export default {
components: {
Icon,
GlLink,
GlButton,
},
props: {
zoomMeetingUrl: {
......@@ -19,32 +18,46 @@ export default {
default: '',
},
},
computed: {
pinnedLinks() {
return [
{
id: 'publishedIncidentUrl',
url: this.publishedIncidentUrl,
text: STATUS_PAGE_PUBLISHED,
icon: 'tanuki',
},
{
id: 'zoomMeetingUrl',
url: this.zoomMeetingUrl,
text: JOIN_ZOOM_MEETING,
icon: 'brand-zoom',
},
];
},
},
methods: {
needsPaddingClass(i) {
return i < this.pinnedLinks.length - 1;
},
},
};
</script>
<template>
<div class="border-bottom gl-mb-6 gl-display-flex gl-justify-content-start">
<div v-if="publishedIncidentUrl" class="gl-pr-3">
<gl-link
:href="publishedIncidentUrl"
target="_blank"
class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
data-testid="publishedIncidentUrl"
>
<icon name="tanuki" :size="14" />
<strong class="vertical-align-top">{{ __('Published on status page') }}</strong>
</gl-link>
</div>
<div v-if="zoomMeetingUrl">
<gl-link
:href="zoomMeetingUrl"
target="_blank"
class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
data-testid="zoomMeetingUrl"
>
<icon name="brand-zoom" :size="14" />
<strong class="vertical-align-top">{{ __('Join Zoom meeting') }}</strong>
</gl-link>
</div>
<template v-for="(link, i) in pinnedLinks">
<div v-if="link.url" :key="link.id" :class="{ 'gl-pr-3': needsPaddingClass(i) }">
<gl-button
:href="link.url"
target="_blank"
:icon="link.icon"
size="small"
class="gl-font-weight-bold gl-mb-5"
:data-testid="link.id"
>{{ link.text }}</gl-button
>
</div>
</template>
</div>
</template>
......@@ -15,3 +15,6 @@ export const IssuableType = {
Epic: 'epic',
MergeRequest: 'merge_request',
};
export const STATUS_PAGE_PUBLISHED = __('Published on status page');
export const JOIN_ZOOM_MEETING = __('Join Zoom meeting');
......@@ -3,6 +3,7 @@ import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale';
import { GlTooltipDirective } from '@gitlab/ui';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
......@@ -11,6 +12,9 @@ export default {
Icon,
SmartVirtualList,
},
directives: {
GlTooltip: GlTooltipDirective,
},
store,
props: {
heading: {
......@@ -69,12 +73,24 @@ export default {
>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
<div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.classname }}</div>
<div
v-gl-tooltip
:title="testCase.classname"
class="table-mobile-content pr-md-1 text-truncate"
>
{{ testCase.classname }}
</div>
</div>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
<div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.name }}</div>
<div
v-gl-tooltip
:title="testCase.name"
class="table-mobile-content pr-md-1 text-truncate"
>
{{ testCase.name }}
</div>
</div>
<div class="table-section section-10 section-wrap">
......
......@@ -22,7 +22,7 @@ type AppData {
username: String!
}
type SubmitContentChangesInput {
input SubmitContentChangesInput {
project: String!
sourcePath: String!
content: String!
......
......@@ -3,18 +3,19 @@ import { escape } from 'lodash';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
import SidebarMediator from '~/sidebar/sidebar_mediator';
/**
* Creates the HTML template for each row of the mentions dropdown.
*
* @param original An object from the array returned from the `autocomplete_sources/members` API
* @returns {string} An HTML template
* @param original - An object from the array returned from the `autocomplete_sources/members` API
* @returns {string} - An HTML template
*/
function menuItemTemplate({ original }) {
const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';
const avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
gl-display-inline-flex gl-align-items-center gl-justify-content-center`;
gl-display-inline-flex! gl-align-items-center gl-justify-content-center`;
const avatarTag = original.avatar_url
? `<img
......@@ -48,6 +49,7 @@ export default {
},
data() {
return {
assignees: undefined,
members: undefined,
};
},
......@@ -76,19 +78,37 @@ export default {
*/
getMembers(inputText, processValues) {
if (this.members) {
processValues(this.members);
processValues(this.getFilteredMembers());
} else if (this.dataSources.members) {
axios
.get(this.dataSources.members)
.then(response => {
this.members = response.data;
processValues(response.data);
processValues(this.getFilteredMembers());
})
.catch(() => {});
} else {
processValues([]);
}
},
getFilteredMembers() {
const fullText = this.$slots.default[0].elm.value;
if (!this.assignees) {
this.assignees =
SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
}
if (fullText.startsWith('/assign @')) {
return this.members.filter(member => !this.assignees.includes(member.username));
}
if (fullText.startsWith('/unassign @')) {
return this.members.filter(member => this.assignees.includes(member.username));
}
return this.members;
},
},
render(createElement) {
return createElement('div', this.$slots.default);
......
......@@ -4,21 +4,25 @@ import '~/behaviors/markdown/render_gfm';
import { unescape } from 'lodash';
import { __, sprintf } from '~/locale';
import { stripHtml } from '~/lib/utils/text_utility';
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue';
import icon from '../icon.vue';
import Flash from '~/flash';
import GLForm from '~/gl_form';
import MarkdownHeader from './header.vue';
import MarkdownToolbar from './toolbar.vue';
import Icon from '../icon.vue';
import GlMentions from '~/vue_shared/components/gl_mentions.vue';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
markdownHeader,
markdownToolbar,
icon,
GlMentions,
MarkdownHeader,
MarkdownToolbar,
Icon,
Suggestions,
},
mixins: [glFeatureFlagsMixin()],
props: {
isSubmitting: {
type: Boolean,
......@@ -159,12 +163,10 @@ export default {
},
},
mounted() {
/*
GLForm class handles all the toolbar buttons
*/
// GLForm class handles all the toolbar buttons
return new GLForm($(this.$refs['gl-form']), {
emojis: this.enableAutocomplete,
members: this.enableAutocomplete,
members: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
issues: this.enableAutocomplete,
mergeRequests: this.enableAutocomplete,
epics: this.enableAutocomplete,
......@@ -243,7 +245,10 @@ export default {
/>
<div v-show="!previewMarkdown" class="md-write-holder">
<div class="zen-backdrop">
<slot name="textarea"></slot>
<gl-mentions v-if="glFeatures.tributeAutocomplete">
<slot name="textarea"></slot>
</gl-mentions>
<slot v-else name="textarea"></slot>
<a
class="zen-control zen-control-leave js-zen-leave gl-text-gray-700"
href="#"
......
......@@ -74,19 +74,6 @@
}
}
&:focus:hover,
&:focus {
&.header-user-dropdown-toggle .header-user-notification-dot {
border-color: $white;
}
}
&:hover {
&.header-user-dropdown-toggle .header-user-notification-dot {
border-color: $nav-svg-color + 33;
}
}
&:hover,
&:focus {
@include media-breakpoint-up(sm) {
......@@ -96,6 +83,10 @@
svg {
fill: currentColor;
}
&.header-user-dropdown-toggle .header-user-notification-dot {
border-color: $nav-svg-color + 33;
}
}
}
......@@ -109,6 +100,10 @@
fill: $nav-svg-color;
}
}
&.header-user-dropdown-toggle .header-user-notification-dot {
border-color: $white;
}
}
.impersonated-user,
......
......@@ -46,6 +46,7 @@ def set_issuables_index_only_actions
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
push_frontend_feature_flag(:tribute_autocomplete, @project)
end
before_action only: :show do
......
......@@ -27,7 +27,7 @@ def self.default_plans
end
def actual_limits
self.limits || PlanLimits.new
self.limits || self.build_limits
end
def default?
......
......@@ -40,7 +40,7 @@
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2', tabindex: '-1'
.file-editor.code
%pre.js-edit-mode-pane.qa-editor#editor= params[:content] || local_assigns[:blob_data]
%pre.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }= params[:content] || local_assigns[:blob_data]
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
......
- breadcrumb_title "Repository"
- page_title "Edit", @blob.path, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
- unless Feature.enabled?(:monaco_blobs)
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
- if @conflict
.alert.alert-danger
......
- breadcrumb_title "Repository"
- page_title "New File", @path.presence, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
- unless Feature.enabled?(:monaco_blobs)
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
.editor-title-row
%h3.page-title.blob-new-page-title
New file
......
---
title: Move Usage activity by stage for Configure to Core
merge_request: 33672
author:
type: changed
---
title: Update pinned links to use GlButton
merge_request: 34620
author:
type: other
---
title: Assign plan_id when building a new plan limit
merge_request: 34845
author:
type: fixed
---
title: Use GpgKeys::CreateService when an admin creates a new GPG key for a user
merge_request: 34737
author: Rajendra Kadam
type: fixed
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать