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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-14 00:08:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-14 00:08:20 +0300
commit87731a5333142f369829341116ab2d4445e47d66 (patch)
tree7fa601e799d5e06c2d3eb79543bf4b22522ea3ce /app
parente9606d7f51144274f9a390c9dd683200daab8eed (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_table_button.vue91
-rw-r--r--app/assets/javascripts/content_editor/components/top_toolbar.vue6
-rw-r--r--app/assets/javascripts/content_editor/extensions/table.js7
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_cell.js9
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_header.js9
-rw-r--r--app/assets/javascripts/content_editor/extensions/table_row.js51
-rw-r--r--app/assets/javascripts/content_editor/services/create_content_editor.js8
-rw-r--r--app/assets/javascripts/content_editor/services/utils.js2
-rw-r--r--app/assets/javascripts/jobs/utils.js6
-rw-r--r--app/assets/javascripts/token_access/components/token_access.vue15
-rw-r--r--app/models/ci/build_trace_chunk.rb14
-rw-r--r--app/views/groups/milestones/_form.html.haml38
-rw-r--r--app/views/projects/milestones/_form.html.haml40
-rw-r--r--app/views/shared/milestones/_form_dates.html.haml24
14 files changed, 240 insertions, 80 deletions
diff --git a/app/assets/javascripts/content_editor/components/toolbar_table_button.vue b/app/assets/javascripts/content_editor/components/toolbar_table_button.vue
new file mode 100644
index 00000000000..08e3b250832
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/toolbar_table_button.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlDropdown, GlDropdownDivider, GlDropdownForm, GlButton } from '@gitlab/ui';
+import { Editor as TiptapEditor } from '@tiptap/vue-2';
+import { __, sprintf } from '~/locale';
+import { clamp } from '../services/utils';
+
+export const tableContentType = 'table';
+
+const MIN_ROWS = 3;
+const MIN_COLS = 3;
+const MAX_ROWS = 8;
+const MAX_COLS = 8;
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownForm,
+ GlButton,
+ },
+ props: {
+ tiptapEditor: {
+ type: TiptapEditor,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ maxRows: MIN_ROWS,
+ maxCols: MIN_COLS,
+ rows: 1,
+ cols: 1,
+ };
+ },
+ methods: {
+ list(n) {
+ return new Array(n).fill().map((_, i) => i + 1);
+ },
+ setRowsAndCols(rows, cols) {
+ this.rows = rows;
+ this.cols = cols;
+ this.maxRows = clamp(rows + 1, MIN_ROWS, MAX_ROWS);
+ this.maxCols = clamp(cols + 1, MIN_COLS, MAX_COLS);
+ },
+ resetState() {
+ this.rows = 1;
+ this.cols = 1;
+ },
+ insertTable() {
+ this.tiptapEditor
+ .chain()
+ .focus()
+ .insertTable({
+ rows: this.rows,
+ cols: this.cols,
+ withHeaderRow: true,
+ })
+ .run();
+
+ this.resetState();
+
+ this.$emit('execute', { contentType: 'table' });
+ },
+ getButtonLabel(rows, cols) {
+ return sprintf(__('Insert a %{rows}x%{cols} table.'), { rows, cols });
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown size="small" category="tertiary" icon="table">
+ <gl-dropdown-form class="gl-px-3! gl-w-auto!">
+ <div class="gl-w-auto!">
+ <div v-for="c of list(maxCols)" :key="c" class="gl-display-flex">
+ <gl-button
+ v-for="r of list(maxRows)"
+ :key="r"
+ :data-testid="`table-${r}-${c}`"
+ :class="{ 'gl-bg-blue-50!': r <= rows && c <= cols }"
+ :aria-label="getButtonLabel(r, c)"
+ class="gl-display-inline! gl-px-0! gl-w-5! gl-h-5! gl-rounded-0!"
+ @mouseover="setRowsAndCols(r, c)"
+ @click="insertTable()"
+ />
+ </div>
+ <gl-dropdown-divider />
+ {{ getButtonLabel(rows, cols) }}
+ </div>
+ </gl-dropdown-form>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue
index 398a9610fb5..e1bc26d50fc 100644
--- a/app/assets/javascripts/content_editor/components/top_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue
@@ -5,6 +5,7 @@ import { ContentEditor } from '../services/content_editor';
import Divider from './divider.vue';
import ToolbarButton from './toolbar_button.vue';
import ToolbarLinkButton from './toolbar_link_button.vue';
+import ToolbarTableButton from './toolbar_table_button.vue';
import ToolbarTextStyleDropdown from './toolbar_text_style_dropdown.vue';
const trackingMixin = Tracking.mixin({
@@ -16,6 +17,7 @@ export default {
ToolbarButton,
ToolbarTextStyleDropdown,
ToolbarLinkButton,
+ ToolbarTableButton,
Divider,
},
mixins: [trackingMixin],
@@ -132,5 +134,9 @@ export default {
:tiptap-editor="contentEditor.tiptapEditor"
@execute="trackToolbarControlExecution"
/>
+ <toolbar-table-button
+ :tiptap-editor="contentEditor.tiptapEditor"
+ @execute="trackToolbarControlExecution"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/content_editor/extensions/table.js b/app/assets/javascripts/content_editor/extensions/table.js
new file mode 100644
index 00000000000..566f7a21a85
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/table.js
@@ -0,0 +1,7 @@
+import { Table } from '@tiptap/extension-table';
+
+export const tiptapExtension = Table;
+
+export function serializer(state, node) {
+ state.renderContent(node);
+}
diff --git a/app/assets/javascripts/content_editor/extensions/table_cell.js b/app/assets/javascripts/content_editor/extensions/table_cell.js
new file mode 100644
index 00000000000..6c25b867466
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/table_cell.js
@@ -0,0 +1,9 @@
+import { TableCell } from '@tiptap/extension-table-cell';
+
+export const tiptapExtension = TableCell.extend({
+ content: 'inline*',
+});
+
+export function serializer(state, node) {
+ state.renderInline(node);
+}
diff --git a/app/assets/javascripts/content_editor/extensions/table_header.js b/app/assets/javascripts/content_editor/extensions/table_header.js
new file mode 100644
index 00000000000..3475857b9e6
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/table_header.js
@@ -0,0 +1,9 @@
+import { TableHeader } from '@tiptap/extension-table-header';
+
+export const tiptapExtension = TableHeader.extend({
+ content: 'inline*',
+});
+
+export function serializer(state, node) {
+ state.renderInline(node);
+}
diff --git a/app/assets/javascripts/content_editor/extensions/table_row.js b/app/assets/javascripts/content_editor/extensions/table_row.js
new file mode 100644
index 00000000000..07d2eb4faa2
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/table_row.js
@@ -0,0 +1,51 @@
+import { TableRow } from '@tiptap/extension-table-row';
+
+export const tiptapExtension = TableRow.extend({
+ allowGapCursor: false,
+});
+
+export function serializer(state, node) {
+ const isHeaderRow = node.child(0).type.name === 'tableHeader';
+
+ const renderRow = () => {
+ const cellWidths = [];
+
+ state.flushClose(1);
+
+ state.write('| ');
+ node.forEach((cell, _, i) => {
+ if (i) state.write(' | ');
+
+ const { length } = state.out;
+ state.render(cell, node, i);
+ cellWidths.push(state.out.length - length);
+ });
+ state.write(' |');
+
+ state.closeBlock(node);
+
+ return cellWidths;
+ };
+
+ const renderHeaderRow = (cellWidths) => {
+ state.flushClose(1);
+
+ state.write('|');
+ node.forEach((cell, _, i) => {
+ if (i) state.write('|');
+
+ state.write(cell.attrs.align === 'center' ? ':' : '-');
+ state.write(state.repeat('-', cellWidths[i]));
+ state.write(cell.attrs.align === 'center' || cell.attrs.align === 'right' ? ':' : '-');
+ });
+ state.write('|');
+
+ state.closeBlock(node);
+ };
+
+ if (isHeaderRow) {
+ renderHeaderRow(renderRow());
+ } else {
+ renderRow();
+ }
+}
diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js
index 03ddae2aa7c..9251fdbbdc5 100644
--- a/app/assets/javascripts/content_editor/services/create_content_editor.js
+++ b/app/assets/javascripts/content_editor/services/create_content_editor.js
@@ -20,6 +20,10 @@ import * as ListItem from '../extensions/list_item';
import * as OrderedList from '../extensions/ordered_list';
import * as Paragraph from '../extensions/paragraph';
import * as Strike from '../extensions/strike';
+import * as Table from '../extensions/table';
+import * as TableCell from '../extensions/table_cell';
+import * as TableHeader from '../extensions/table_header';
+import * as TableRow from '../extensions/table_row';
import * as Text from '../extensions/text';
import buildSerializerConfig from './build_serializer_config';
import { ContentEditor } from './content_editor';
@@ -70,6 +74,10 @@ export const createContentEditor = ({
OrderedList,
Paragraph,
Strike,
+ TableCell,
+ TableHeader,
+ TableRow,
+ Table,
Text,
];
diff --git a/app/assets/javascripts/content_editor/services/utils.js b/app/assets/javascripts/content_editor/services/utils.js
index ca2f9762ff8..2a2c7f617da 100644
--- a/app/assets/javascripts/content_editor/services/utils.js
+++ b/app/assets/javascripts/content_editor/services/utils.js
@@ -15,3 +15,5 @@ export const readFileAsDataURL = (file) => {
reader.readAsDataURL(file);
});
};
+
+export const clamp = (n, min, max) => Math.max(Math.min(n, max), min);
diff --git a/app/assets/javascripts/jobs/utils.js b/app/assets/javascripts/jobs/utils.js
index 122f23a5bb5..1ccecf3eb53 100644
--- a/app/assets/javascripts/jobs/utils.js
+++ b/app/assets/javascripts/jobs/utils.js
@@ -3,10 +3,10 @@
* https?:\/\/
*
* up until a disallowed character or whitespace
- * [^"<>\\^`{|}\s]+
+ * [^"<>()\\^`{|}\s]+
*
* and a disallowed character or whitespace, including non-ending chars .,:;!?
- * [^"<>\\^`{|}\s.,:;!?]
+ * [^"<>()\\^`{|}\s.,:;!?]
*/
-export const linkRegex = /(https?:\/\/[^"<>\\^`{|}\s]+[^"<>\\^`{|}\s.,:;!?])/g;
+export const linkRegex = /(https?:\/\/[^"<>()\\^`{|}\s]+[^"<>()\\^`{|}\s.,:;!?])/g;
export default { linkRegex };
diff --git a/app/assets/javascripts/token_access/components/token_access.vue b/app/assets/javascripts/token_access/components/token_access.vue
index 0229d226258..24565c441d8 100644
--- a/app/assets/javascripts/token_access/components/token_access.vue
+++ b/app/assets/javascripts/token_access/components/token_access.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlCard, GlFormGroup, GlFormInput, GlLoadingIcon, GlToggle } from '@gitlab/ui';
+import { GlButton, GlCard, GlFormInput, GlLoadingIcon, GlToggle } from '@gitlab/ui';
import createFlash from '~/flash';
import { __, s__ } from '~/locale';
import addProjectCIJobTokenScopeMutation from '../graphql/mutations/add_project_ci_job_token_scope.mutation.graphql';
@@ -16,7 +16,6 @@ export default {
`CICD|Select projects that can be accessed by API requests authenticated with this project's CI_JOB_TOKEN CI/CD variable.`,
),
cardHeaderTitle: s__('CICD|Add an existing project to the scope'),
- formGroupLabel: __('Search for project'),
addProject: __('Add project'),
cancel: __('Cancel'),
addProjectPlaceholder: __('Paste project path (i.e. gitlab-org/gitlab)'),
@@ -26,7 +25,6 @@ export default {
components: {
GlButton,
GlCard,
- GlFormGroup,
GlFormInput,
GlLoadingIcon,
GlToggle,
@@ -183,13 +181,10 @@ export default {
<h5 class="gl-my-0">{{ $options.i18n.cardHeaderTitle }}</h5>
</template>
<template #default>
- <gl-form-group :label="$options.i18n.formGroupLabel" label-for="token-project-search">
- <gl-form-input
- id="token-project-search"
- v-model="targetProjectPath"
- :placeholder="$options.i18n.addProjectPlaceholder"
- />
- </gl-form-group>
+ <gl-form-input
+ v-model="targetProjectPath"
+ :placeholder="$options.i18n.addProjectPlaceholder"
+ />
</template>
<template #footer>
<gl-button
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index 581dcf5c1a1..3fa9a484b0c 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -14,19 +14,7 @@ module Ci
belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id
- default_value_for :data_store do |chunk|
- # We're using the safe operator here to get to the project for which we're
- # creating a TraceChunk because the build attribute would not be populated
- # when the chunk was initialized by FactoryBot:
- # https://github.com/thoughtbot/factory_bot/wiki/How-factory_bot-interacts-with-ActiveRecord
- # While the `default_value_for` gem depends on an `after_initialize`
- # callback.
- if Feature.enabled?(:dedicated_redis_trace_chunks, chunk.build&.project, type: :ops)
- :redis_trace_chunks
- else
- :redis
- end
- end
+ default_value_for :data_store, :redis_trace_chunks
after_create { metrics.increment_trace_operation(operation: :chunked) }
diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml
index 259e96901fd..a6302569187 100644
--- a/app/views/groups/milestones/_form.html.haml
+++ b/app/views/groups/milestones/_form.html.haml
@@ -1,25 +1,23 @@
= form_for [@group, @milestone], html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
= form_errors(@milestone)
- .row
- .col-md-6
- .form-group.row
- .col-form-label.col-sm-2
- = f.label :title, _("Title")
- .col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control", data: { qa_selector: "milestone_title_field" }, required: true, autofocus: true
- .form-group.row.milestone-description
- .col-form-label.col-sm-2
- = f.label :description, _("Description")
- .col-sm-10
- = render layout: 'shared/md_preview', locals: { url: group_preview_markdown_path } do
- = render 'shared/zen', f: f, attr: :description,
- classes: 'note-textarea',
- qa_selector: 'milestone_description_field',
- supports_autocomplete: true,
- placeholder: _('Write milestone description...')
- .clearfix
- .error-alert
- = render "shared/milestones/form_dates", f: f
+ .form-group.row
+ .col-form-label.col-sm-2
+ = f.label :title, _("Title")
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, class: "form-control", data: { qa_selector: "milestone_title_field" }, required: true, autofocus: true
+ = render "shared/milestones/form_dates", f: f
+ .form-group.row.milestone-description
+ .col-form-label.col-sm-2
+ = f.label :description, _("Description")
+ .col-sm-10
+ = render layout: 'shared/md_preview', locals: { url: group_preview_markdown_path } do
+ = render 'shared/zen', f: f, attr: :description,
+ classes: 'note-textarea',
+ qa_selector: 'milestone_description_field',
+ supports_autocomplete: true,
+ placeholder: _('Write milestone description...')
+ .clearfix
+ .error-alert
.form-actions
- if @milestone.new_record?
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index dfb9defb91c..5f2057df4aa 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,27 +1,25 @@
= form_for [@project, @milestone],
html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
= form_errors(@milestone)
- .row
- .col-md-6
- .form-group.row
- .col-form-label.col-sm-2
- = f.label :title, _('Title')
- .col-sm-10
- = f.text_field :title, maxlength: 255, class: 'form-control gl-form-input', data: { qa_selector: 'milestone_title_field' }, required: true, autofocus: true
- .form-group.row.milestone-description
- .col-form-label.col-sm-2
- = f.label :description, _('Description')
- .col-sm-10
- = render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project) } do
- = render 'shared/zen', f: f, attr: :description,
- classes: 'note-textarea',
- qa_selector: 'milestone_description_field',
- supports_autocomplete: true,
- placeholder: _('Write milestone description...')
- = render 'shared/notes/hints'
- .clearfix
- .error-alert
- = render 'shared/milestones/form_dates', f: f
+ .form-group.row
+ .col-form-label.col-sm-2
+ = f.label :title, _('Title')
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, class: 'form-control gl-form-input', data: { qa_selector: 'milestone_title_field' }, required: true, autofocus: true
+ = render 'shared/milestones/form_dates', f: f
+ .form-group.row.milestone-description
+ .col-form-label.col-sm-2
+ = f.label :description, _('Description')
+ .col-sm-10
+ = render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project) } do
+ = render 'shared/zen', f: f, attr: :description,
+ classes: 'note-textarea',
+ qa_selector: 'milestone_description_field',
+ supports_autocomplete: true,
+ placeholder: _('Write milestone description...')
+ = render 'shared/notes/hints'
+ .clearfix
+ .error-alert
.form-actions
- if @milestone.new_record?
diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml
index e0664c1feba..7a41e381a96 100644
--- a/app/views/shared/milestones/_form_dates.html.haml
+++ b/app/views/shared/milestones/_form_dates.html.haml
@@ -1,13 +1,11 @@
-.col-md-6
- .form-group.row
- .col-form-label.col-sm-2
- = f.label :start_date, _('Start Date')
- .col-sm-10
- = f.text_field :start_date, class: "datepicker form-control gl-form-input", data: { qa_selector: "start_date_field" }, placeholder: _('Select start date'), autocomplete: 'off'
- %a.inline.float-right.gl-mt-2.js-clear-start-date{ href: "#" }= _('Clear start date')
- .form-group.row
- .col-form-label.col-sm-2
- = f.label :due_date, _('Due Date')
- .col-sm-10
- = f.text_field :due_date, class: "datepicker form-control gl-form-input", data: { qa_selector: "due_date_field" }, placeholder: _('Select due date'), autocomplete: 'off'
- %a.inline.float-right.gl-mt-2.js-clear-due-date{ href: "#" }= _('Clear due date')
+.form-group.row
+ .col-form-label.col-sm-2
+ = f.label :start_date, _('Start Date')
+ .col-sm-4
+ = f.text_field :start_date, class: "datepicker form-control gl-form-input", data: { qa_selector: "start_date_field" }, placeholder: _('Select start date'), autocomplete: 'off'
+ %a.inline.float-right.gl-mt-2.js-clear-start-date{ href: "#" }= _('Clear start date')
+ .col-form-label.col-sm-2
+ = f.label :due_date, _('Due Date')
+ .col-sm-4
+ = f.text_field :due_date, class: "datepicker form-control gl-form-input", data: { qa_selector: "due_date_field" }, placeholder: _('Select due date'), autocomplete: 'off'
+ %a.inline.float-right.gl-mt-2.js-clear-due-date{ href: "#" }= _('Clear due date')