diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 5e66aa05218..345dfaf895b 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -283,9 +283,9 @@ function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagCo /* eslint-disable @gitlab/require-i18n-strings */ export function keypressNoteText(e) { - if (this.selectionStart === this.selectionEnd) { - return; - } + if (!gon.markdown_surround_selection) return; + if (this.selectionStart === this.selectionEnd) return; + const keys = { '*': '**{text}**', // wraps with bold character _: '_{text}_', // wraps with italic character diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index add5046e213..45bab5f6cd1 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -49,7 +49,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController :tab_width, :sourcegraph_enabled, :gitpod_enabled, - :render_whitespace_in_code + :render_whitespace_in_code, + :markdown_surround_selection ] end end diff --git a/app/models/user.rb b/app/models/user.rb index 4c2bef2c725..9b288d99a7f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -294,6 +294,7 @@ class User < ApplicationRecord :setup_for_company, :setup_for_company=, :render_whitespace_in_code, :render_whitespace_in_code=, :experience_level, :experience_level=, + :markdown_surround_selection, :markdown_surround_selection=, to: :user_preference delegate :path, to: :namespace, allow_nil: true, prefix: true diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index 49b93ffaf66..0bf8c8f901d 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -27,6 +27,7 @@ class UserPreference < ApplicationRecord default_value_for :time_display_relative, value: true, allows_nil: false default_value_for :time_format_in_24h, value: false, allows_nil: false default_value_for :render_whitespace_in_code, value: false, allows_nil: false + default_value_for :markdown_surround_selection, value: true, allows_nil: false class << self def notes_filters diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index cd76a67b692..535964028f4 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -88,6 +88,15 @@ = s_("Preferences|Show one file at a time on merge request's Changes tab") .form-text.text-muted = s_("Preferences|Instead of all the files changed, show only one file at a time. To switch between files, use the file browser.") + .form-group.form-check + = f.check_box :markdown_surround_selection, class: 'form-check-input' + = f.label :markdown_surround_selection, class: 'form-check-label' do + = s_('Preferences|Surround text selection when typing quotes or brackets') + .form-text.text-muted + - supported_characters = %w(" ' ` \( [ { < * _).map {|char| "#{char}" }.join(', ') + - msg = "Preferences|When you type in a description or comment box, selected text is surrounded by the corresponding character after typing one of the following characters: #{supported_characters}." + = s_(msg).html_safe + .form-group = f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold' = f.number_field :tab_width, diff --git a/changelogs/unreleased/247918-add-user-preference-to-turn-off-keystroke-formatting.yml b/changelogs/unreleased/247918-add-user-preference-to-turn-off-keystroke-formatting.yml new file mode 100644 index 00000000000..a68e6c67e6b --- /dev/null +++ b/changelogs/unreleased/247918-add-user-preference-to-turn-off-keystroke-formatting.yml @@ -0,0 +1,5 @@ +--- +title: Add user preference to turn off selected text keystroke formatting +merge_request: 53079 +author: +type: added diff --git a/db/migrate/20210209232508_add_markdown_surround_selection_to_user_preferences.rb b/db/migrate/20210209232508_add_markdown_surround_selection_to_user_preferences.rb new file mode 100644 index 00000000000..c4063a55d18 --- /dev/null +++ b/db/migrate/20210209232508_add_markdown_surround_selection_to_user_preferences.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class AddMarkdownSurroundSelectionToUserPreferences < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + with_lock_retries do + add_column :user_preferences, :markdown_surround_selection, :boolean, default: true, null: false + end + end + + def down + with_lock_retries do + remove_column :user_preferences, :markdown_surround_selection, :boolean + end + end +end diff --git a/db/schema_migrations/20210209232508 b/db/schema_migrations/20210209232508 new file mode 100644 index 00000000000..d424fff0469 --- /dev/null +++ b/db/schema_migrations/20210209232508 @@ -0,0 +1 @@ +484751de711e873e0f0f22d5951e36bf60d4826ebc95afa45e4f6cdaa0e3c024 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 38f79b4810f..7d10a327477 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17928,7 +17928,8 @@ CREATE TABLE user_preferences ( tab_width smallint, experience_level smallint, view_diffs_file_by_file boolean DEFAULT false NOT NULL, - gitpod_enabled boolean DEFAULT false NOT NULL + gitpod_enabled boolean DEFAULT false NOT NULL, + markdown_surround_selection boolean DEFAULT true NOT NULL ); CREATE SEQUENCE user_preferences_id_seq diff --git a/doc/administration/object_storage.md b/doc/administration/object_storage.md index a14f902db91..85c3a4aa767 100644 --- a/doc/administration/object_storage.md +++ b/doc/administration/object_storage.md @@ -31,6 +31,8 @@ GitLab has been tested on a number of object storage providers: HTTP Range Requests from working with CI job artifacts](https://gitlab.com/gitlab-org/gitlab/-/issues/223806). Be sure to upgrade to GitLab v13.3.0 or above if you use S3 storage with this hardware. +- Ceph S3 prior to [Kraken 11.0.2](https://ceph.com/releases/kraken-11-0-2-released/) does not support the [Upload Copy Part API](https://gitlab.com/gitlab-org/gitlab/-/issues/300604). You may need to [disable multi-threaded copying](#multi-threaded-copying). + ## Configuration guides There are two ways of specifying object storage configuration in GitLab: @@ -737,7 +739,22 @@ following command: Feature.disable(:use_workhorse_s3_client) ``` -#### IAM Permissions +### Multi-threaded copying + +GitLab uses the [S3 Upload Part Copy API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html) +to accelerate the copying of files within a bucket. Ceph S3 [prior to Kraken 11.0.2](https://ceph.com/releases/kraken-11-0-2-released/) +does not support this and [returns a 404 error when files are copied during the upload process](https://gitlab.com/gitlab-org/gitlab/-/issues/300604). + +The feature can be disabled using the `:s3_multithreaded_uploads` +feature flag. To disable the feature, ask a GitLab administrator with +[Rails console access](feature_flags.md#how-to-enable-and-disable-features-behind-flags) +to run the following command: + +```ruby +Feature.disable(:s3_multithreaded_uploads) +``` + +### IAM Permissions To set up an instance profile: @@ -754,7 +771,6 @@ To set up an instance profile: "Action": [ "s3:PutObject", "s3:GetObject", - "s3:AbortMultipartUpload", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::test-bucket/*" diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 3dd317c5a64..c7e215c143f 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -7,13 +7,14 @@ module Gitlab include WebpackHelper def add_gon_variables - gon.api_version = 'v4' - gon.default_avatar_url = default_avatar_url - gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size - gon.asset_host = ActionController::Base.asset_host - gon.webpack_public_path = webpack_public_path - gon.relative_url_root = Gitlab.config.gitlab.relative_url_root - gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class + gon.api_version = 'v4' + gon.default_avatar_url = default_avatar_url + gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size + gon.asset_host = ActionController::Base.asset_host + gon.webpack_public_path = webpack_public_path + gon.relative_url_root = Gitlab.config.gitlab.relative_url_root + gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class + gon.markdown_surround_selection = current_user&.markdown_surround_selection if Gitlab.config.sentry.enabled gon.sentry_dsn = Gitlab.config.sentry.clientside_dsn diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5bd1a5384fe..d4505fc97cc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22615,6 +22615,9 @@ msgstr "" msgid "Preferences|Sourcegraph" msgstr "" +msgid "Preferences|Surround text selection when typing quotes or brackets" +msgstr "" + msgid "Preferences|Syntax highlighting theme" msgstr "" diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index 43de195c702..b538257fac0 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -171,27 +171,40 @@ describe('init markdown', () => { expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`)); }); - it.each` - key | expected - ${'['} | ${`[${selected}]`} - ${'*'} | ${`**${selected}**`} - ${"'"} | ${`'${selected}'`} - ${'_'} | ${`_${selected}_`} - ${'`'} | ${`\`${selected}\``} - ${'"'} | ${`"${selected}"`} - ${'{'} | ${`{${selected}}`} - ${'('} | ${`(${selected})`} - ${'<'} | ${`<${selected}>`} - `('generates $expected when $key is pressed', ({ key, expected }) => { - const event = new KeyboardEvent('keydown', { key }); + describe('surrounds selected text with matching character', () => { + it.each` + key | expected + ${'['} | ${`[${selected}]`} + ${'*'} | ${`**${selected}**`} + ${"'"} | ${`'${selected}'`} + ${'_'} | ${`_${selected}_`} + ${'`'} | ${`\`${selected}\``} + ${'"'} | ${`"${selected}"`} + ${'{'} | ${`{${selected}}`} + ${'('} | ${`(${selected})`} + ${'<'} | ${`<${selected}>`} + `('generates $expected when $key is pressed', ({ key, expected }) => { + const event = new KeyboardEvent('keydown', { key }); + gon.markdown_surround_selection = true; - textArea.addEventListener('keydown', keypressNoteText); - textArea.dispatchEvent(event); + textArea.addEventListener('keydown', keypressNoteText); + textArea.dispatchEvent(event); - expect(textArea.value).toEqual(text.replace(selected, expected)); + expect(textArea.value).toEqual(text.replace(selected, expected)); - // cursor placement should be after selection + 2 tag lengths - expect(textArea.selectionStart).toBe(selectedIndex + expected.length); + // cursor placement should be after selection + 2 tag lengths + expect(textArea.selectionStart).toBe(selectedIndex + expected.length); + }); + + it('does nothing if user preference disabled', () => { + const event = new KeyboardEvent('keydown', { key: '[' }); + gon.markdown_surround_selection = false; + + textArea.addEventListener('keydown', keypressNoteText); + textArea.dispatchEvent(event); + + expect(textArea.value).toEqual(text); + }); }); describe('and text to be selected', () => { diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a84421f6d2c..f0981469123 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -41,6 +41,9 @@ RSpec.describe User do it { is_expected.to delegate_method(:show_whitespace_in_diffs).to(:user_preference) } it { is_expected.to delegate_method(:show_whitespace_in_diffs=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:view_diffs_file_by_file).to(:user_preference) } + it { is_expected.to delegate_method(:view_diffs_file_by_file=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:tab_width).to(:user_preference) } it { is_expected.to delegate_method(:tab_width=).to(:user_preference).with_arguments(:args) } @@ -59,6 +62,9 @@ RSpec.describe User do it { is_expected.to delegate_method(:experience_level).to(:user_preference) } it { is_expected.to delegate_method(:experience_level=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:markdown_surround_selection).to(:user_preference) } + it { is_expected.to delegate_method(:markdown_surround_selection=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:job_title).to(:user_detail).allow_nil } it { is_expected.to delegate_method(:job_title=).to(:user_detail).with_arguments(:args).allow_nil }