diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/import_entities/import_groups/components/import_table.vue | 17 | ||||
-rw-r--r-- | app/assets/javascripts/import_entities/import_groups/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/issuable/issuable_form.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/repository/components/blob_content_viewer.vue | 5 | ||||
-rw-r--r-- | app/assets/javascripts/repository/constants.js | 21 | ||||
-rw-r--r-- | app/assets/javascripts/users_select/index.js | 18 | ||||
-rw-r--r-- | app/assets/stylesheets/startup/startup-dark.scss | 3 | ||||
-rw-r--r-- | app/assets/stylesheets/themes/_dark.scss | 2 | ||||
-rw-r--r-- | app/models/wiki.rb | 86 | ||||
-rw-r--r-- | app/views/import/bulk_imports/status.html.haml | 1 | ||||
-rw-r--r-- | app/views/layouts/_head.html.haml | 47 | ||||
-rw-r--r-- | app/views/notify/issue_due_email.html.haml | 2 | ||||
-rw-r--r-- | app/views/registrations/welcome/show.html.haml | 2 |
13 files changed, 160 insertions, 49 deletions
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue index 028197ec9b1..ca04824c663 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue @@ -71,6 +71,10 @@ export default { type: String, required: true, }, + historyPath: { + type: String, + required: true, + }, }, data() { @@ -485,12 +489,15 @@ export default { <template> <div> - <h1 - class="gl-my-0 gl-py-4 gl-font-size-h1 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex" + <div + class="gl-display-flex gl-align-items-center gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1" > - <img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" /> - {{ s__('BulkImport|Import groups from GitLab') }} - </h1> + <h1 class="gl-my-0 gl-py-4 gl-font-size-h1gl-display-flex"> + <img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" /> + {{ s__('BulkImport|Import groups from GitLab') }} + </h1> + <gl-link :href="historyPath" class="gl-ml-auto">{{ s__('BulkImport|History') }}</gl-link> + </div> <gl-alert v-if="unavailableFeatures.length > 0 && unavailableFeaturesAlertVisible" variant="warning" diff --git a/app/assets/javascripts/import_entities/import_groups/index.js b/app/assets/javascripts/import_entities/import_groups/index.js index 67a7258d504..02af0db7f9a 100644 --- a/app/assets/javascripts/import_entities/import_groups/index.js +++ b/app/assets/javascripts/import_entities/import_groups/index.js @@ -17,6 +17,7 @@ export function mountImportGroupsApp(mountElement) { jobsPath, sourceUrl, groupPathRegex, + historyPath, } = mountElement.dataset; const apolloProvider = new VueApollo({ defaultClient: createApolloClient({ @@ -38,6 +39,7 @@ export function mountImportGroupsApp(mountElement) { sourceUrl, jobsPath, groupPathRegex: new RegExp(`^(${groupPathRegex})$`), + historyPath, }, }); }, diff --git a/app/assets/javascripts/issuable/issuable_form.js b/app/assets/javascripts/issuable/issuable_form.js index 018cadad50f..8e76a33c7dd 100644 --- a/app/assets/javascripts/issuable/issuable_form.js +++ b/app/assets/javascripts/issuable/issuable_form.js @@ -65,7 +65,8 @@ export default class IssuableForm { this.gfmAutoComplete = new GfmAutoComplete( gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources, ).setup(); - this.usersSelect = new UsersSelect(); + const autoAssignToMe = form.get(0).id === 'new_merge_request'; + this.usersSelect = new UsersSelect(undefined, undefined, { autoAssignToMe }); this.reviewersSelect = new UsersSelect(undefined, '.js-reviewer-search'); this.zenMode = new ZenMode(); diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index c9e4aab1db1..ea64f06e1c1 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -17,7 +17,7 @@ import getRefMixin from '../mixins/get_ref'; import blobInfoQuery from '../queries/blob_info.query.graphql'; import userInfoQuery from '../queries/user_info.query.graphql'; import applicationInfoQuery from '../queries/application_info.query.graphql'; -import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE } from '../constants'; +import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE, LEGACY_FILE_TYPES } from '../constants'; import BlobButtonGroup from './blob_button_group.vue'; import ForkSuggestion from './fork_suggestion.vue'; import { loadViewer } from './blob_viewers'; @@ -132,7 +132,8 @@ export default { return this.shouldLoadLegacyViewer ? null : loadViewer(fileType, this.isUsingLfs); }, shouldLoadLegacyViewer() { - return this.viewer.fileType === TEXT_FILE_TYPE && !this.glFeatures.highlightJs; + const isTextFile = this.viewer.fileType === TEXT_FILE_TYPE && !this.glFeatures.highlightJs; + return isTextFile || LEGACY_FILE_TYPES.includes(this.blobInfo.fileType); }, legacyViewerLoaded() { return ( diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js index bb9d3180be8..2cafeed2ef4 100644 --- a/app/assets/javascripts/repository/constants.js +++ b/app/assets/javascripts/repository/constants.js @@ -86,3 +86,24 @@ export const DEFAULT_BLOB_INFO = { export const TEXT_FILE_TYPE = 'text'; export const LFS_STORAGE = 'lfs'; + +/** + * We have some features (like linking to external dependencies) that our frontend highlighter + * do not yet support. + * These are file types that we want the legacy (backend) syntax highlighter to highlight. + */ +export const LEGACY_FILE_TYPES = [ + 'package_json', + 'gemfile', + 'gemspec', + 'composer_json', + 'podfile', + 'podspec', + 'podspec_json', + 'cartfile', + 'godeps_json', + 'requirements_txt', + 'cargo_toml', + 'go_mod', + 'go_sum', +]; diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js index f7a5589af90..e1e5cc565c6 100644 --- a/app/assets/javascripts/users_select/index.js +++ b/app/assets/javascripts/users_select/index.js @@ -35,7 +35,7 @@ function UsersSelect(currentUser, els, options = {}) { } } - const { handleClick } = options; + const { handleClick, autoAssignToMe } = options; const userSelect = this; $els.each((i, dropdown) => { @@ -172,10 +172,7 @@ function UsersSelect(currentUser, els, options = {}) { }); }; - $assignToMeLink.on('click', (e) => { - e.preventDefault(); - $(e.currentTarget).hide(); - + const onAssignToMeClick = () => { if ($dropdown.data('multiSelect')) { assignYourself(); checkMaxSelect(); @@ -194,8 +191,19 @@ function UsersSelect(currentUser, els, options = {}) { .text(gon.current_user_fullname) .removeClass('is-default'); } + }; + + $assignToMeLink.on('click', (e) => { + e.preventDefault(); + $(e.currentTarget).hide(); + onAssignToMeClick(); }); + if (autoAssignToMe) { + $assignToMeLink.hide(); + onAssignToMeClick(); + } + $block.on('click', '.js-assign-yourself', (e) => { e.preventDefault(); return assignTo(userSelect.currentUser.id); diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index 8e5ff794f28..d8d0eda9478 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -12,6 +12,7 @@ body.gl-dark { --gl-text-color: #fafafa; --border-color: #4f4f4f; --black: #fff; + --nav-active-bg: rgba(255, 255, 255, 0.08); } :root { --white: #333; @@ -1787,6 +1788,7 @@ body.gl-dark { --white: #333; --black: #fff; --svg-status-bg: #333; + --nav-active-bg: rgba(255, 255, 255, 0.08); } .nav-sidebar li a { color: var(--gray-600); @@ -2023,6 +2025,7 @@ body.gl-dark { --white: #333; --black: #fff; --svg-status-bg: #333; + --nav-active-bg: rgba(255, 255, 255, 0.08); } .tab-width-8 { -moz-tab-size: 8; diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss index 550e3981401..6a9e96c3ac5 100644 --- a/app/assets/stylesheets/themes/_dark.scss +++ b/app/assets/stylesheets/themes/_dark.scss @@ -99,6 +99,7 @@ $white-normal: #333; $white-dark: #444; $border-color: #4f4f4f; +$nav-active-bg: rgba(255, 255, 255, 0.08); body.gl-dark { --gray-10: #{$gray-10}; @@ -199,6 +200,7 @@ body.gl-dark { --black: #{$black}; --svg-status-bg: #{$white}; + --nav-active-bg: #{$nav-active-bg}; .gl-button.gl-button, .gl-button.gl-button.btn-block { diff --git a/app/models/wiki.rb b/app/models/wiki.rb index b3f09b20463..b29c43bcfe1 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -13,43 +13,65 @@ class Wiki markdown: { name: 'Markdown', default_extension: :md, + extension_regex: Regexp.new('md|mkdn?|mdown|markdown', 'i'), created_by_user: true }, rdoc: { name: 'RDoc', default_extension: :rdoc, + extension_regex: Regexp.new('rdoc', 'i'), created_by_user: true }, asciidoc: { name: 'AsciiDoc', default_extension: :asciidoc, + extension_regex: Regexp.new('adoc|asciidoc', 'i'), created_by_user: true }, org: { name: 'Org', default_extension: :org, + extension_regex: Regexp.new('org', 'i'), created_by_user: true }, textile: { name: 'Textile', - default_extension: :textile + default_extension: :textile, + extension_regex: Regexp.new('textile', 'i') }, creole: { name: 'Creole', - default_extension: :creole + default_extension: :creole, + extension_regex: Regexp.new('creole', 'i') }, rest: { name: 'reStructuredText', - default_extension: :rst + default_extension: :rst, + extension_regex: Regexp.new('re?st(\.txt)?', 'i') }, mediawiki: { name: 'MediaWiki', - default_extension: :mediawiki + default_extension: :mediawiki, + extension_regex: Regexp.new('(media)?wiki', 'i') + }, + pod: { + name: 'Pod', + default_extension: :pod, + extension_regex: Regexp.new('pod', 'i') + }, + plaintext: { + name: 'Plain Text', + default_extension: :txt, + extension_regex: Regexp.new('txt', 'i') } }.freeze unless defined?(MARKUPS) VALID_USER_MARKUPS = MARKUPS.select { |_, v| v[:created_by_user] }.freeze unless defined?(VALID_USER_MARKUPS) + unless defined?(ALLOWED_EXTENSIONS_REGEX) + ALLOWED_EXTENSIONS_REGEX = Regexp.union(MARKUPS.map { |key, value| value[:extension_regex] }).freeze + end + CouldNotCreateWikiError = Class.new(StandardError) HOMEPAGE = 'home' @@ -205,15 +227,36 @@ class Wiki end def create_page(title, content, format = :markdown, message = nil) - commit = commit_details(:created, message, title) + if Feature.enabled?(:gitaly_replace_wiki_create_page, container, default_enabled: :yaml) + with_valid_format(format) do |default_extension| + if file_exists_by_regex?(title) + raise_duplicate_page_error! + end + + capture_git_error(:created) do + create_wiki_repository unless repository_exists? + sanitized_path = sluggified_full_path(title, default_extension) + repository.create_file(user, sanitized_path, content, **multi_commit_options(:created, message, title)) + repository.expire_status_cache if repository.empty? + after_wiki_activity - wiki.write_page(title, format.to_sym, content, commit) - repository.expire_status_cache if repository.empty? - after_wiki_activity + true + rescue Gitlab::Git::Index::IndexError + raise_duplicate_page_error! + end + end + else + commit = commit_details(:created, message, title) + + wiki.write_page(title, format.to_sym, content, commit) + repository.expire_status_cache if repository.empty? + after_wiki_activity - true + true + end rescue Gitlab::Git::Wiki::DuplicatePageError => e - @error_message = "Duplicate page: #{e.message}" + @error_message = _("Duplicate page: %{error_message}" % { error_message: e.message }) + false end @@ -393,12 +436,33 @@ class Wiki yield default_extension end + def file_exists_by_regex?(title) + return false unless repository_exists? + + escaped_title = Regexp.escape(sluggified_title(title)) + regex = Regexp.new("^#{escaped_title}\.#{ALLOWED_EXTENSIONS_REGEX}$", 'i') + + repository.ls_files('HEAD').any? { |s| s =~ regex } + end + + def raise_duplicate_page_error! + raise Gitlab::Git::Wiki::DuplicatePageError, _('A page with that title already exists') + end + def sluggified_full_path(title, extension) sluggified_title(title) + '.' + extension end def sluggified_title(title) - Gitlab::EncodingHelper.encode_utf8_no_detect(title).tr(' ', '-') + utf8_encoded_title = Gitlab::EncodingHelper.encode_utf8_no_detect(title) + + sanitized_title(utf8_encoded_title).tr(' ', '-') + end + + def sanitized_title(title) + clean_absolute_path = File.expand_path(title, '/') + + Pathname.new(clean_absolute_path).relative_path_from('/').to_s end end diff --git a/app/views/import/bulk_imports/status.html.haml b/app/views/import/bulk_imports/status.html.haml index 2aae8811678..71866bab30b 100644 --- a/app/views/import/bulk_imports/status.html.haml +++ b/app/views/import/bulk_imports/status.html.haml @@ -8,4 +8,5 @@ jobs_path: realtime_changes_import_bulk_imports_path(format: :json), source_url: @source_url, group_path_regex: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, + history_path: history_import_bulk_imports_path, group_url_error_message: group_url_error_message } } diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 15cd9bece71..48b9015ccc0 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -3,32 +3,14 @@ %head{ prefix: "og: http://ogp.me/ns#" } %meta{ charset: "utf-8" } + %title= page_title(site_name) + = render 'layouts/loading_hints' %meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' } = render 'layouts/startup_js' - -# Open Graph - http://ogp.me/ - %meta{ property: 'og:type', content: "object" } - %meta{ property: 'og:site_name', content: site_name } - %meta{ property: 'og:title', content: page_title } - %meta{ property: 'og:description', content: page_description } - %meta{ property: 'og:image', content: page_image } - %meta{ property: 'og:image:width', content: '64' } - %meta{ property: 'og:image:height', content: '64' } - %meta{ property: 'og:url', content: request.base_url + request.fullpath } - - -# Twitter Card - https://dev.twitter.com/cards/types/summary - %meta{ property: 'twitter:card', content: "summary" } - %meta{ property: 'twitter:title', content: page_title } - %meta{ property: 'twitter:description', content: page_description } - %meta{ property: 'twitter:image', content: page_image } - = page_card_meta_tags - - %title= page_title(site_name) - %meta{ name: "description", content: page_description } - - if page_canonical_link %link{ rel: 'canonical', href: page_canonical_link } @@ -67,13 +49,32 @@ = yield :project_javascripts - = csrf_meta_tags - = csp_meta_tag - = action_cable_meta_tag + -# Open Graph - http://ogp.me/ + %meta{ property: 'og:type', content: "object" } + %meta{ property: 'og:site_name', content: site_name } + %meta{ property: 'og:title', content: page_title } + %meta{ property: 'og:description', content: page_description } + %meta{ property: 'og:image', content: page_image } + %meta{ property: 'og:image:width', content: '64' } + %meta{ property: 'og:image:height', content: '64' } + %meta{ property: 'og:url', content: request.base_url + request.fullpath } + + -# Twitter Card - https://dev.twitter.com/cards/types/summary + %meta{ property: 'twitter:card', content: "summary" } + %meta{ property: 'twitter:title', content: page_title } + %meta{ property: 'twitter:description', content: page_description } + %meta{ property: 'twitter:image', content: page_image } + = page_card_meta_tags + + %meta{ name: "description", content: page_description } %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' } %meta{ name: 'theme-color', content: user_theme_primary_color } + = csrf_meta_tags + = csp_meta_tag + = action_cable_meta_tag + -# Apple Safari/iOS home screen icons = favicon_link_tag 'touch-icon-iphone.png', rel: 'apple-touch-icon' = favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76' diff --git a/app/views/notify/issue_due_email.html.haml b/app/views/notify/issue_due_email.html.haml index 3208d061928..9dd501022dd 100644 --- a/app/views/notify/issue_due_email.html.haml +++ b/app/views/notify/issue_due_email.html.haml @@ -1,5 +1,5 @@ %p.details - = sprintf(s_("Notify|%{author_link}'s issue %{issue_reference_link} is due soon."), { author_link: link_to(@issue.author_name, user_url(@issue.author)), issue_reference_link: issue_reference_link(@issue) }) + = sprintf(s_("Notify|%{author_link}'s issue %{issue_reference_link} is due soon."), { author_link: link_to(@issue.author_name, user_url(@issue.author)), issue_reference_link: issue_reference_link(@issue) }).html_safe - if @issue.assignees.any? %p diff --git a/app/views/registrations/welcome/show.html.haml b/app/views/registrations/welcome/show.html.haml index 6235cce5d80..1f6976d4f38 100644 --- a/app/views/registrations/welcome/show.html.haml +++ b/app/views/registrations/welcome/show.html.haml @@ -27,7 +27,7 @@ - if Feature.enabled?(:user_other_role_details) .row .form-group.col-sm-12.js-other-role-group.hidden - = f.label :other_role, _('What is your job title? (optional)'), class: 'form-check-label gl-mb-3' + = f.label :other_role, _('What is your job title? (optional)') = f.text_field :other_role, class: 'form-control' = render_if_exists "registrations/welcome/jobs_to_be_done", f: f = render_if_exists "registrations/welcome/setup_for_company", f: f |