diff --git a/CHANGELOG b/CHANGELOG index f72bb670ece..39239bebcfb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.7.0 (unreleased) + - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - Improved Markdown rendering performance !3389 (Yorick Peterse) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu) - Preserve time notes/comments have been updated at when moving issue diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 47b080406d4..6a670d5e887 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,5 +1,5 @@ class @AwardsHandler - constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> + constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) -> $(".js-add-award").on "click", (event) => event.stopPropagation() event.preventDefault() @@ -34,7 +34,7 @@ class @AwardsHandler $("#emoji_search").focus() else $('.js-add-award').addClass "is-loading" - $.get "/emojis", (response) => + $.get @get_emojis_url, (response) => $('.js-add-award').removeClass "is-loading" $(".js-award-holder").append response setTimeout => diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index e3192823a1a..0b3af592d4a 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,3 +1,9 @@ +.calender-block { + @media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) { + overflow-x: scroll; + } +} + .user-calendar-activities { .calendar_onclick_hr { padding: 0; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index fc3b0a422a7..94f5a12ff6a 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -56,6 +56,17 @@ } } + .nav-search { + display: inline-block; + width: 50%; + padding: 11px 0; + + /* Small devices (phones, tablets, 768px and lower) */ + @media (max-width: $screen-sm-min) { + width: 100%; + } + } + .nav-links { display: inline-block; width: 50%; diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index b1886fbe67b..c3c7bc9fdbe 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -138,6 +138,12 @@ } } + a.no-attachment-icon { + &:before { + display: none; + } + } + /* Link to current header. */ h1, h2, h3, h4, h5, h6 { position: relative; diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 588ad767426..3571eefd570 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -15,7 +15,7 @@ %td - if project - = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace" + = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 3274ba5377b..6dd2fef395d 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,4 +1,4 @@ -.admin-dashboard +.admin-dashboard.prepend-top-default .row .col-md-4 %h4 Statistics diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 41c43899978..149593e7f46 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -1,5 +1,5 @@ - page_title "Deploy Keys" -.panel.panel-default +.panel.panel-default.prepend-top-default .panel-heading Public deploy keys (#{@deploy_keys.count}) .controls diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml new file mode 100644 index 00000000000..9025aaac097 --- /dev/null +++ b/app/views/admin/groups/_group.html.haml @@ -0,0 +1,28 @@ +- css_class = '' unless local_assigns[:css_class] +- css_class += ' no-description' if group.description.blank? + +%li.group-row{ class: css_class } + .controls.hidden-xs + = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm' + = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove' + + .stats + %span + = icon('bookmark') + = number_with_delimiter(group.projects.count) + + %span + = icon('users') + = number_with_delimiter(group.users.count) + + %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} + = visibility_level_icon(group.visibility_level, fw: false) + + = image_tag group_icon(group), class: 'avatar s40 hidden-xs' + .title + = link_to [:admin, group], class: 'group-name' do + = group.name + + - if group.description.present? + .description + = markdown(group.description, pipeline: :description) diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 6bdc885a312..775072a7441 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,20 +1,19 @@ - page_title "Groups" %h3.page-title Groups (#{number_with_delimiter(@groups.total_count)}) - = link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right" %p.light Group allows you to keep projects organized. Use groups for uniting related projects. -%hr -= form_tag admin_groups_path, method: :get, class: 'form-inline' do - = hidden_field_tag :sort, @sort - .form-group - = text_field_tag :name, params[:name], class: "form-control" - = button_tag "Search", class: "btn submit btn-primary" +.top-area + .nav-search + = form_tag admin_groups_path, method: :get, class: 'form-inline' do + = hidden_field_tag :sort, @sort + = text_field_tag :name, params[:name], class: "form-control" + = button_tag "Search", class: "btn submit btn-primary" - .pull-right + .nav-controls .dropdown.inline %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %span.light @@ -33,34 +32,10 @@ = sort_title_recently_updated = link_to admin_groups_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated + = link_to 'New Group', new_admin_group_path, class: "btn btn-new" -%hr - -%ul.bordered-list +%ul.content-list - @groups.each do |group| - %li - .clearfix - .pull-right.prepend-top-10 - = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-sm" - = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-sm btn-remove" - - %h4 - = link_to [:admin, group] do - %span{ class: visibility_level_color(group.visibility_level) } - = visibility_level_icon(group.visibility_level) - - %i.fa.fa-folder - = group.name - - → - %span.monospace - %strong #{group.path}/ - .clearfix - %p - = truncate group.description, length: 150 - .clearfix - %p.light - #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')} - + = render 'group', group: group = paginate @groups, theme: "gitlab" diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml index 3c57e3dc174..05d6b9ed238 100644 --- a/app/views/admin/labels/index.html.haml +++ b/app/views/admin/labels/index.html.haml @@ -1,8 +1,10 @@ - page_title "Labels" -= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do - New label -%h3.page-title - Labels + +%div + = link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do + New label + %h3.page-title + Labels %hr .labels @@ -13,4 +15,4 @@ - else .light-well .nothing-here-block There are no labels yet - + diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index c407972cd08..2dad64b8d0f 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -1,4 +1,4 @@ -%p.lead +%p.lead.prepend-top-default %span To register a new runner you should enter the following registration token. With this token the runner will request a unique runner token and use that for future communication. diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml index 5d342ef58e5..69fc81cb45c 100644 --- a/app/views/profiles/two_factor_auths/new.html.haml +++ b/app/views/profiles/two_factor_auths/new.html.haml @@ -7,8 +7,6 @@ %p Increase your account's security by enabling two-factor authentication (2FA). .col-lg-9 - %p - Status: #{current_user.two_factor_enabled? ? 'enabled' : 'disabled'} %p Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code. More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}. diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index d22d1da8402..2cf9115e4dd 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -39,7 +39,7 @@ %td = build.name - .pull-right + .label-container - if build.tags.any? - build.tags.each do |tag| %span.label.label-primary diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index bca816f22cb..0c4b6a5618b 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -87,7 +87,7 @@ %div{ class: container_class } .tab-content #activity.tab-pane - .gray-content-block.white.second-block + .gray-content-block.calender-block.white.second-block.hidden-xs %div{ class: container_class } .user-calendar{data: {href: user_calendar_path}} %h4.center.light diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 02647229776..8ffcdc4a327 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -15,12 +15,14 @@ - if current_user :javascript + var get_emojis_url = "#{emojis_path}"; var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"; var noteable_type = "#{votable.class.name.underscore}"; var noteable_id = "#{votable.id}"; var aliases = #{AwardEmoji.aliases.to_json}; window.awards_handler = new AwardsHandler( + get_emojis_url, post_emoji_url, noteable_type, noteable_id, diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 223b7277b51..9f6aed1c5b9 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -85,7 +85,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I have an existing Wiki page with images linked on page' do - wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![image](image.jpg)", :markdown, "first commit") + wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![alt text](image.jpg)", :markdown, "first commit") @wiki_page = wiki.find_page("pictures") end diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb new file mode 100644 index 00000000000..ccd106860bd --- /dev/null +++ b/lib/banzai/filter/image_link_filter.rb @@ -0,0 +1,27 @@ +module Banzai + module Filter + # HTML filter that wraps links around inline images. + class ImageLinkFilter < HTML::Pipeline::Filter + + # Find every image that isn't already wrapped in an `a` tag, create + # a new node (a link to the image source), copy the image as a child + # of the anchor, and then replace the img with the link-wrapped version. + def call + doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img| + + link = doc.document.create_element( + 'a', + class: 'no-attachment-icon', + href: img['src'], + target: '_blank' + ) + + link.children = img.clone + img.replace(link) + end + + doc + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 8cd4b50e65a..ed3cfd6b023 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -7,6 +7,7 @@ module Banzai Filter::SanitizationFilter, Filter::UploadLinkFilter, + Filter::ImageLinkFilter, Filter::EmojiFilter, Filter::TableOfContentsFilter, Filter::AutolinkFilter, diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb index 252bf2747e1..19a54946fe0 100644 --- a/spec/factories/forked_project_links.rb +++ b/spec/factories/forked_project_links.rb @@ -13,5 +13,10 @@ FactoryGirl.define do factory :forked_project_link do association :forked_to_project, factory: :project association :forked_from_project, factory: :project + + after(:create) do |link| + link.forked_from_project.reload + link.forked_to_project.reload + end end end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index dc41be8246f..de6aed74fb4 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -61,7 +61,7 @@ describe "User Feed", feature: true do end it 'should have XHTML summaries in merge request descriptions' do - expect(body).to match /Here is the fix: ]*\/>/ + expect(body).to match /Here is the fix: ]*>]*\/><\/a>/ end end end diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb new file mode 100644 index 00000000000..dd5594750c8 --- /dev/null +++ b/spec/lib/banzai/filter/image_link_filter_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Banzai::Filter::ImageLinkFilter, lib: true do + include FilterSpecHelper + + def image(path) + %() + end + + it 'wraps the image with a link to the image src' do + doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) + expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] + end + + it 'does not wrap a duplicate link' do + exp = act = %q(#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}) + expect(filter(act).to_html).to eq exp + end + + it 'works with external images' do + doc = filter(image('https://i.imgur.com/DfssX9C.jpg')) + expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href'] + end +end