diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/content_editor/constants.js | 7 | ||||
-rw-r--r-- | app/assets/javascripts/content_editor/extensions/code.js | 13 | ||||
-rw-r--r-- | app/assets/javascripts/content_editor/extensions/frontmatter.js | 11 | ||||
-rw-r--r-- | app/assets/javascripts/content_editor/services/markdown_serializer.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/labels/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/projects/project_visibility.js | 21 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/labels.scss | 5 | ||||
-rw-r--r-- | app/models/ci/secure_file.rb | 33 | ||||
-rw-r--r-- | app/models/project.rb | 1 | ||||
-rw-r--r-- | app/uploaders/ci/secure_file_uploader.rb | 46 | ||||
-rw-r--r-- | app/views/shared/_label.html.haml | 32 |
11 files changed, 147 insertions, 26 deletions
diff --git a/app/assets/javascripts/content_editor/constants.js b/app/assets/javascripts/content_editor/constants.js index 4af9dc8e405..5e56078df01 100644 --- a/app/assets/javascripts/content_editor/constants.js +++ b/app/assets/javascripts/content_editor/constants.js @@ -49,3 +49,10 @@ export const LOADING_ERROR_EVENT = 'loadingError'; export const PARSE_HTML_PRIORITY_LOWEST = 1; export const PARSE_HTML_PRIORITY_DEFAULT = 50; export const PARSE_HTML_PRIORITY_HIGHEST = 100; + +export const EXTENSION_PRIORITY_LOWER = 75; +/** + * 100 is the default priority in Tiptap + * https://tiptap.dev/guide/custom-extensions/#priority + */ +export const EXTENSION_PRIORITY_DEFAULT = 100; diff --git a/app/assets/javascripts/content_editor/extensions/code.js b/app/assets/javascripts/content_editor/extensions/code.js index f93c22ad10e..53f6d9b995c 100644 --- a/app/assets/javascripts/content_editor/extensions/code.js +++ b/app/assets/javascripts/content_editor/extensions/code.js @@ -1 +1,12 @@ -export { Code as default } from '@tiptap/extension-code'; +import Code from '@tiptap/extension-code'; +import { EXTENSION_PRIORITY_LOWER } from '../constants'; + +export default Code.extend({ + excludes: null, + /** + * Reduce the rendering priority of the code mark to + * ensure the bold, italic, and strikethrough marks + * are rendered first. + */ + priority: EXTENSION_PRIORITY_LOWER, +}); diff --git a/app/assets/javascripts/content_editor/extensions/frontmatter.js b/app/assets/javascripts/content_editor/extensions/frontmatter.js index c09c10bc524..9842027e192 100644 --- a/app/assets/javascripts/content_editor/extensions/frontmatter.js +++ b/app/assets/javascripts/content_editor/extensions/frontmatter.js @@ -14,9 +14,20 @@ export default CodeBlockHighlight.extend({ }, ]; }, + addCommands() { + return { + setFrontmatter: (attributes) => ({ commands }) => { + return commands.setNode(this.name, attributes); + }, + toggleFrontmatter: (attributes) => ({ commands }) => { + return commands.toggleNode(this.name, 'paragraph', attributes); + }, + }; + }, addNodeView() { return new VueNodeViewRenderer(FrontmatterWrapper); }, + addInputRules() { return []; }, diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 278ef326c7a..d54fb7cded2 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -65,8 +65,8 @@ import { const defaultSerializerConfig = { marks: { [Bold.name]: defaultMarkdownSerializer.marks.strong, - [Code.name]: defaultMarkdownSerializer.marks.code, [Italic.name]: { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true }, + [Code.name]: defaultMarkdownSerializer.marks.code, [Subscript.name]: { open: '<sub>', close: '</sub>', mixable: true }, [Superscript.name]: { open: '<sup>', close: '</sup>', mixable: true }, [InlineDiff.name]: { diff --git a/app/assets/javascripts/labels/index.js b/app/assets/javascripts/labels/index.js index 22a9c0a89c0..e87ad8d9a06 100644 --- a/app/assets/javascripts/labels/index.js +++ b/app/assets/javascripts/labels/index.js @@ -26,7 +26,7 @@ export function initLabels() { if ($('.prioritized-labels').length) { new LabelManager(); // eslint-disable-line no-new } - $('.label-subscription').each((i, el) => { + $('.js-label-subscription').each((i, el) => { const $el = $(el); if ($el.find('.dropdown-group-label').length) { diff --git a/app/assets/javascripts/projects/project_visibility.js b/app/assets/javascripts/projects/project_visibility.js index 1b57a69d464..c962554c9f4 100644 --- a/app/assets/javascripts/projects/project_visibility.js +++ b/app/assets/javascripts/projects/project_visibility.js @@ -1,4 +1,6 @@ import $ from 'jquery'; +import { escape } from 'lodash'; +import { __, sprintf } from '~/locale'; import eventHub from '~/projects/new/event_hub'; // Values are from lib/gitlab/visibility_level.rb @@ -25,10 +27,21 @@ function setVisibilityOptions({ name, visibility, showPath, editPath }) { if (reason) { const optionTitle = option.querySelector('.option-title'); const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : ''; - reason.innerHTML = `This project cannot be ${optionName} because the visibility of - <a href="${showPath}">${name}</a> is ${visibility}. To make this project - ${optionName}, you must first <a href="${editPath}">change the visibility</a> - of the parent group.`; + reason.innerHTML = sprintf( + __( + 'This project cannot be %{visibilityLevel} because the visibility of %{openShowLink}%{name}%{closeShowLink} is %{visibility}. To make this project %{visibilityLevel}, you must first %{openEditLink}change the visibility%{closeEditLink} of the parent group.', + ), + { + visibilityLevel: optionName, + name: escape(name), + visibility, + openShowLink: `<a href="${showPath}">`, + closeShowLink: '</a>', + openEditLink: `<a href="${editPath}">`, + closeEditLink: '</a>', + }, + false, + ); } } else { option.classList.remove('disabled'); diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 08949578f46..82216b8d5c5 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -107,9 +107,8 @@ display: none; } -.label-subscribe-button { - width: 105px; - font-weight: 200; +.label-subscription { + width: 109px; } .labels-container { diff --git a/app/models/ci/secure_file.rb b/app/models/ci/secure_file.rb new file mode 100644 index 00000000000..56f632b6232 --- /dev/null +++ b/app/models/ci/secure_file.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Ci + class SecureFile < Ci::ApplicationRecord + include FileStoreMounter + + FILE_SIZE_LIMIT = 5.megabytes.freeze + CHECKSUM_ALGORITHM = 'sha256' + + belongs_to :project, optional: false + + validates :file, presence: true, file_size: { maximum: FILE_SIZE_LIMIT } + validates :checksum, :file_store, :name, :permissions, :project_id, presence: true + + before_validation :assign_checksum + + enum permissions: { read_only: 0, read_write: 1, execute: 2 } + + default_value_for(:file_store) { Ci::SecureFileUploader.default_store } + + mount_file_store_uploader Ci::SecureFileUploader + + def checksum_algorithm + CHECKSUM_ALGORITHM + end + + private + + def assign_checksum + self.checksum = file.checksum if file.present? && file_changed? + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 7e2c98c85b3..81ee1c1fe55 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -340,6 +340,7 @@ class Project < ApplicationRecord has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :variables, class_name: 'Ci::Variable' has_many :triggers, class_name: 'Ci::Trigger' + has_many :secure_files, class_name: 'Ci::SecureFile' has_many :environments has_many :environments_for_dashboard, -> { from(with_rank.unfoldered.available, :environments).where('rank <= 3') }, class_name: 'Environment' has_many :deployments diff --git a/app/uploaders/ci/secure_file_uploader.rb b/app/uploaders/ci/secure_file_uploader.rb new file mode 100644 index 00000000000..514d88dd177 --- /dev/null +++ b/app/uploaders/ci/secure_file_uploader.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Ci + class SecureFileUploader < GitlabUploader + include ObjectStorage::Concern + + storage_options Gitlab.config.ci_secure_files + + # Use Lockbox to encrypt/decrypt the stored file (registers CarrierWave callbacks) + encrypt(key: :key) + + def key + OpenSSL::HMAC.digest('SHA256', Gitlab::Application.secrets.db_key_base, model.project_id.to_s) + end + + def checksum + @checksum ||= Digest::SHA256.hexdigest(model.file.read) + end + + def store_dir + dynamic_segment + end + + private + + def dynamic_segment + Gitlab::HashedPath.new('secure_files', model.id, root_hash: model.project_id) + end + + class << self + # direct upload is disabled since the file + # must always be encrypted + def direct_upload_enabled? + false + end + + def background_upload_enabled? + false + end + + def default_store + object_store_enabled? ? ObjectStorage::Store::REMOTE : ObjectStorage::Store::LOCAL + end + end + end +end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index e4ef0a52eba..9428813f6b0 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -10,18 +10,18 @@ = render "shared/label_row", label: label, force_priority: force_priority %ul.label-actions-list - if can?(current_user, :admin_label, @project) - %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), + %li.gl-display-inline-block.js-toggle-priority.gl-ml-3{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } - %button.add-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip.gl-ml-3{ title: _('Prioritize'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Prioritize label') } + %button.add-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip{ title: _('Prioritize'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Prioritize label') } = sprite_icon('star-o') - %button.remove-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip.gl-ml-3{ title: _('Remove priority'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Deprioritize label') } + %button.remove-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip{ title: _('Remove priority'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Deprioritize label') } = sprite_icon('star') - if can?(current_user, :admin_label, label) - %li.inline + %li.gl-display-inline-block = link_to label.edit_path, class: 'btn gl-button btn-default-tertiary btn-sm edit has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria_label: _('Edit') do = sprite_icon('pencil') - if can?(current_user, :admin_label, label) - %li.inline + %li.gl-display-inline-block .dropdown %button{ type: 'button', class: 'btn gl-button btn-default-tertiary btn-sm js-label-options-dropdown', data: { toggle: 'dropdown' }, aria_label: _('Label actions dropdown') } = sprite_icon('ellipsis_v') @@ -41,23 +41,23 @@ %button.text-danger.js-delete-label-modal-button{ type: 'button', data: { label_name: label.name, subject_name: label.subject_name, destroy_path: label.destroy_path } } = _('Delete') - if current_user - %li.inline.label-subscription + %li.gl-display-inline-block.label-subscription.js-label-subscription.gl-ml-3 - if label.can_subscribe_to_label_in_different_levels? - %button.js-unsubscribe-button.gl-button.label-subscribe-button.btn.btn-default.gl-ml-3{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } - %span= _('Unsubscribe') + %button.js-unsubscribe-button.gl-button.btn.btn-default.gl-w-full{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } + %span.gl-button-text= _('Unsubscribe') .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } - %button.gl-button.label-subscribe-button.btn.btn-default.gl-ml-3{ data: { toggle: 'dropdown' } } - %span + %button.gl-button.btn.btn-default.gl-w-full{ data: { toggle: 'dropdown' } } + %span.gl-button-text = _('Subscribe') = sprite_icon('chevron-down') .dropdown-menu.dropdown-open-left %ul %li - %button.js-subscribe-button.label-subscribe-button.gl-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } } - %span= _('Subscribe at project level') + %button.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } } + %span.gl-button-text= _('Subscribe at project level') %li - %button.js-subscribe-button.js-group-level.label-subscribe-button.gl-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } - %span= _('Subscribe at group level') + %button.js-subscribe-button.js-group-level{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } + %span.gl-button-text= _('Subscribe at group level') - else - %button.gl-button.js-subscribe-button.label-subscribe-button.btn.btn-default.gl-ml-3{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } - %span= label_subscription_toggle_button_text(label, @project) + %button.gl-button.js-subscribe-button.btn.btn-default.gl-w-full{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title } + %span.gl-button-text= label_subscription_toggle_button_text(label, @project) |