diff options
Diffstat (limited to 'app')
113 files changed, 2807 insertions, 602 deletions
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png Binary files differnew file mode 100644 index 00000000000..a8ad7b6eab6 --- /dev/null +++ b/app/assets/images/emoji.png diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 1539eba0faa..affab5bb030 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -5,7 +5,7 @@ # the compiled file. # #= require jquery -#= require jquery.ui.all +#= require jquery-ui #= require jquery_ujs #= require jquery.cookie #= require jquery.endless-scroll diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 3ff9ba77dfc..04bf5cc7bb5 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,12 +1,28 @@ class @AwardsHandler constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> + $(".add-award").click (event)-> + event.stopPropagation() + event.preventDefault() + $(".emoji-menu").show() + + $("html").click -> + if !$(event.target).closest(".emoji-menu").length + if $(".emoji-menu").is(":visible") + $(".emoji-menu").hide() + + @renderFrequentlyUsedBlock() + @setupSearch() addAward: (emoji) -> emoji = @normilizeEmojiName(emoji) @postEmoji emoji, => @addAwardToEmojiBar(emoji) + + $(".emoji-menu").hide() - addAwardToEmojiBar: (emoji, custom_path = '') -> + addAwardToEmojiBar: (emoji) -> + @addEmojiToFrequentlyUsedList(emoji) + emoji = @normilizeEmojiName(emoji) if @exist(emoji) if @isActive(emoji) @@ -17,7 +33,7 @@ class @AwardsHandler counter.parent().addClass("active") @addMeToAuthorList(emoji) else - @createEmoji(emoji, custom_path) + @createEmoji(emoji) exist: (emoji) -> @findEmojiIcon(emoji).length > 0 @@ -54,35 +70,39 @@ class @AwardsHandler resetTooltip: (award) -> award.tooltip("destroy") - # "destroy" call is asynchronous, this is why we need to set timeout. + # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout. setTimeout (-> award.tooltip() ), 200 - createEmoji: (emoji, custom_path) -> + createEmoji: (emoji) -> + emojiCssClass = @resolveNameToCssClass(emoji) + nodes = [] nodes.push("<div class='award active' title='me'>") - nodes.push("<div class='icon' data-emoji='" + emoji + "'>") - nodes.push(@getImage(emoji, custom_path)) + nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>") + nodes.push("<div class='counter'>1</div>") nodes.push("</div>") - nodes.push("<div class='counter'>1") - nodes.push("</div></div>") - $(".awards-controls").before(nodes.join("\n")) + emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji) $(".award").tooltip() - getImage: (emoji, custom_path) -> - if custom_path - $("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html() + resolveNameToCssClass: (emoji) -> + emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") + + if emoji_icon.length > 0 + unicodeName = emoji_icon.data("unicode-name") else - $("li[data-emoji='" + emoji + "']").html() + # Find by alias + unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name") + "emoji-#{unicodeName}" postEmoji: (emoji, callback) -> $.post @post_emoji_url, { note: { - note: ":" + emoji + ":" + note: ":#{emoji}:" noteable_type: @noteable_type noteable_id: @noteable_id }},(data) -> @@ -90,7 +110,7 @@ class @AwardsHandler callback.call() findEmojiIcon: (emoji) -> - $(".icon[data-emoji='" + emoji + "']") + $(".award [data-emoji='#{emoji}']") scrollToAwards: -> $('body, html').animate({ @@ -99,3 +119,46 @@ class @AwardsHandler normilizeEmojiName: (emoji) -> @aliases[emoji] || emoji + + addEmojiToFrequentlyUsedList: (emoji) -> + frequently_used_emojis = @getFrequentlyUsedEmojis() + frequently_used_emojis.push(emoji) + $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 }) + + getFrequentlyUsedEmojis: -> + frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") + + frequently_used_emojis = ["thumbsup", "thumbsdown"].concat(frequently_used_emojis) + + _.compact(_.uniq(frequently_used_emojis)) + + renderFrequentlyUsedBlock: -> + frequently_used_emojis = @getFrequentlyUsedEmojis() + + ul = $("<ul>") + + for emoji in frequently_used_emojis + do (emoji) -> + $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul) + + $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used")) + + setupSearch: -> + $("input.emoji-search").keyup (ev) => + term = $(ev.target).val() + + # Clean previous search results + $("ul.emoji-search,h5.emoji-search").remove() + + if term + # Generate a search result block + h5 = $("<h5>").text("Search results").addClass("emoji-search") + found_emojis = @searchEmojis(term).show() + ul = $("<ul>").addClass("emoji-search").append(found_emojis) + $(".emoji-menu-content ul, .emoji-menu-content h5").hide() + $(".emoji-menu-content").append(h5).append(ul) + else + $(".emoji-menu-content").children().show() + + searchEmojis: (term)-> + $(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone() diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee index 195f8b11e5d..9df932817f6 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee +++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee @@ -35,7 +35,7 @@ class @BlobFileDropzone return this.on 'sending', (file, xhr, formData) -> - formData.append('new_branch', form.find('.js-new-branch').val()) + formData.append('target_branch', form.find('.js-target-branch').val()) formData.append('create_merge_request', form.find('.js-create-merge-request').val()) formData.append('commit_message', form.find('.js-commit-message').val()) return diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index c4b63966fe7..738ffc8343b 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -18,7 +18,7 @@ class @MergeRequestWidget if data.state == "merged" urlSuffix = if deleteSourceBranch then '?delete_source=true' else '' - window.location.href = window.location.href + urlSuffix + window.location.href = window.location.pathname + urlSuffix else if data.merge_error $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>") else diff --git a/app/assets/javascripts/new_branch_form.js.coffee b/app/assets/javascripts/new_branch_form.js.coffee new file mode 100644 index 00000000000..4b350854f78 --- /dev/null +++ b/app/assets/javascripts/new_branch_form.js.coffee @@ -0,0 +1,78 @@ +class @NewBranchForm + constructor: (form, availableRefs) -> + @branchNameError = form.find('.js-branch-name-error') + @name = form.find('.js-branch-name') + @ref = form.find('#ref') + + @setupAvailableRefs(availableRefs) + @setupRestrictions() + @addBinding() + @init() + + addBinding: -> + @name.on 'blur', @validate + + init: -> + @name.trigger 'blur' if @name.val().length > 0 + + setupAvailableRefs: (availableRefs) -> + @ref.autocomplete + source: availableRefs, + minLength: 1 + + setupRestrictions: -> + startsWith = { + pattern: /^(\/|\.)/g, + prefix: "can't start with", + conjunction: "or" + } + + endsWith = { + pattern: /(\/|\.|\.lock)$/g, + prefix: "can't end in", + conjunction: "or" + } + + invalid = { + pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g + prefix: "can't contain", + conjunction: ", " + } + + single = { + pattern: /^@+$/g + prefix: "can't be", + conjunction: "or" + } + + @restrictions = [startsWith, invalid, endsWith, single] + + validate: => + @branchNameError.empty() + + unique = (values, value) -> + values.push(value) unless value in values + values + + formatter = (values, restriction) -> + formatted = values.map (value) -> + switch + when /\s/.test value then 'spaces' + when /\/{2,}/g.test value then 'consecutive slashes' + else "'#{value}'" + + "#{restriction.prefix} #{formatted.join(restriction.conjunction)}" + + validator = (errors, restriction) => + matched = @name.val().match(restriction.pattern) + + if matched + errors.concat formatter(matched.reduce(unique, []), restriction) + else + errors + + errors = @restrictions.reduce validator, [] + + if errors.length > 0 + errorMessage = $("<span/>").text(errors.join(', ')) + @branchNameError.append(errorMessage) diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee index 3c7b776155f..03f0f51acfa 100644 --- a/app/assets/javascripts/new_commit_form.js.coffee +++ b/app/assets/javascripts/new_commit_form.js.coffee @@ -1,6 +1,6 @@ class @NewCommitForm constructor: (form) -> - @newBranch = form.find('.js-new-branch') + @newBranch = form.find('.js-target-branch') @originalBranch = form.find('.js-original-branch') @createMergeRequest = form.find('.js-create-merge-request') @createMergeRequestContainer = form.find('.js-create-merge-request-container') diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 35dc7829da2..9e5204bfeeb 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -127,7 +127,7 @@ class @Notes @initTaskList() if note.award - awards_handler.addAwardToEmojiBar(note.note, note.emoji_path) + awards_handler.addAwardToEmojiBar(note.note) awards_handler.scrollToAwards() ### diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 1f221945c06..d7a658f8faa 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,7 +1,7 @@ class @Project constructor: -> # Git protocol switcher - $('.js-protocol-switch').click -> + $('ul.clone-options-dropdown a').click -> return if $(@).hasClass('active') @@ -10,7 +10,8 @@ class @Project # Add the active class for the clicked button $(@).toggleClass('active') - url = $(@).data('clone') + url = $("#project_clone").val() + console.log("url",url) # Update the input field $('#project_clone').val(url) diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee index db5faf71faf..f2887af190b 100644 --- a/app/assets/javascripts/projects_list.js.coffee +++ b/app/assets/javascripts/projects_list.js.coffee @@ -8,17 +8,17 @@ class @ProjectsList $(".projects-list-filter").keyup -> terms = $(this).val() - uiBox = $(this).closest('.projects-list-holder') + uiBox = $('div.projects-list-holder') if terms == "" || terms == undefined - uiBox.find(".projects-list li").show() + uiBox.find("ul.projects-list li").show() else - uiBox.find(".projects-list li").each (index) -> - name = $(this).find(".filter-title").text() + uiBox.find("ul.projects-list li").each (index) -> + name = $(this).find("span.filter-title").text() if name.toLowerCase().search(terms.toLowerCase()) == -1 $(this).hide() else $(this).show() - uiBox.find(".projects-list li.bottom").hide() + uiBox.find("ul.projects-list li.bottom").hide() diff --git a/app/assets/javascripts/star.js.coffee b/app/assets/javascripts/star.js.coffee new file mode 100644 index 00000000000..d849b2e7950 --- /dev/null +++ b/app/assets/javascripts/star.js.coffee @@ -0,0 +1,22 @@ +class @Star + constructor: -> + $('.project-home-panel .toggle-star').on('ajax:success', (e, data, status, xhr) -> + $this = $(this) + $starSpan = $this.find('span') + $starIcon = $this.find('i') + + toggleStar = (isStarred) -> + $this.parent().find('span.count').text data.star_count + if isStarred + $starSpan.removeClass('starred').text 'Star' + $starIcon.removeClass('fa-star').addClass 'fa-star-o' + else + $starSpan.addClass('starred').text 'Unstar' + $starIcon.removeClass('fa-star-o').addClass 'fa-star' + return + + toggleStar $starSpan.hasClass('starred') + return + ).on 'ajax:error', (e, xhr, status, error) -> + new Flash('Star toggle failed. Try again later.', 'alert') + return
\ No newline at end of file diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 7b060ce4853..0c0451fe4dd 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -2,8 +2,8 @@ * This is a manifest file that'll automatically include all the stylesheets available in this directory * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * the top of the compiled file, but it's generally better to create a new file per style scope. - *= require jquery.ui.datepicker - *= require jquery.ui.autocomplete + *= require jquery-ui/datepicker + *= require jquery-ui/autocomplete *= require jquery.atwho *= require select2 *= require_self @@ -48,4 +48,4 @@ /* * Styles for JS behaviors. */ -@import "behaviors.scss";
\ No newline at end of file +@import "behaviors.scss"; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index a62c0f62a4c..206d39cc9b3 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -76,7 +76,7 @@ .cover-block { text-align: center; - background: #f7f8fa; + background: $background-color; margin: -$gl-padding; margin-bottom: 0; padding: 44px $gl-padding; diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index fe56266284b..97a94638847 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,10 +1,9 @@ @mixin btn-default { - @include border-radius(2px); + @include border-radius(3px); border-width: 1px; border-style: solid; - text-transform: uppercase; - font-size: 13px; - font-weight: 600; + font-size: 15px; + font-weight: 500; line-height: 18px; padding: 11px $gl-padding; letter-spacing: .4px; @@ -18,7 +17,7 @@ @mixin btn-middle { @include btn-default; - @include border-radius(2px); + @include border-radius(3px); padding: 11px 24px; } @@ -51,6 +50,10 @@ @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF); } +@mixin btn-blue-medium { + @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #FFFFFF); +} + @mixin btn-orange { @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF); } @@ -60,7 +63,7 @@ } @mixin btn-gray { - @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, #313236); + @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236); } @mixin btn-white { @@ -75,6 +78,10 @@ padding: 5px 10px; } + &.btn-nr { + padding: 7px 10px; + } + &.btn-xs { padding: 1px 5px; } @@ -91,11 +98,15 @@ @include btn-gray; } - &.btn-primary, + &.btn-primary { + @include btn-blue-medium; + } + &.btn-info { @include btn-blue; } + &.btn-close, &.btn-warning { @include btn-orange; } @@ -110,20 +121,8 @@ float: right; } - &.btn-close { - color: $gl-danger; - border-color: $gl-danger; - &:hover { - color: #B94A48; - } - } - &.btn-reopen { - color: $gl-success; - border-color: $gl-success; - &:hover { - color: #468847; - } + /* should be same as parent class for now */ } &.btn-grouped { diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 7562ef6d24b..11730000f85 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -374,7 +374,7 @@ table { } } -.center-top-menu { +.center-top-menu, .left-top-menu { @include nav-menu; text-align: center; margin-top: 5px; @@ -408,6 +408,11 @@ table { } } +.left-top-menu { + text-align: left; + border-bottom: 1px solid #EEE; +} + .center-middle-menu { @include nav-menu; padding: 0; diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index fba67ba0b64..e93dbab0c42 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -5,7 +5,7 @@ */ .status-box { - @include border-radius(2px); + @include border-radius(3px); display: block; float: left; @@ -25,7 +25,7 @@ } &.status-box-open { - background-color: #019875; + background-color: $green-light; color: #FFF; } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index aa5acb93cc5..a1a9990241d 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -5,7 +5,7 @@ html { } body { - background-color: #EAEBEC !important; + background-color: #F3F3F3 !important; &.navless { background-color: white !important; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 11c48d26ab5..41fd890f14f 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -123,7 +123,6 @@ padding: 0; margin: 0; list-style: none; - margin-top: 5px; height: 56px; li { @@ -131,9 +130,9 @@ a { padding: 14px; - font-size: 17px; + font-size: 15px; line-height: 28px; - color: #7f8fa4; + color: #959494; border-bottom: 2px solid transparent; &:hover, &:active, &:focus { @@ -143,8 +142,8 @@ } &.active a { - color: #4c4e54; - border-bottom: 2px solid #1cacfc; + color: #616060; + border-bottom: 2px solid #4688f1; } .badge { diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 6f44c323732..c00709fb6bb 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -81,7 +81,7 @@ display: none; } - .center-top-menu { + .center-top-menu, .left-top-menu { li a { font-size: 14px; padding: 19px 10px; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 2ef40a6e517..af75123b0af 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -1,9 +1,9 @@ -$hover: #FFFAF1; +$hover: #faf9f9; $gl-text-color: #54565B; $gl-text-green: #4A2; $gl-text-red: #D12F19; $gl-text-orange: #D90; -$gl-header-color: #4c4e54; +$gl-header-color: #323232; $gl-link-color: #333c48; $md-text-color: #444; $md-link-color: #3084bb; @@ -15,13 +15,14 @@ $sidebar_width: 230px; $avatar_radius: 50%; $code_font_size: 13px; $code_line_height: 1.5; -$border-color: #dce0e6; +$border-color: #efeff1; $table-border-color: #eef0f2; -$background-color: #F7F8FA; +$background-color: #faf9f9; $header-height: 58px; $fixed-layout-width: 1280px; -$gl-gray: #7f8fa4; +$gl-gray: #5a5a5a; $gl-padding: 16px; +$gl-padding-top:10px; $gl-avatar-size: 46px; /* @@ -29,12 +30,12 @@ $gl-avatar-size: 46px; */ $white-light: #FFFFFF; -$white-normal: #DCE0E5; -$white-dark: #E4E7ED; +$white-normal: #ededed; +$white-dark: #ededed; -$gray-light: #F0F2F5; -$gray-normal: #DCE0E5; -$gray-dark: #E4E7ED; +$gray-light: #f7f7f7; +$gray-normal: #ededed; +$gray-dark: #ededed; $green-light: #31AF64; $green-normal: #2FAA60; @@ -44,6 +45,10 @@ $blue-light: #2EA8E5; $blue-normal: #2D9FD8; $blue-dark: #2897CE; +$blue-medium-light: #3498CB; +$blue-medium: #2F8EBF; +$blue-medium-dark: #2D86B4; + $orange-light: #FC6443; $orange-normal: #E75E40; $orange-dark: #CE5237; @@ -52,11 +57,11 @@ $red-light: #F43263; $red-normal: #E52C5A; $red-dark: #D22852; -$border-white-light: #E3E7EC; +$border-white-light: #F1F2F4; $border-white-normal: #D6DAE2; $border-white-dark: #C6CACF; -$border-gray-light: #DCE0E5; +$border-gray-light: #d1d1d1; $border-gray-normal: #D6DAE2; $border-gray-dark: #C6CACF; @@ -76,6 +81,8 @@ $border-red-light: #E52C5A; $border-red-normal: #D22852; $border-red-dark: #CA264F; +/* header */ +$light-grey-header: #faf9f9; /* * State colors: diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 041b811a606..87dd30f4111 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -2,6 +2,12 @@ @include clearfix; line-height: 34px; + .emoji-icon { + width: 20px; + height: 20px; + margin: 7px 0 0 5px; + } + .award { @include border-radius(5px); @@ -40,6 +46,7 @@ } .awards-controls { + position: relative; margin-left: 10px; float: left; @@ -55,32 +62,64 @@ } } - .awards-menu { - padding: $gl-padding; - min-width: 214px; + .emoji-menu{ + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175); + box-shadow: 0 6px 12px rgba(0,0,0,.175); + + .emoji-menu-content { + padding: $gl-padding; + width: 300px; + height: 300px; + overflow-y: scroll; + + h5 { + clear: left; + } - > li { - cursor: pointer; - width: 30px; - height: 30px; - text-align: center; - @include border-radius(5px); + ul { + list-style-type: none; + margin-left: -20px; + margin-bottom: 20px; + overflow: auto; + } - img { - margin-bottom: 2px; + input.emoji-search{ + background: image-url("icon-search.png") 240px no-repeat; } - &:hover { - background-color: #ccc; + li { + cursor: pointer; + width: 30px; + height: 30px; + text-align: center; + float: left; + margin: 3px; + list-decorate: none; + @include border-radius(5px); + + &:hover { + background-color: #ccc; + } } } } } - - .awards-menu{ - li { - float: left; - margin: 3px; - } - } } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 0f3463a9144..deab805dbc2 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -5,7 +5,7 @@ border-bottom: 1px solid $border-color; color: #5c5d5e; font-size: 16px; - line-height: 42px; + line-height: 34px; .author { color: #5c5d5e; diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss new file mode 100644 index 00000000000..89a94c5a780 --- /dev/null +++ b/app/assets/stylesheets/pages/emojis.scss @@ -0,0 +1,1272 @@ +/* +File is generated by https://github.com/jakesgordon/sprite-factory and midified manualy +The source: gemojione gem. +*/ + +.emoji-icon{ + background-image: image-url("emoji.png"); + background-repeat: no-repeat; +} + +.emoji-0023-20E3 { background-position: 0px 0px; } +.emoji-0030-20E3 { background-position: -20px 0px; } +.emoji-0031-20E3 { background-position: -40px 0px; } +.emoji-0032-20E3 { background-position: -60px 0px; } +.emoji-0033-20E3 { background-position: -80px 0px; } +.emoji-0034-20E3 { background-position: -100px 0px; } +.emoji-0035-20E3 { background-position: -120px 0px; } +.emoji-0036-20E3 { background-position: -140px 0px; } +.emoji-0037-20E3 { background-position: -160px 0px; } +.emoji-0038-20E3 { background-position: -180px 0px; } +.emoji-0039-20E3 { background-position: -200px 0px; } +.emoji-00A9 { background-position: -220px 0px; } +.emoji-00AE { background-position: -240px 0px; } +.emoji-1F004 { background-position: -260px 0px; } +.emoji-1F0CF { background-position: -280px 0px; } +.emoji-1F170 { background-position: -300px 0px; } +.emoji-1F171 { background-position: -320px 0px; } +.emoji-1F17E { background-position: -340px 0px; } +.emoji-1F17F { background-position: -360px 0px; } +.emoji-1F18E { background-position: -380px 0px; } +.emoji-1F191 { background-position: -400px 0px; } +.emoji-1F192 { background-position: -420px 0px; } +.emoji-1F193 { background-position: -440px 0px; } +.emoji-1F194 { background-position: -460px 0px; } +.emoji-1F195 { background-position: -480px 0px; } +.emoji-1F196 { background-position: -500px 0px; } +.emoji-1F197 { background-position: -520px 0px; } +.emoji-1F198 { background-position: -540px 0px; } +.emoji-1F199 { background-position: -560px 0px; } +.emoji-1F19A { background-position: -580px 0px; } +.emoji-1F1E6-1F1E8 { background-position: -600px 0px; } +.emoji-1F1E6-1F1E9 { background-position: -620px 0px; } +.emoji-1F1E6-1F1EA { background-position: -640px 0px; } +.emoji-1F1E6-1F1EB { background-position: -660px 0px; } +.emoji-1F1E6-1F1EC { background-position: -680px 0px; } +.emoji-1F1E6-1F1EE { background-position: -700px 0px; } +.emoji-1F1E6-1F1F1 { background-position: -720px 0px; } +.emoji-1F1E6-1F1F2 { background-position: -740px 0px; } +.emoji-1F1E6-1F1F4 { background-position: -760px 0px; } +.emoji-1F1E6-1F1F7 { background-position: -780px 0px; } +.emoji-1F1E6-1F1F9 { background-position: -800px 0px; } +.emoji-1F1E6-1F1FA { background-position: -820px 0px; } +.emoji-1F1E6-1F1FC { background-position: -840px 0px; } +.emoji-1F1E6-1F1FF { background-position: -860px 0px; } +.emoji-1F1E7-1F1E6 { background-position: -880px 0px; } +.emoji-1F1E7-1F1E7 { background-position: -900px 0px; } +.emoji-1F1E7-1F1E9 { background-position: -920px 0px; } +.emoji-1F1E7-1F1EA { background-position: -940px 0px; } +.emoji-1F1E7-1F1EB { background-position: -960px 0px; } +.emoji-1F1E7-1F1EC { background-position: -980px 0px; } +.emoji-1F1E7-1F1ED { background-position: -1000px 0px; } +.emoji-1F1E7-1F1EE { background-position: -1020px 0px; } +.emoji-1F1E7-1F1EF { background-position: -1040px 0px; } +.emoji-1F1E7-1F1F2 { background-position: -1060px 0px; } +.emoji-1F1E7-1F1F3 { background-position: -1080px 0px; } +.emoji-1F1E7-1F1F4 { background-position: -1100px 0px; } +.emoji-1F1E7-1F1F7 { background-position: -1120px 0px; } +.emoji-1F1E7-1F1F8 { background-position: -1140px 0px; } +.emoji-1F1E7-1F1F9 { background-position: -1160px 0px; } +.emoji-1F1E7-1F1FC { background-position: -1180px 0px; } +.emoji-1F1E7-1F1FE { background-position: -1200px 0px; } +.emoji-1F1E7-1F1FF { background-position: -1220px 0px; } +.emoji-1F1E8-1F1E6 { background-position: -1240px 0px; } +.emoji-1F1E8-1F1E9 { background-position: -1260px 0px; } +.emoji-1F1E8-1F1EB { background-position: -1280px 0px; } +.emoji-1F1E8-1F1EC { background-position: -1300px 0px; } +.emoji-1F1E8-1F1ED { background-position: -1320px 0px; } +.emoji-1F1E8-1F1EE { background-position: -1340px 0px; } +.emoji-1F1E8-1F1F1 { background-position: -1360px 0px; } +.emoji-1F1E8-1F1F2 { background-position: -1380px 0px; } +.emoji-1F1E8-1F1F3 { background-position: -1400px 0px; } +.emoji-1F1E8-1F1F4 { background-position: -1420px 0px; } +.emoji-1F1E8-1F1F7 { background-position: -1440px 0px; } +.emoji-1F1E8-1F1FA { background-position: -1460px 0px; } +.emoji-1F1E8-1F1FB { background-position: -1480px 0px; } +.emoji-1F1E8-1F1FE { background-position: -1500px 0px; } +.emoji-1F1E8-1F1FF { background-position: -1520px 0px; } +.emoji-1F1E9-1F1EA { background-position: -1540px 0px; } +.emoji-1F1E9-1F1EF { background-position: -1560px 0px; } +.emoji-1F1E9-1F1F0 { background-position: -1580px 0px; } +.emoji-1F1E9-1F1F2 { background-position: -1600px 0px; } +.emoji-1F1E9-1F1F4 { background-position: -1620px 0px; } +.emoji-1F1E9-1F1FF { background-position: -1640px 0px; } +.emoji-1F1EA-1F1E8 { background-position: -1660px 0px; } +.emoji-1F1EA-1F1EA { background-position: -1680px 0px; } +.emoji-1F1EA-1F1EC { background-position: -1700px 0px; } +.emoji-1F1EA-1F1ED { background-position: -1720px 0px; } +.emoji-1F1EA-1F1F7 { background-position: -1740px 0px; } +.emoji-1F1EA-1F1F8 { background-position: -1760px 0px; } +.emoji-1F1EA-1F1F9 { background-position: -1780px 0px; } +.emoji-1F1EB-1F1EE { background-position: -1800px 0px; } +.emoji-1F1EB-1F1EF { background-position: -1820px 0px; } +.emoji-1F1EB-1F1F0 { background-position: -1840px 0px; } +.emoji-1F1EB-1F1F2 { background-position: -1860px 0px; } +.emoji-1F1EB-1F1F4 { background-position: -1880px 0px; } +.emoji-1F1EB-1F1F7 { background-position: -1900px 0px; } +.emoji-1F1EC-1F1E6 { background-position: -1920px 0px; } +.emoji-1F1EC-1F1E7 { background-position: -1940px 0px; } +.emoji-1F1EC-1F1E9 { background-position: -1960px 0px; } +.emoji-1F1EC-1F1EA { background-position: -1980px 0px; } +.emoji-1F1EC-1F1ED { background-position: -2000px 0px; } +.emoji-1F1EC-1F1EE { background-position: -2020px 0px; } +.emoji-1F1EC-1F1F1 { background-position: -2040px 0px; } +.emoji-1F1EC-1F1F2 { background-position: -2060px 0px; } +.emoji-1F1EC-1F1F3 { background-position: -2080px 0px; } +.emoji-1F1EC-1F1F6 { background-position: -2100px 0px; } +.emoji-1F1EC-1F1F7 { background-position: -2120px 0px; } +.emoji-1F1EC-1F1F9 { background-position: -2140px 0px; } +.emoji-1F1EC-1F1FA { background-position: -2160px 0px; } +.emoji-1F1EC-1F1FC { background-position: -2180px 0px; } +.emoji-1F1EC-1F1FE { background-position: -2200px 0px; } +.emoji-1F1ED-1F1F0 { background-position: -2220px 0px; } +.emoji-1F1ED-1F1F3 { background-position: -2240px 0px; } +.emoji-1F1ED-1F1F7 { background-position: -2260px 0px; } +.emoji-1F1ED-1F1F9 { background-position: -2280px 0px; } +.emoji-1F1ED-1F1FA { background-position: -2300px 0px; } +.emoji-1F1EE-1F1E9 { background-position: -2320px 0px; } +.emoji-1F1EE-1F1EA { background-position: -2340px 0px; } +.emoji-1F1EE-1F1F1 { background-position: -2360px 0px; } +.emoji-1F1EE-1F1F3 { background-position: -2380px 0px; } +.emoji-1F1EE-1F1F6 { background-position: -2400px 0px; } +.emoji-1F1EE-1F1F7 { background-position: -2420px 0px; } +.emoji-1F1EE-1F1F8 { background-position: -2440px 0px; } +.emoji-1F1EE-1F1F9 { background-position: -2460px 0px; } +.emoji-1F1EF-1F1EA { background-position: -2480px 0px; } +.emoji-1F1EF-1F1F2 { background-position: -2500px 0px; } +.emoji-1F1EF-1F1F4 { background-position: -2520px 0px; } +.emoji-1F1EF-1F1F5 { background-position: -2540px 0px; } +.emoji-1F1F0-1F1EA { background-position: -2560px 0px; } +.emoji-1F1F0-1F1EC { background-position: -2580px 0px; } +.emoji-1F1F0-1F1ED { background-position: -2600px 0px; } +.emoji-1F1F0-1F1EE { background-position: -2620px 0px; } +.emoji-1F1F0-1F1F2 { background-position: -2640px 0px; } +.emoji-1F1F0-1F1F3 { background-position: -2660px 0px; } +.emoji-1F1F0-1F1F5 { background-position: -2680px 0px; } +.emoji-1F1F0-1F1F7 { background-position: -2700px 0px; } +.emoji-1F1F0-1F1FC { background-position: -2720px 0px; } +.emoji-1F1F0-1F1FE { background-position: -2740px 0px; } +.emoji-1F1F0-1F1FF { background-position: -2760px 0px; } +.emoji-1F1F1-1F1E6 { background-position: -2780px 0px; } +.emoji-1F1F1-1F1E7 { background-position: -2800px 0px; } +.emoji-1F1F1-1F1E8 { background-position: -2820px 0px; } +.emoji-1F1F1-1F1EE { background-position: -2840px 0px; } +.emoji-1F1F1-1F1F0 { background-position: -2860px 0px; } +.emoji-1F1F1-1F1F7 { background-position: -2880px 0px; } +.emoji-1F1F1-1F1F8 { background-position: -2900px 0px; } +.emoji-1F1F1-1F1F9 { background-position: -2920px 0px; } +.emoji-1F1F1-1F1FA { background-position: -2940px 0px; } +.emoji-1F1F1-1F1FB { background-position: -2960px 0px; } +.emoji-1F1F1-1F1FE { background-position: -2980px 0px; } +.emoji-1F1F2-1F1E6 { background-position: -3000px 0px; } +.emoji-1F1F2-1F1E8 { background-position: -3020px 0px; } +.emoji-1F1F2-1F1E9 { background-position: -3040px 0px; } +.emoji-1F1F2-1F1EA { background-position: -3060px 0px; } +.emoji-1F1F2-1F1EC { background-position: -3080px 0px; } +.emoji-1F1F2-1F1ED { background-position: -3100px 0px; } +.emoji-1F1F2-1F1F0 { background-position: -3120px 0px; } +.emoji-1F1F2-1F1F1 { background-position: -3140px 0px; } +.emoji-1F1F2-1F1F2 { background-position: -3160px 0px; } +.emoji-1F1F2-1F1F3 { background-position: -3180px 0px; } +.emoji-1F1F2-1F1F4 { background-position: -3200px 0px; } +.emoji-1F1F2-1F1F7 { background-position: -3220px 0px; } +.emoji-1F1F2-1F1F8 { background-position: -3240px 0px; } +.emoji-1F1F2-1F1F9 { background-position: -3260px 0px; } +.emoji-1F1F2-1F1FA { background-position: -3280px 0px; } +.emoji-1F1F2-1F1FB { background-position: -3300px 0px; } +.emoji-1F1F2-1F1FC { background-position: -3320px 0px; } +.emoji-1F1F2-1F1FD { background-position: -3340px 0px; } +.emoji-1F1F2-1F1FE { background-position: -3360px 0px; } +.emoji-1F1F2-1F1FF { background-position: -3380px 0px; } +.emoji-1F1F3-1F1E6 { background-position: -3400px 0px; } +.emoji-1F1F3-1F1E8 { background-position: -3420px 0px; } +.emoji-1F1F3-1F1EA { background-position: -3440px 0px; } +.emoji-1F1F3-1F1EC { background-position: -3460px 0px; } +.emoji-1F1F3-1F1EE { background-position: -3480px 0px; } +.emoji-1F1F3-1F1F1 { background-position: -3500px 0px; } +.emoji-1F1F3-1F1F4 { background-position: -3520px 0px; } +.emoji-1F1F3-1F1F5 { background-position: -3540px 0px; } +.emoji-1F1F3-1F1F7 { background-position: -3560px 0px; } +.emoji-1F1F3-1F1FA { background-position: -3580px 0px; } +.emoji-1F1F3-1F1FF { background-position: -3600px 0px; } +.emoji-1F1F4-1F1F2 { background-position: -3620px 0px; } +.emoji-1F1F5-1F1E6 { background-position: -3640px 0px; } +.emoji-1F1F5-1F1EA { background-position: -3660px 0px; } +.emoji-1F1F5-1F1EB { background-position: -3680px 0px; } +.emoji-1F1F5-1F1EC { background-position: -3700px 0px; } +.emoji-1F1F5-1F1ED { background-position: -3720px 0px; } +.emoji-1F1F5-1F1F0 { background-position: -3740px 0px; } +.emoji-1F1F5-1F1F1 { background-position: -3760px 0px; } +.emoji-1F1F5-1F1F7 { background-position: -3780px 0px; } +.emoji-1F1F5-1F1F8 { background-position: -3800px 0px; } +.emoji-1F1F5-1F1F9 { background-position: -3820px 0px; } +.emoji-1F1F5-1F1FC { background-position: -3840px 0px; } +.emoji-1F1F5-1F1FE { background-position: -3860px 0px; } +.emoji-1F1F6-1F1E6 { background-position: -3880px 0px; } +.emoji-1F1F7-1F1F4 { background-position: -3900px 0px; } +.emoji-1F1F7-1F1F8 { background-position: -3920px 0px; } +.emoji-1F1F7-1F1FA { background-position: -3940px 0px; } +.emoji-1F1F7-1F1FC { background-position: -3960px 0px; } +.emoji-1F1F8-1F1E6 { background-position: -3980px 0px; } +.emoji-1F1F8-1F1E7 { background-position: -4000px 0px; } +.emoji-1F1F8-1F1E8 { background-position: -4020px 0px; } +.emoji-1F1F8-1F1E9 { background-position: -4040px 0px; } +.emoji-1F1F8-1F1EA { background-position: -4060px 0px; } +.emoji-1F1F8-1F1EC { background-position: -4080px 0px; } +.emoji-1F1F8-1F1ED { background-position: -4100px 0px; } +.emoji-1F1F8-1F1EE { background-position: -4120px 0px; } +.emoji-1F1F8-1F1F0 { background-position: -4140px 0px; } +.emoji-1F1F8-1F1F1 { background-position: -4160px 0px; } +.emoji-1F1F8-1F1F2 { background-position: -4180px 0px; } +.emoji-1F1F8-1F1F3 { background-position: -4200px 0px; } +.emoji-1F1F8-1F1F4 { background-position: -4220px 0px; } +.emoji-1F1F8-1F1F7 { background-position: -4240px 0px; } +.emoji-1F1F8-1F1F9 { background-position: -4260px 0px; } +.emoji-1F1F8-1F1FB { background-position: -4280px 0px; } +.emoji-1F1F8-1F1FE { background-position: -4300px 0px; } +.emoji-1F1F8-1F1FF { background-position: -4320px 0px; } +.emoji-1F1F9-1F1E9 { background-position: -4340px 0px; } +.emoji-1F1F9-1F1EC { background-position: -4360px 0px; } +.emoji-1F1F9-1F1ED { background-position: -4380px 0px; } +.emoji-1F1F9-1F1EF { background-position: -4400px 0px; } +.emoji-1F1F9-1F1F1 { background-position: -4420px 0px; } +.emoji-1F1F9-1F1F2 { background-position: -4440px 0px; } +.emoji-1F1F9-1F1F3 { background-position: -4460px 0px; } +.emoji-1F1F9-1F1F4 { background-position: -4480px 0px; } +.emoji-1F1F9-1F1F7 { background-position: -4500px 0px; } +.emoji-1F1F9-1F1F9 { background-position: -4520px 0px; } +.emoji-1F1F9-1F1FB { background-position: -4540px 0px; } +.emoji-1F1F9-1F1FC { background-position: -4560px 0px; } +.emoji-1F1F9-1F1FF { background-position: -4580px 0px; } +.emoji-1F1FA-1F1E6 { background-position: -4600px 0px; } +.emoji-1F1FA-1F1EC { background-position: -4620px 0px; } +.emoji-1F1FA-1F1F8 { background-position: -4640px 0px; } +.emoji-1F1FA-1F1FE { background-position: -4660px 0px; } +.emoji-1F1FA-1F1FF { background-position: -4680px 0px; } +.emoji-1F1FB-1F1E6 { background-position: -4700px 0px; } +.emoji-1F1FB-1F1E8 { background-position: -4720px 0px; } +.emoji-1F1FB-1F1EA { background-position: -4740px 0px; } +.emoji-1F1FB-1F1EE { background-position: -4760px 0px; } +.emoji-1F1FB-1F1F3 { background-position: -4780px 0px; } +.emoji-1F1FB-1F1FA { background-position: -4800px 0px; } +.emoji-1F1FC-1F1EB { background-position: -4820px 0px; } +.emoji-1F1FC-1F1F8 { background-position: -4840px 0px; } +.emoji-1F1FD-1F1F0 { background-position: -4860px 0px; } +.emoji-1F1FE-1F1EA { background-position: -4880px 0px; } +.emoji-1F1FF-1F1E6 { background-position: -4900px 0px; } +.emoji-1F1FF-1F1F2 { background-position: -4920px 0px; } +.emoji-1F1FF-1F1FC { background-position: -4940px 0px; } +.emoji-1F201 { background-position: -4960px 0px; } +.emoji-1F202 { background-position: -4980px 0px; } +.emoji-1F21A { background-position: -5000px 0px; } +.emoji-1F22F { background-position: -5020px 0px; } +.emoji-1F232 { background-position: -5040px 0px; } +.emoji-1F233 { background-position: -5060px 0px; } +.emoji-1F234 { background-position: -5080px 0px; } +.emoji-1F235 { background-position: -5100px 0px; } +.emoji-1F236 { background-position: -5120px 0px; } +.emoji-1F237 { background-position: -5140px 0px; } +.emoji-1F238 { background-position: -5160px 0px; } +.emoji-1F239 { background-position: -5180px 0px; } +.emoji-1F23A { background-position: -5200px 0px; } +.emoji-1F250 { background-position: -5220px 0px; } +.emoji-1F251 { background-position: -5240px 0px; } +.emoji-1F300 { background-position: -5260px 0px; } +.emoji-1F301 { background-position: -5280px 0px; } +.emoji-1F302 { background-position: -5300px 0px; } +.emoji-1F303 { background-position: -5320px 0px; } +.emoji-1F304 { background-position: -5340px 0px; } +.emoji-1F305 { background-position: -5360px 0px; } +.emoji-1F306 { background-position: -5380px 0px; } +.emoji-1F307 { background-position: -5400px 0px; } +.emoji-1F308 { background-position: -5420px 0px; } +.emoji-1F309 { background-position: -5440px 0px; } +.emoji-1F30A { background-position: -5460px 0px; } +.emoji-1F30B { background-position: -5480px 0px; } +.emoji-1F30C { background-position: -5500px 0px; } +.emoji-1F30D { background-position: -5520px 0px; } +.emoji-1F30E { background-position: -5540px 0px; } +.emoji-1F30F { background-position: -5560px 0px; } +.emoji-1F310 { background-position: -5580px 0px; } +.emoji-1F311 { background-position: -5600px 0px; } +.emoji-1F312 { background-position: -5620px 0px; } +.emoji-1F313 { background-position: -5640px 0px; } +.emoji-1F314 { background-position: -5660px 0px; } +.emoji-1F315 { background-position: -5680px 0px; } +.emoji-1F316 { background-position: -5700px 0px; } +.emoji-1F317 { background-position: -5720px 0px; } +.emoji-1F318 { background-position: -5740px 0px; } +.emoji-1F319 { background-position: -5760px 0px; } +.emoji-1F31A { background-position: -5780px 0px; } +.emoji-1F31B { background-position: -5800px 0px; } +.emoji-1F31C { background-position: -5820px 0px; } +.emoji-1F31D { background-position: -5840px 0px; } +.emoji-1F31E { background-position: -5860px 0px; } +.emoji-1F31F { background-position: -5880px 0px; } +.emoji-1F320 { background-position: -5900px 0px; } +.emoji-1F321 { background-position: -5920px 0px; } +.emoji-1F327 { background-position: -5940px 0px; } +.emoji-1F328 { background-position: -5960px 0px; } +.emoji-1F329 { background-position: -5980px 0px; } +.emoji-1F32A { background-position: -6000px 0px; } +.emoji-1F32B { background-position: -6020px 0px; } +.emoji-1F32C { background-position: -6040px 0px; } +.emoji-1F330 { background-position: -6060px 0px; } +.emoji-1F331 { background-position: -6080px 0px; } +.emoji-1F332 { background-position: -6100px 0px; } +.emoji-1F333 { background-position: -6120px 0px; } +.emoji-1F334 { background-position: -6140px 0px; } +.emoji-1F335 { background-position: -6160px 0px; } +.emoji-1F336 { background-position: -6180px 0px; } +.emoji-1F337 { background-position: -6200px 0px; } +.emoji-1F338 { background-position: -6220px 0px; } +.emoji-1F339 { background-position: -6240px 0px; } +.emoji-1F33A { background-position: -6260px 0px; } +.emoji-1F33B { background-position: -6280px 0px; } +.emoji-1F33C { background-position: -6300px 0px; } +.emoji-1F33D { background-position: -6320px 0px; } +.emoji-1F33E { background-position: -6340px 0px; } +.emoji-1F33F { background-position: -6360px 0px; } +.emoji-1F340 { background-position: -6380px 0px; } +.emoji-1F341 { background-position: -6400px 0px; } +.emoji-1F342 { background-position: -6420px 0px; } +.emoji-1F343 { background-position: -6440px 0px; } +.emoji-1F344 { background-position: -6460px 0px; } +.emoji-1F345 { background-position: -6480px 0px; } +.emoji-1F346 { background-position: -6500px 0px; } +.emoji-1F347 { background-position: -6520px 0px; } +.emoji-1F348 { background-position: -6540px 0px; } +.emoji-1F349 { background-position: -6560px 0px; } +.emoji-1F34A { background-position: -6580px 0px; } +.emoji-1F34B { background-position: -6600px 0px; } +.emoji-1F34C { background-position: -6620px 0px; } +.emoji-1F34D { background-position: -6640px 0px; } +.emoji-1F34E { background-position: -6660px 0px; } +.emoji-1F34F { background-position: -6680px 0px; } +.emoji-1F350 { background-position: -6700px 0px; } +.emoji-1F351 { background-position: -6720px 0px; } +.emoji-1F352 { background-position: -6740px 0px; } +.emoji-1F353 { background-position: -6760px 0px; } +.emoji-1F354 { background-position: -6780px 0px; } +.emoji-1F355 { background-position: -6800px 0px; } +.emoji-1F356 { background-position: -6820px 0px; } +.emoji-1F357 { background-position: -6840px 0px; } +.emoji-1F358 { background-position: -6860px 0px; } +.emoji-1F359 { background-position: -6880px 0px; } +.emoji-1F35A { background-position: -6900px 0px; } +.emoji-1F35B { background-position: -6920px 0px; } +.emoji-1F35C { background-position: -6940px 0px; } +.emoji-1F35D { background-position: -6960px 0px; } +.emoji-1F35E { background-position: -6980px 0px; } +.emoji-1F35F { background-position: -7000px 0px; } +.emoji-1F360 { background-position: -7020px 0px; } +.emoji-1F361 { background-position: -7040px 0px; } +.emoji-1F362 { background-position: -7060px 0px; } +.emoji-1F363 { background-position: -7080px 0px; } +.emoji-1F364 { background-position: -7100px 0px; } +.emoji-1F365 { background-position: -7120px 0px; } +.emoji-1F366 { background-position: -7140px 0px; } +.emoji-1F367 { background-position: -7160px 0px; } +.emoji-1F368 { background-position: -7180px 0px; } +.emoji-1F369 { background-position: -7200px 0px; } +.emoji-1F36A { background-position: -7220px 0px; } +.emoji-1F36B { background-position: -7240px 0px; } +.emoji-1F36C { background-position: -7260px 0px; } +.emoji-1F36D { background-position: -7280px 0px; } +.emoji-1F36E { background-position: -7300px 0px; } +.emoji-1F36F { background-position: -7320px 0px; } +.emoji-1F370 { background-position: -7340px 0px; } +.emoji-1F371 { background-position: -7360px 0px; } +.emoji-1F372 { background-position: -7380px 0px; } +.emoji-1F373 { background-position: -7400px 0px; } +.emoji-1F374 { background-position: -7420px 0px; } +.emoji-1F375 { background-position: -7440px 0px; } +.emoji-1F376 { background-position: -7460px 0px; } +.emoji-1F377 { background-position: -7480px 0px; } +.emoji-1F378 { background-position: -7500px 0px; } +.emoji-1F379 { background-position: -7520px 0px; } +.emoji-1F37A { background-position: -7540px 0px; } +.emoji-1F37B { background-position: -7560px 0px; } +.emoji-1F37C { background-position: -7580px 0px; } +.emoji-1F37D { background-position: -7600px 0px; } +.emoji-1F380 { background-position: -7620px 0px; } +.emoji-1F381 { background-position: -7640px 0px; } +.emoji-1F382 { background-position: -7660px 0px; } +.emoji-1F383 { background-position: -7680px 0px; } +.emoji-1F384 { background-position: -7700px 0px; } +.emoji-1F385 { background-position: -7720px 0px; } +.emoji-1F386 { background-position: -7740px 0px; } +.emoji-1F387 { background-position: -7760px 0px; } +.emoji-1F388 { background-position: -7780px 0px; } +.emoji-1F389 { background-position: -7800px 0px; } +.emoji-1F38A { background-position: -7820px 0px; } +.emoji-1F38B { background-position: -7840px 0px; } +.emoji-1F38C { background-position: -7860px 0px; } +.emoji-1F38D { background-position: -7880px 0px; } +.emoji-1F38E { background-position: -7900px 0px; } +.emoji-1F38F { background-position: -7920px 0px; } +.emoji-1F390 { background-position: -7940px 0px; } +.emoji-1F391 { background-position: -7960px 0px; } +.emoji-1F392 { background-position: -7980px 0px; } +.emoji-1F393 { background-position: -8000px 0px; } +.emoji-1F394 { background-position: -8020px 0px; } +.emoji-1F395 { background-position: -8040px 0px; } +.emoji-1F396 { background-position: -8060px 0px; } +.emoji-1F397 { background-position: -8080px 0px; } +.emoji-1F398 { background-position: -8100px 0px; } +.emoji-1F399 { background-position: -8120px 0px; } +.emoji-1F39A { background-position: -8140px 0px; } +.emoji-1F39B { background-position: -8160px 0px; } +.emoji-1F39C { background-position: -8180px 0px; } +.emoji-1F39D { background-position: -8200px 0px; } +.emoji-1F39E { background-position: -8220px 0px; } +.emoji-1F39F { background-position: -8240px 0px; } +.emoji-1F3A0 { background-position: -8260px 0px; } +.emoji-1F3A1 { background-position: -8280px 0px; } +.emoji-1F3A2 { background-position: -8300px 0px; } +.emoji-1F3A3 { background-position: -8320px 0px; } +.emoji-1F3A4 { background-position: -8340px 0px; } +.emoji-1F3A5 { background-position: -8360px 0px; } +.emoji-1F3A6 { background-position: -8380px 0px; } +.emoji-1F3A7 { background-position: -8400px 0px; } +.emoji-1F3A8 { background-position: -8420px 0px; } +.emoji-1F3A9 { background-position: -8440px 0px; } +.emoji-1F3AA { background-position: -8460px 0px; } +.emoji-1F3AB { background-position: -8480px 0px; } +.emoji-1F3AC { background-position: -8500px 0px; } +.emoji-1F3AD { background-position: -8520px 0px; } +.emoji-1F3AE { background-position: -8540px 0px; } +.emoji-1F3AF { background-position: -8560px 0px; } +.emoji-1F3B0 { background-position: -8580px 0px; } +.emoji-1F3B1 { background-position: -8600px 0px; } +.emoji-1F3B2 { background-position: -8620px 0px; } +.emoji-1F3B3 { background-position: -8640px 0px; } +.emoji-1F3B4 { background-position: -8660px 0px; } +.emoji-1F3B5 { background-position: -8680px 0px; } +.emoji-1F3B6 { background-position: -8700px 0px; } +.emoji-1F3B7 { background-position: -8720px 0px; } +.emoji-1F3B8 { background-position: -8740px 0px; } +.emoji-1F3B9 { background-position: -8760px 0px; } +.emoji-1F3BA { background-position: -8780px 0px; } +.emoji-1F3BB { background-position: -8800px 0px; } +.emoji-1F3BC { background-position: -8820px 0px; } +.emoji-1F3BD { background-position: -8840px 0px; } +.emoji-1F3BE { background-position: -8860px 0px; } +.emoji-1F3BF { background-position: -8880px 0px; } +.emoji-1F3C0 { background-position: -8900px 0px; } +.emoji-1F3C1 { background-position: -8920px 0px; } +.emoji-1F3C2 { background-position: -8940px 0px; } +.emoji-1F3C3 { background-position: -8960px 0px; } +.emoji-1F3C4 { background-position: -8980px 0px; } +.emoji-1F3C5 { background-position: -9000px 0px; } +.emoji-1F3C6 { background-position: -9020px 0px; } +.emoji-1F3C7 { background-position: -9040px 0px; } +.emoji-1F3C8 { background-position: -9060px 0px; } +.emoji-1F3C9 { background-position: -9080px 0px; } +.emoji-1F3CA { background-position: -9100px 0px; } +.emoji-1F3CB { background-position: -9120px 0px; } +.emoji-1F3CC { background-position: -9140px 0px; } +.emoji-1F3CD { background-position: -9160px 0px; } +.emoji-1F3CE { background-position: -9180px 0px; } +.emoji-1F3D4 { background-position: -9200px 0px; } +.emoji-1F3D5 { background-position: -9220px 0px; } +.emoji-1F3D6 { background-position: -9240px 0px; } +.emoji-1F3D7 { background-position: -9260px 0px; } +.emoji-1F3D8 { background-position: -9280px 0px; } +.emoji-1F3D9 { background-position: -9300px 0px; } +.emoji-1F3DA { background-position: -9320px 0px; } +.emoji-1F3DB { background-position: -9340px 0px; } +.emoji-1F3DC { background-position: -9360px 0px; } +.emoji-1F3DD { background-position: -9380px 0px; } +.emoji-1F3DE { background-position: -9400px 0px; } +.emoji-1F3DF { background-position: -9420px 0px; } +.emoji-1F3E0 { background-position: -9440px 0px; } +.emoji-1F3E1 { background-position: -9460px 0px; } +.emoji-1F3E2 { background-position: -9480px 0px; } +.emoji-1F3E3 { background-position: -9500px 0px; } +.emoji-1F3E4 { background-position: -9520px 0px; } +.emoji-1F3E5 { background-position: -9540px 0px; } +.emoji-1F3E6 { background-position: -9560px 0px; } +.emoji-1F3E7 { background-position: -9580px 0px; } +.emoji-1F3E8 { background-position: -9600px 0px; } +.emoji-1F3E9 { background-position: -9620px 0px; } +.emoji-1F3EA { background-position: -9640px 0px; } +.emoji-1F3EB { background-position: -9660px 0px; } +.emoji-1F3EC { background-position: -9680px 0px; } +.emoji-1F3ED { background-position: -9700px 0px; } +.emoji-1F3EE { background-position: -9720px 0px; } +.emoji-1F3EF { background-position: -9740px 0px; } +.emoji-1F3F0 { background-position: -9760px 0px; } +.emoji-1F3F1 { background-position: -9780px 0px; } +.emoji-1F3F2 { background-position: -9800px 0px; } +.emoji-1F3F3 { background-position: -9820px 0px; } +.emoji-1F3F4 { background-position: -9840px 0px; } +.emoji-1F3F5 { background-position: -9860px 0px; } +.emoji-1F3F6 { background-position: -9880px 0px; } +.emoji-1F3F7 { background-position: -9900px 0px; } +.emoji-1F400 { background-position: -9920px 0px; } +.emoji-1F401 { background-position: -9940px 0px; } +.emoji-1F402 { background-position: -9960px 0px; } +.emoji-1F403 { background-position: -9980px 0px; } +.emoji-1F404 { background-position: -10000px 0px; } +.emoji-1F405 { background-position: -10020px 0px; } +.emoji-1F406 { background-position: -10040px 0px; } +.emoji-1F407 { background-position: -10060px 0px; } +.emoji-1F408 { background-position: -10080px 0px; } +.emoji-1F409 { background-position: -10100px 0px; } +.emoji-1F40A { background-position: -10120px 0px; } +.emoji-1F40B { background-position: -10140px 0px; } +.emoji-1F40C { background-position: -10160px 0px; } +.emoji-1F40D { background-position: -10180px 0px; } +.emoji-1F40E { background-position: -10200px 0px; } +.emoji-1F40F { background-position: -10220px 0px; } +.emoji-1F410 { background-position: -10240px 0px; } +.emoji-1F411 { background-position: -10260px 0px; } +.emoji-1F412 { background-position: -10280px 0px; } +.emoji-1F413 { background-position: -10300px 0px; } +.emoji-1F414 { background-position: -10320px 0px; } +.emoji-1F415 { background-position: -10340px 0px; } +.emoji-1F416 { background-position: -10360px 0px; } +.emoji-1F417 { background-position: -10380px 0px; } +.emoji-1F418 { background-position: -10400px 0px; } +.emoji-1F419 { background-position: -10420px 0px; } +.emoji-1F41A { background-position: -10440px 0px; } +.emoji-1F41B { background-position: -10460px 0px; } +.emoji-1F41C { background-position: -10480px 0px; } +.emoji-1F41D { background-position: -10500px 0px; } +.emoji-1F41E { background-position: -10520px 0px; } +.emoji-1F41F { background-position: -10540px 0px; } +.emoji-1F420 { background-position: -10560px 0px; } +.emoji-1F421 { background-position: -10580px 0px; } +.emoji-1F422 { background-position: -10600px 0px; } +.emoji-1F423 { background-position: -10620px 0px; } +.emoji-1F424 { background-position: -10640px 0px; } +.emoji-1F425 { background-position: -10660px 0px; } +.emoji-1F426 { background-position: -10680px 0px; } +.emoji-1F427 { background-position: -10700px 0px; } +.emoji-1F428 { background-position: -10720px 0px; } +.emoji-1F429 { background-position: -10740px 0px; } +.emoji-1F42A { background-position: -10760px 0px; } +.emoji-1F42B { background-position: -10780px 0px; } +.emoji-1F42C { background-position: -10800px 0px; } +.emoji-1F42D { background-position: -10820px 0px; } +.emoji-1F42E { background-position: -10840px 0px; } +.emoji-1F42F { background-position: -10860px 0px; } +.emoji-1F430 { background-position: -10880px 0px; } +.emoji-1F431 { background-position: -10900px 0px; } +.emoji-1F432 { background-position: -10920px 0px; } +.emoji-1F433 { background-position: -10940px 0px; } +.emoji-1F434 { background-position: -10960px 0px; } +.emoji-1F435 { background-position: -10980px 0px; } +.emoji-1F436 { background-position: -11000px 0px; } +.emoji-1F437 { background-position: -11020px 0px; } +.emoji-1F438 { background-position: -11040px 0px; } +.emoji-1F439 { background-position: -11060px 0px; } +.emoji-1F43A { background-position: -11080px 0px; } +.emoji-1F43B { background-position: -11100px 0px; } +.emoji-1F43C { background-position: -11120px 0px; } +.emoji-1F43D { background-position: -11140px 0px; } +.emoji-1F43E { background-position: -11160px 0px; } +.emoji-1F43F { background-position: -11180px 0px; } +.emoji-1F440 { background-position: -11200px 0px; } +.emoji-1F441 { background-position: -11220px 0px; } +.emoji-1F442 { background-position: -11240px 0px; } +.emoji-1F443 { background-position: -11260px 0px; } +.emoji-1F444 { background-position: -11280px 0px; } +.emoji-1F445 { background-position: -11300px 0px; } +.emoji-1F446 { background-position: -11320px 0px; } +.emoji-1F447 { background-position: -11340px 0px; } +.emoji-1F448 { background-position: -11360px 0px; } +.emoji-1F449 { background-position: -11380px 0px; } +.emoji-1F44A { background-position: -11400px 0px; } +.emoji-1F44B { background-position: -11420px 0px; } +.emoji-1F44C { background-position: -11440px 0px; } +.emoji-1F44D { background-position: -11460px 0px; } +.emoji-1F44E { background-position: -11480px 0px; } +.emoji-1F44F { background-position: -11500px 0px; } +.emoji-1F450 { background-position: -11520px 0px; } +.emoji-1F451 { background-position: -11540px 0px; } +.emoji-1F452 { background-position: -11560px 0px; } +.emoji-1F453 { background-position: -11580px 0px; } +.emoji-1F454 { background-position: -11600px 0px; } +.emoji-1F455 { background-position: -11620px 0px; } +.emoji-1F456 { background-position: -11640px 0px; } +.emoji-1F457 { background-position: -11660px 0px; } +.emoji-1F458 { background-position: -11680px 0px; } +.emoji-1F459 { background-position: -11700px 0px; } +.emoji-1F45A { background-position: -11720px 0px; } +.emoji-1F45B { background-position: -11740px 0px; } +.emoji-1F45C { background-position: -11760px 0px; } +.emoji-1F45D { background-position: -11780px 0px; } +.emoji-1F45E { background-position: -11800px 0px; } +.emoji-1F45F { background-position: -11820px 0px; } +.emoji-1F460 { background-position: -11840px 0px; } +.emoji-1F461 { background-position: -11860px 0px; } +.emoji-1F462 { background-position: -11880px 0px; } +.emoji-1F463 { background-position: -11900px 0px; } +.emoji-1F464 { background-position: -11920px 0px; } +.emoji-1F465 { background-position: -11940px 0px; } +.emoji-1F466 { background-position: -11960px 0px; } +.emoji-1F467 { background-position: -11980px 0px; } +.emoji-1F468 { background-position: -12000px 0px; } +.emoji-1F468-1F468-1F466 { background-position: -12020px 0px; } +.emoji-1F468-1F468-1F466-1F466 { background-position: -12040px 0px; } +.emoji-1F468-1F468-1F467 { background-position: -12060px 0px; } +.emoji-1F468-1F468-1F467-1F466 { background-position: -12080px 0px; } +.emoji-1F468-1F468-1F467-1F467 { background-position: -12100px 0px; } +.emoji-1F468-1F469-1F466-1F466 { background-position: -12120px 0px; } +.emoji-1F468-1F469-1F467 { background-position: -12140px 0px; } +.emoji-1F468-1F469-1F467-1F466 { background-position: -12160px 0px; } +.emoji-1F468-1F469-1F467-1F467 { background-position: -12180px 0px; } +.emoji-1F468-2764-1F468 { background-position: -12200px 0px; } +.emoji-1F468-2764-1F48B-1F468 { background-position: -12220px 0px; } +.emoji-1F469 { background-position: -12240px 0px; } +.emoji-1F469-1F469-1F466 { background-position: -12260px 0px; } +.emoji-1F469-1F469-1F466-1F466 { background-position: -12280px 0px; } +.emoji-1F469-1F469-1F467 { background-position: -12300px 0px; } +.emoji-1F469-1F469-1F467-1F466 { background-position: -12320px 0px; } +.emoji-1F469-1F469-1F467-1F467 { background-position: -12340px 0px; } +.emoji-1F469-2764-1F469 { background-position: -12360px 0px; } +.emoji-1F469-2764-1F48B-1F469 { background-position: -12380px 0px; } +.emoji-1F46A { background-position: -12400px 0px; } +.emoji-1F46B { background-position: -12420px 0px; } +.emoji-1F46C { background-position: -12440px 0px; } +.emoji-1F46D { background-position: -12460px 0px; } +.emoji-1F46E { background-position: -12480px 0px; } +.emoji-1F46F { background-position: -12500px 0px; } +.emoji-1F470 { background-position: -12520px 0px; } +.emoji-1F471 { background-position: -12540px 0px; } +.emoji-1F472 { background-position: -12560px 0px; } +.emoji-1F473 { background-position: -12580px 0px; } +.emoji-1F474 { background-position: -12600px 0px; } +.emoji-1F475 { background-position: -12620px 0px; } +.emoji-1F476 { background-position: -12640px 0px; } +.emoji-1F477 { background-position: -12660px 0px; } +.emoji-1F478 { background-position: -12680px 0px; } +.emoji-1F479 { background-position: -12700px 0px; } +.emoji-1F47A { background-position: -12720px 0px; } +.emoji-1F47B { background-position: -12740px 0px; } +.emoji-1F47C { background-position: -12760px 0px; } +.emoji-1F47D { background-position: -12780px 0px; } +.emoji-1F47E { background-position: -12800px 0px; } +.emoji-1F47F { background-position: -12820px 0px; } +.emoji-1F480 { background-position: -12840px 0px; } +.emoji-1F481 { background-position: -12860px 0px; } +.emoji-1F482 { background-position: -12880px 0px; } +.emoji-1F483 { background-position: -12900px 0px; } +.emoji-1F484 { background-position: -12920px 0px; } +.emoji-1F485 { background-position: -12940px 0px; } +.emoji-1F486 { background-position: -12960px 0px; } +.emoji-1F487 { background-position: -12980px 0px; } +.emoji-1F488 { background-position: -13000px 0px; } +.emoji-1F489 { background-position: -13020px 0px; } +.emoji-1F48A { background-position: -13040px 0px; } +.emoji-1F48B { background-position: -13060px 0px; } +.emoji-1F48C { background-position: -13080px 0px; } +.emoji-1F48D { background-position: -13100px 0px; } +.emoji-1F48E { background-position: -13120px 0px; } +.emoji-1F48F { background-position: -13140px 0px; } +.emoji-1F490 { background-position: -13160px 0px; } +.emoji-1F491 { background-position: -13180px 0px; } +.emoji-1F492 { background-position: -13200px 0px; } +.emoji-1F493 { background-position: -13220px 0px; } +.emoji-1F494 { background-position: -13240px 0px; } +.emoji-1F495 { background-position: -13260px 0px; } +.emoji-1F496 { background-position: -13280px 0px; } +.emoji-1F497 { background-position: -13300px 0px; } +.emoji-1F498 { background-position: -13320px 0px; } +.emoji-1F499 { background-position: -13340px 0px; } +.emoji-1F49A { background-position: -13360px 0px; } +.emoji-1F49B { background-position: -13380px 0px; } +.emoji-1F49C { background-position: -13400px 0px; } +.emoji-1F49D { background-position: -13420px 0px; } +.emoji-1F49E { background-position: -13440px 0px; } +.emoji-1F49F { background-position: -13460px 0px; } +.emoji-1F4A0 { background-position: -13480px 0px; } +.emoji-1F4A1 { background-position: -13500px 0px; } +.emoji-1F4A2 { background-position: -13520px 0px; } +.emoji-1F4A3 { background-position: -13540px 0px; } +.emoji-1F4A4 { background-position: -13560px 0px; } +.emoji-1F4A5 { background-position: -13580px 0px; } +.emoji-1F4A6 { background-position: -13600px 0px; } +.emoji-1F4A7 { background-position: -13620px 0px; } +.emoji-1F4A8 { background-position: -13640px 0px; } +.emoji-1F4A9 { background-position: -13660px 0px; } +.emoji-1F4AA { background-position: -13680px 0px; } +.emoji-1F4AB { background-position: -13700px 0px; } +.emoji-1F4AC { background-position: -13720px 0px; } +.emoji-1F4AD { background-position: -13740px 0px; } +.emoji-1F4AE { background-position: -13760px 0px; } +.emoji-1F4AF { background-position: -13780px 0px; } +.emoji-1F4B0 { background-position: -13800px 0px; } +.emoji-1F4B1 { background-position: -13820px 0px; } +.emoji-1F4B2 { background-position: -13840px 0px; } +.emoji-1F4B3 { background-position: -13860px 0px; } +.emoji-1F4B4 { background-position: -13880px 0px; } +.emoji-1F4B5 { background-position: -13900px 0px; } +.emoji-1F4B6 { background-position: -13920px 0px; } +.emoji-1F4B7 { background-position: -13940px 0px; } +.emoji-1F4B8 { background-position: -13960px 0px; } +.emoji-1F4B9 { background-position: -13980px 0px; } +.emoji-1F4BA { background-position: -14000px 0px; } +.emoji-1F4BB { background-position: -14020px 0px; } +.emoji-1F4BC { background-position: -14040px 0px; } +.emoji-1F4BD { background-position: -14060px 0px; } +.emoji-1F4BE { background-position: -14080px 0px; } +.emoji-1F4BF { background-position: -14100px 0px; } +.emoji-1F4C0 { background-position: -14120px 0px; } +.emoji-1F4C1 { background-position: -14140px 0px; } +.emoji-1F4C2 { background-position: -14160px 0px; } +.emoji-1F4C3 { background-position: -14180px 0px; } +.emoji-1F4C4 { background-position: -14200px 0px; } +.emoji-1F4C5 { background-position: -14220px 0px; } +.emoji-1F4C6 { background-position: -14240px 0px; } +.emoji-1F4C7 { background-position: -14260px 0px; } +.emoji-1F4C8 { background-position: -14280px 0px; } +.emoji-1F4C9 { background-position: -14300px 0px; } +.emoji-1F4CA { background-position: -14320px 0px; } +.emoji-1F4CB { background-position: -14340px 0px; } +.emoji-1F4CC { background-position: -14360px 0px; } +.emoji-1F4CD { background-position: -14380px 0px; } +.emoji-1F4CE { background-position: -14400px 0px; } +.emoji-1F4CF { background-position: -14420px 0px; } +.emoji-1F4D0 { background-position: -14440px 0px; } +.emoji-1F4D1 { background-position: -14460px 0px; } +.emoji-1F4D2 { background-position: -14480px 0px; } +.emoji-1F4D3 { background-position: -14500px 0px; } +.emoji-1F4D4 { background-position: -14520px 0px; } +.emoji-1F4D5 { background-position: -14540px 0px; } +.emoji-1F4D6 { background-position: -14560px 0px; } +.emoji-1F4D7 { background-position: -14580px 0px; } +.emoji-1F4D8 { background-position: -14600px 0px; } +.emoji-1F4D9 { background-position: -14620px 0px; } +.emoji-1F4DA { background-position: -14640px 0px; } +.emoji-1F4DB { background-position: -14660px 0px; } +.emoji-1F4DC { background-position: -14680px 0px; } +.emoji-1F4DD { background-position: -14700px 0px; } +.emoji-1F4DE { background-position: -14720px 0px; } +.emoji-1F4DF { background-position: -14740px 0px; } +.emoji-1F4E0 { background-position: -14760px 0px; } +.emoji-1F4E1 { background-position: -14780px 0px; } +.emoji-1F4E2 { background-position: -14800px 0px; } +.emoji-1F4E3 { background-position: -14820px 0px; } +.emoji-1F4E4 { background-position: -14840px 0px; } +.emoji-1F4E5 { background-position: -14860px 0px; } +.emoji-1F4E6 { background-position: -14880px 0px; } +.emoji-1F4E7 { background-position: -14900px 0px; } +.emoji-1F4E8 { background-position: -14920px 0px; } +.emoji-1F4E9 { background-position: -14940px 0px; } +.emoji-1F4EA { background-position: -14960px 0px; } +.emoji-1F4EB { background-position: -14980px 0px; } +.emoji-1F4EC { background-position: -15000px 0px; } +.emoji-1F4ED { background-position: -15020px 0px; } +.emoji-1F4EE { background-position: -15040px 0px; } +.emoji-1F4EF { background-position: -15060px 0px; } +.emoji-1F4F0 { background-position: -15080px 0px; } +.emoji-1F4F1 { background-position: -15100px 0px; } +.emoji-1F4F2 { background-position: -15120px 0px; } +.emoji-1F4F3 { background-position: -15140px 0px; } +.emoji-1F4F4 { background-position: -15160px 0px; } +.emoji-1F4F5 { background-position: -15180px 0px; } +.emoji-1F4F6 { background-position: -15200px 0px; } +.emoji-1F4F7 { background-position: -15220px 0px; } +.emoji-1F4F8 { background-position: -15240px 0px; } +.emoji-1F4F9 { background-position: -15260px 0px; } +.emoji-1F4FA { background-position: -15280px 0px; } +.emoji-1F4FB { background-position: -15300px 0px; } +.emoji-1F4FC { background-position: -15320px 0px; } +.emoji-1F4FD { background-position: -15340px 0px; } +.emoji-1F4FE { background-position: -15360px 0px; } +.emoji-1F500 { background-position: -15380px 0px; } +.emoji-1F501 { background-position: -15400px 0px; } +.emoji-1F502 { background-position: -15420px 0px; } +.emoji-1F503 { background-position: -15440px 0px; } +.emoji-1F504 { background-position: -15460px 0px; } +.emoji-1F505 { background-position: -15480px 0px; } +.emoji-1F506 { background-position: -15500px 0px; } +.emoji-1F507 { background-position: -15520px 0px; } +.emoji-1F508 { background-position: -15540px 0px; } +.emoji-1F509 { background-position: -15560px 0px; } +.emoji-1F50A { background-position: -15580px 0px; } +.emoji-1F50B { background-position: -15600px 0px; } +.emoji-1F50C { background-position: -15620px 0px; } +.emoji-1F50D { background-position: -15640px 0px; } +.emoji-1F50E { background-position: -15660px 0px; } +.emoji-1F50F { background-position: -15680px 0px; } +.emoji-1F510 { background-position: -15700px 0px; } +.emoji-1F511 { background-position: -15720px 0px; } +.emoji-1F512 { background-position: -15740px 0px; } +.emoji-1F513 { background-position: -15760px 0px; } +.emoji-1F514 { background-position: -15780px 0px; } +.emoji-1F515 { background-position: -15800px 0px; } +.emoji-1F516 { background-position: -15820px 0px; } +.emoji-1F517 { background-position: -15840px 0px; } +.emoji-1F518 { background-position: -15860px 0px; } +.emoji-1F519 { background-position: -15880px 0px; } +.emoji-1F51A { background-position: -15900px 0px; } +.emoji-1F51B { background-position: -15920px 0px; } +.emoji-1F51C { background-position: -15940px 0px; } +.emoji-1F51D { background-position: -15960px 0px; } +.emoji-1F51E { background-position: -15980px 0px; } +.emoji-1F51F { background-position: -16000px 0px; } +.emoji-1F520 { background-position: -16020px 0px; } +.emoji-1F521 { background-position: -16040px 0px; } +.emoji-1F522 { background-position: -16060px 0px; } +.emoji-1F523 { background-position: -16080px 0px; } +.emoji-1F524 { background-position: -16100px 0px; } +.emoji-1F525 { background-position: -16120px 0px; } +.emoji-1F526 { background-position: -16140px 0px; } +.emoji-1F527 { background-position: -16160px 0px; } +.emoji-1F528 { background-position: -16180px 0px; } +.emoji-1F529 { background-position: -16200px 0px; } +.emoji-1F52A { background-position: -16220px 0px; } +.emoji-1F52B { background-position: -16240px 0px; } +.emoji-1F52C { background-position: -16260px 0px; } +.emoji-1F52D { background-position: -16280px 0px; } +.emoji-1F52E { background-position: -16300px 0px; } +.emoji-1F52F { background-position: -16320px 0px; } +.emoji-1F530 { background-position: -16340px 0px; } +.emoji-1F531 { background-position: -16360px 0px; } +.emoji-1F532 { background-position: -16380px 0px; } +.emoji-1F533 { background-position: -16400px 0px; } +.emoji-1F534 { background-position: -16420px 0px; } +.emoji-1F535 { background-position: -16440px 0px; } +.emoji-1F536 { background-position: -16460px 0px; } +.emoji-1F537 { background-position: -16480px 0px; } +.emoji-1F538 { background-position: -16500px 0px; } +.emoji-1F539 { background-position: -16520px 0px; } +.emoji-1F53A { background-position: -16540px 0px; } +.emoji-1F53B { background-position: -16560px 0px; } +.emoji-1F53C { background-position: -16580px 0px; } +.emoji-1F53D { background-position: -16600px 0px; } +.emoji-1F546 { background-position: -16620px 0px; } +.emoji-1F547 { background-position: -16640px 0px; } +.emoji-1F548 { background-position: -16660px 0px; } +.emoji-1F549 { background-position: -16680px 0px; } +.emoji-1F54A { background-position: -16700px 0px; } +.emoji-1F550 { background-position: -16720px 0px; } +.emoji-1F551 { background-position: -16740px 0px; } +.emoji-1F552 { background-position: -16760px 0px; } +.emoji-1F553 { background-position: -16780px 0px; } +.emoji-1F554 { background-position: -16800px 0px; } +.emoji-1F555 { background-position: -16820px 0px; } +.emoji-1F556 { background-position: -16840px 0px; } +.emoji-1F557 { background-position: -16860px 0px; } +.emoji-1F558 { background-position: -16880px 0px; } +.emoji-1F559 { background-position: -16900px 0px; } +.emoji-1F55A { background-position: -16920px 0px; } +.emoji-1F55B { background-position: -16940px 0px; } +.emoji-1F55C { background-position: -16960px 0px; } +.emoji-1F55D { background-position: -16980px 0px; } +.emoji-1F55E { background-position: -17000px 0px; } +.emoji-1F55F { background-position: -17020px 0px; } +.emoji-1F560 { background-position: -17040px 0px; } +.emoji-1F561 { background-position: -17060px 0px; } +.emoji-1F562 { background-position: -17080px 0px; } +.emoji-1F563 { background-position: -17100px 0px; } +.emoji-1F564 { background-position: -17120px 0px; } +.emoji-1F565 { background-position: -17140px 0px; } +.emoji-1F566 { background-position: -17160px 0px; } +.emoji-1F567 { background-position: -17180px 0px; } +.emoji-1F568 { background-position: -17200px 0px; } +.emoji-1F569 { background-position: -17220px 0px; } +.emoji-1F56A { background-position: -17240px 0px; } +.emoji-1F56B { background-position: -17260px 0px; } +.emoji-1F56C { background-position: -17280px 0px; } +.emoji-1F56D { background-position: -17300px 0px; } +.emoji-1F56E { background-position: -17320px 0px; } +.emoji-1F56F { background-position: -17340px 0px; } +.emoji-1F570 { background-position: -17360px 0px; } +.emoji-1F571 { background-position: -17380px 0px; } +.emoji-1F572 { background-position: -17400px 0px; } +.emoji-1F573 { background-position: -17420px 0px; } +.emoji-1F574 { background-position: -17440px 0px; } +.emoji-1F575 { background-position: -17460px 0px; } +.emoji-1F576 { background-position: -17480px 0px; } +.emoji-1F577 { background-position: -17500px 0px; } +.emoji-1F578 { background-position: -17520px 0px; } +.emoji-1F579 { background-position: -17540px 0px; } +.emoji-1F57B { background-position: -17560px 0px; } +.emoji-1F57E { background-position: -17580px 0px; } +.emoji-1F57F { background-position: -17600px 0px; } +.emoji-1F581 { background-position: -17620px 0px; } +.emoji-1F582 { background-position: -17640px 0px; } +.emoji-1F583 { background-position: -17660px 0px; } +.emoji-1F585 { background-position: -17680px 0px; } +.emoji-1F586 { background-position: -17700px 0px; } +.emoji-1F587 { background-position: -17720px 0px; } +.emoji-1F588 { background-position: -17740px 0px; } +.emoji-1F589 { background-position: -17760px 0px; } +.emoji-1F58A { background-position: -17780px 0px; } +.emoji-1F58B { background-position: -17800px 0px; } +.emoji-1F58C { background-position: -17820px 0px; } +.emoji-1F58D { background-position: -17840px 0px; } +.emoji-1F58E { background-position: -17860px 0px; } +.emoji-1F58F { background-position: -17880px 0px; } +.emoji-1F590 { background-position: -17900px 0px; } +.emoji-1F591 { background-position: -17920px 0px; } +.emoji-1F592 { background-position: -17940px 0px; } +.emoji-1F593 { background-position: -17960px 0px; } +.emoji-1F594 { background-position: -17980px 0px; } +.emoji-1F595 { background-position: -18000px 0px; } +.emoji-1F596 { background-position: -18020px 0px; } +.emoji-1F597 { background-position: -18040px 0px; } +.emoji-1F598 { background-position: -18060px 0px; } +.emoji-1F599 { background-position: -18080px 0px; } +.emoji-1F59E { background-position: -18100px 0px; } +.emoji-1F59F { background-position: -18120px 0px; } +.emoji-1F5A5 { background-position: -18140px 0px; } +.emoji-1F5A6 { background-position: -18160px 0px; } +.emoji-1F5A7 { background-position: -18180px 0px; } +.emoji-1F5A8 { background-position: -18200px 0px; } +.emoji-1F5A9 { background-position: -18220px 0px; } +.emoji-1F5AA { background-position: -18240px 0px; } +.emoji-1F5AB { background-position: -18260px 0px; } +.emoji-1F5AD { background-position: -18280px 0px; } +.emoji-1F5AE { background-position: -18300px 0px; } +.emoji-1F5AF { background-position: -18320px 0px; } +.emoji-1F5B2 { background-position: -18340px 0px; } +.emoji-1F5B3 { background-position: -18360px 0px; } +.emoji-1F5B4 { background-position: -18380px 0px; } +.emoji-1F5B8 { background-position: -18400px 0px; } +.emoji-1F5B9 { background-position: -18420px 0px; } +.emoji-1F5BC { background-position: -18440px 0px; } +.emoji-1F5BD { background-position: -18460px 0px; } +.emoji-1F5BE { background-position: -18480px 0px; } +.emoji-1F5C0 { background-position: -18500px 0px; } +.emoji-1F5C1 { background-position: -18520px 0px; } +.emoji-1F5C2 { background-position: -18540px 0px; } +.emoji-1F5C3 { background-position: -18560px 0px; } +.emoji-1F5C4 { background-position: -18580px 0px; } +.emoji-1F5C6 { background-position: -18600px 0px; } +.emoji-1F5C7 { background-position: -18620px 0px; } +.emoji-1F5C9 { background-position: -18640px 0px; } +.emoji-1F5CA { background-position: -18660px 0px; } +.emoji-1F5CE { background-position: -18680px 0px; } +.emoji-1F5CF { background-position: -18700px 0px; } +.emoji-1F5D0 { background-position: -18720px 0px; } +.emoji-1F5D1 { background-position: -18740px 0px; } +.emoji-1F5D2 { background-position: -18760px 0px; } +.emoji-1F5D3 { background-position: -18780px 0px; } +.emoji-1F5D4 { background-position: -18800px 0px; } +.emoji-1F5D8 { background-position: -18820px 0px; } +.emoji-1F5D9 { background-position: -18840px 0px; } +.emoji-1F5DC { background-position: -18860px 0px; } +.emoji-1F5DD { background-position: -18880px 0px; } +.emoji-1F5DE { background-position: -18900px 0px; } +.emoji-1F5E0 { background-position: -18920px 0px; } +.emoji-1F5E1 { background-position: -18940px 0px; } +.emoji-1F5E2 { background-position: -18960px 0px; } +.emoji-1F5E3 { background-position: -18980px 0px; } +.emoji-1F5E8 { background-position: -19000px 0px; } +.emoji-1F5E9 { background-position: -19020px 0px; } +.emoji-1F5EA { background-position: -19040px 0px; } +.emoji-1F5EB { background-position: -19060px 0px; } +.emoji-1F5EC { background-position: -19080px 0px; } +.emoji-1F5ED { background-position: -19100px 0px; } +.emoji-1F5EE { background-position: -19120px 0px; } +.emoji-1F5EF { background-position: -19140px 0px; } +.emoji-1F5F0 { background-position: -19160px 0px; } +.emoji-1F5F1 { background-position: -19180px 0px; } +.emoji-1F5F2 { background-position: -19200px 0px; } +.emoji-1F5F3 { background-position: -19220px 0px; } +.emoji-1F5F4 { background-position: -19240px 0px; } +.emoji-1F5F5 { background-position: -19260px 0px; } +.emoji-1F5F8 { background-position: -19280px 0px; } +.emoji-1F5F9 { background-position: -19300px 0px; } +.emoji-1F5FA { background-position: -19320px 0px; } +.emoji-1F5FB { background-position: -19340px 0px; } +.emoji-1F5FC { background-position: -19360px 0px; } +.emoji-1F5FD { background-position: -19380px 0px; } +.emoji-1F5FE { background-position: -19400px 0px; } +.emoji-1F5FF { background-position: -19420px 0px; } +.emoji-1F600 { background-position: -19440px 0px; } +.emoji-1F601 { background-position: -19460px 0px; } +.emoji-1F602 { background-position: -19480px 0px; } +.emoji-1F603 { background-position: -19500px 0px; } +.emoji-1F604 { background-position: -19520px 0px; } +.emoji-1F605 { background-position: -19540px 0px; } +.emoji-1F606 { background-position: -19560px 0px; } +.emoji-1F607 { background-position: -19580px 0px; } +.emoji-1F608 { background-position: -19600px 0px; } +.emoji-1F609 { background-position: -19620px 0px; } +.emoji-1F60A { background-position: -19640px 0px; } +.emoji-1F60B { background-position: -19660px 0px; } +.emoji-1F60C { background-position: -19680px 0px; } +.emoji-1F60D { background-position: -19700px 0px; } +.emoji-1F60E { background-position: -19720px 0px; } +.emoji-1F60F { background-position: -19740px 0px; } +.emoji-1F610 { background-position: -19760px 0px; } +.emoji-1F611 { background-position: -19780px 0px; } +.emoji-1F612 { background-position: -19800px 0px; } +.emoji-1F613 { background-position: -19820px 0px; } +.emoji-1F614 { background-position: -19840px 0px; } +.emoji-1F615 { background-position: -19860px 0px; } +.emoji-1F616 { background-position: -19880px 0px; } +.emoji-1F617 { background-position: -19900px 0px; } +.emoji-1F618 { background-position: -19920px 0px; } +.emoji-1F619 { background-position: -19940px 0px; } +.emoji-1F61A { background-position: -19960px 0px; } +.emoji-1F61B { background-position: -19980px 0px; } +.emoji-1F61C { background-position: -20000px 0px; } +.emoji-1F61D { background-position: -20020px 0px; } +.emoji-1F61E { background-position: -20040px 0px; } +.emoji-1F61F { background-position: -20060px 0px; } +.emoji-1F620 { background-position: -20080px 0px; } +.emoji-1F621 { background-position: -20100px 0px; } +.emoji-1F622 { background-position: -20120px 0px; } +.emoji-1F623 { background-position: -20140px 0px; } +.emoji-1F624 { background-position: -20160px 0px; } +.emoji-1F625 { background-position: -20180px 0px; } +.emoji-1F626 { background-position: -20200px 0px; } +.emoji-1F627 { background-position: -20220px 0px; } +.emoji-1F628 { background-position: -20240px 0px; } +.emoji-1F629 { background-position: -20260px 0px; } +.emoji-1F62A { background-position: -20280px 0px; } +.emoji-1F62B { background-position: -20300px 0px; } +.emoji-1F62C { background-position: -20320px 0px; } +.emoji-1F62D { background-position: -20340px 0px; } +.emoji-1F62E { background-position: -20360px 0px; } +.emoji-1F62F { background-position: -20380px 0px; } +.emoji-1F630 { background-position: -20400px 0px; } +.emoji-1F631 { background-position: -20420px 0px; } +.emoji-1F632 { background-position: -20440px 0px; } +.emoji-1F633 { background-position: -20460px 0px; } +.emoji-1F634 { background-position: -20480px 0px; } +.emoji-1F635 { background-position: -20500px 0px; } +.emoji-1F636 { background-position: -20520px 0px; } +.emoji-1F637 { background-position: -20540px 0px; } +.emoji-1F638 { background-position: -20560px 0px; } +.emoji-1F639 { background-position: -20580px 0px; } +.emoji-1F63A { background-position: -20600px 0px; } +.emoji-1F63B { background-position: -20620px 0px; } +.emoji-1F63C { background-position: -20640px 0px; } +.emoji-1F63D { background-position: -20660px 0px; } +.emoji-1F63E { background-position: -20680px 0px; } +.emoji-1F63F { background-position: -20700px 0px; } +.emoji-1F640 { background-position: -20720px 0px; } +.emoji-1F641 { background-position: -20740px 0px; } +.emoji-1F642 { background-position: -20760px 0px; } +.emoji-1F645 { background-position: -20780px 0px; } +.emoji-1F646 { background-position: -20800px 0px; } +.emoji-1F647 { background-position: -20820px 0px; } +.emoji-1F648 { background-position: -20840px 0px; } +.emoji-1F649 { background-position: -20860px 0px; } +.emoji-1F64A { background-position: -20880px 0px; } +.emoji-1F64B { background-position: -20900px 0px; } +.emoji-1F64C { background-position: -20920px 0px; } +.emoji-1F64D { background-position: -20940px 0px; } +.emoji-1F64E { background-position: -20960px 0px; } +.emoji-1F64F { background-position: -20980px 0px; } +.emoji-1F680 { background-position: -21000px 0px; } +.emoji-1F681 { background-position: -21020px 0px; } +.emoji-1F682 { background-position: -21040px 0px; } +.emoji-1F683 { background-position: -21060px 0px; } +.emoji-1F684 { background-position: -21080px 0px; } +.emoji-1F685 { background-position: -21100px 0px; } +.emoji-1F686 { background-position: -21120px 0px; } +.emoji-1F687 { background-position: -21140px 0px; } +.emoji-1F688 { background-position: -21160px 0px; } +.emoji-1F689 { background-position: -21180px 0px; } +.emoji-1F68A { background-position: -21200px 0px; } +.emoji-1F68B { background-position: -21220px 0px; } +.emoji-1F68C { background-position: -21240px 0px; } +.emoji-1F68D { background-position: -21260px 0px; } +.emoji-1F68E { background-position: -21280px 0px; } +.emoji-1F68F { background-position: -21300px 0px; } +.emoji-1F690 { background-position: -21320px 0px; } +.emoji-1F691 { background-position: -21340px 0px; } +.emoji-1F692 { background-position: -21360px 0px; } +.emoji-1F693 { background-position: -21380px 0px; } +.emoji-1F694 { background-position: -21400px 0px; } +.emoji-1F695 { background-position: -21420px 0px; } +.emoji-1F696 { background-position: -21440px 0px; } +.emoji-1F697 { background-position: -21460px 0px; } +.emoji-1F698 { background-position: -21480px 0px; } +.emoji-1F699 { background-position: -21500px 0px; } +.emoji-1F69A { background-position: -21520px 0px; } +.emoji-1F69B { background-position: -21540px 0px; } +.emoji-1F69C { background-position: -21560px 0px; } +.emoji-1F69D { background-position: -21580px 0px; } +.emoji-1F69E { background-position: -21600px 0px; } +.emoji-1F69F { background-position: -21620px 0px; } +.emoji-1F6A0 { background-position: -21640px 0px; } +.emoji-1F6A1 { background-position: -21660px 0px; } +.emoji-1F6A2 { background-position: -21680px 0px; } +.emoji-1F6A3 { background-position: -21700px 0px; } +.emoji-1F6A4 { background-position: -21720px 0px; } +.emoji-1F6A5 { background-position: -21740px 0px; } +.emoji-1F6A6 { background-position: -21760px 0px; } +.emoji-1F6A7 { background-position: -21780px 0px; } +.emoji-1F6A8 { background-position: -21800px 0px; } +.emoji-1F6A9 { background-position: -21820px 0px; } +.emoji-1F6AA { background-position: -21840px 0px; } +.emoji-1F6AB { background-position: -21860px 0px; } +.emoji-1F6AC { background-position: -21880px 0px; } +.emoji-1F6AD { background-position: -21900px 0px; } +.emoji-1F6AE { background-position: -21920px 0px; } +.emoji-1F6AF { background-position: -21940px 0px; } +.emoji-1F6B0 { background-position: -21960px 0px; } +.emoji-1F6B1 { background-position: -21980px 0px; } +.emoji-1F6B2 { background-position: -22000px 0px; } +.emoji-1F6B3 { background-position: -22020px 0px; } +.emoji-1F6B4 { background-position: -22040px 0px; } +.emoji-1F6B5 { background-position: -22060px 0px; } +.emoji-1F6B6 { background-position: -22080px 0px; } +.emoji-1F6B7 { background-position: -22100px 0px; } +.emoji-1F6B8 { background-position: -22120px 0px; } +.emoji-1F6B9 { background-position: -22140px 0px; } +.emoji-1F6BA { background-position: -22160px 0px; } +.emoji-1F6BB { background-position: -22180px 0px; } +.emoji-1F6BC { background-position: -22200px 0px; } +.emoji-1F6BD { background-position: -22220px 0px; } +.emoji-1F6BE { background-position: -22240px 0px; } +.emoji-1F6BF { background-position: -22260px 0px; } +.emoji-1F6C0 { background-position: -22280px 0px; } +.emoji-1F6C1 { background-position: -22300px 0px; } +.emoji-1F6C2 { background-position: -22320px 0px; } +.emoji-1F6C3 { background-position: -22340px 0px; } +.emoji-1F6C4 { background-position: -22360px 0px; } +.emoji-1F6C5 { background-position: -22380px 0px; } +.emoji-1F6C6 { background-position: -22400px 0px; } +.emoji-1F6C7 { background-position: -22420px 0px; } +.emoji-1F6C8 { background-position: -22440px 0px; } +.emoji-1F6C9 { background-position: -22460px 0px; } +.emoji-1F6CA { background-position: -22480px 0px; } +.emoji-1F6CB { background-position: -22500px 0px; } +.emoji-1F6CC { background-position: -22520px 0px; } +.emoji-1F6CD { background-position: -22540px 0px; } +.emoji-1F6CE { background-position: -22560px 0px; } +.emoji-1F6CF { background-position: -22580px 0px; } +.emoji-1F6E0 { background-position: -22600px 0px; } +.emoji-1F6E1 { background-position: -22620px 0px; } +.emoji-1F6E2 { background-position: -22640px 0px; } +.emoji-1F6E3 { background-position: -22660px 0px; } +.emoji-1F6E4 { background-position: -22680px 0px; } +.emoji-1F6E5 { background-position: -22700px 0px; } +.emoji-1F6E6 { background-position: -22720px 0px; } +.emoji-1F6E7 { background-position: -22740px 0px; } +.emoji-1F6E8 { background-position: -22760px 0px; } +.emoji-1F6E9 { background-position: -22780px 0px; } +.emoji-1F6EA { background-position: -22800px 0px; } +.emoji-1F6EB { background-position: -22820px 0px; } +.emoji-1F6EC { background-position: -22840px 0px; } +.emoji-1F6F0 { background-position: -22860px 0px; } +.emoji-1F6F1 { background-position: -22880px 0px; } +.emoji-1F6F2 { background-position: -22900px 0px; } +.emoji-1F6F3 { background-position: -22920px 0px; } +.emoji-203C { background-position: -22940px 0px; } +.emoji-2049 { background-position: -22960px 0px; } +.emoji-2122 { background-position: -22980px 0px; } +.emoji-2139 { background-position: -23000px 0px; } +.emoji-2194 { background-position: -23020px 0px; } +.emoji-2195 { background-position: -23040px 0px; } +.emoji-2196 { background-position: -23060px 0px; } +.emoji-2197 { background-position: -23080px 0px; } +.emoji-2198 { background-position: -23100px 0px; } +.emoji-2199 { background-position: -23120px 0px; } +.emoji-21A9 { background-position: -23140px 0px; } +.emoji-21AA { background-position: -23160px 0px; } +.emoji-231A { background-position: -23180px 0px; } +.emoji-231B { background-position: -23200px 0px; } +.emoji-23E9 { background-position: -23220px 0px; } +.emoji-23EA { background-position: -23240px 0px; } +.emoji-23EB { background-position: -23260px 0px; } +.emoji-23EC { background-position: -23280px 0px; } +.emoji-23F0 { background-position: -23300px 0px; } +.emoji-23F3 { background-position: -23320px 0px; } +.emoji-24C2 { background-position: -23340px 0px; } +.emoji-25AA { background-position: -23360px 0px; } +.emoji-25AB { background-position: -23380px 0px; } +.emoji-25B6 { background-position: -23400px 0px; } +.emoji-25C0 { background-position: -23420px 0px; } +.emoji-25FB { background-position: -23440px 0px; } +.emoji-25FC { background-position: -23460px 0px; } +.emoji-25FD { background-position: -23480px 0px; } +.emoji-25FE { background-position: -23500px 0px; } +.emoji-2600 { background-position: -23520px 0px; } +.emoji-2601 { background-position: -23540px 0px; } +.emoji-260E { background-position: -23560px 0px; } +.emoji-2611 { background-position: -23580px 0px; } +.emoji-2614 { background-position: -23600px 0px; } +.emoji-2615 { background-position: -23620px 0px; } +.emoji-261D { background-position: -23640px 0px; } +.emoji-263A { background-position: -23660px 0px; } +.emoji-2648 { background-position: -23680px 0px; } +.emoji-2649 { background-position: -23700px 0px; } +.emoji-264A { background-position: -23720px 0px; } +.emoji-264B { background-position: -23740px 0px; } +.emoji-264C { background-position: -23760px 0px; } +.emoji-264D { background-position: -23780px 0px; } +.emoji-264E { background-position: -23800px 0px; } +.emoji-264F { background-position: -23820px 0px; } +.emoji-2650 { background-position: -23840px 0px; } +.emoji-2651 { background-position: -23860px 0px; } +.emoji-2652 { background-position: -23880px 0px; } +.emoji-2653 { background-position: -23900px 0px; } +.emoji-2660 { background-position: -23920px 0px; } +.emoji-2663 { background-position: -23940px 0px; } +.emoji-2665 { background-position: -23960px 0px; } +.emoji-2666 { background-position: -23980px 0px; } +.emoji-2668 { background-position: -24000px 0px; } +.emoji-267B { background-position: -24020px 0px; } +.emoji-267F { background-position: -24040px 0px; } +.emoji-2693 { background-position: -24060px 0px; } +.emoji-26A0 { background-position: -24080px 0px; } +.emoji-26A1 { background-position: -24100px 0px; } +.emoji-26AA { background-position: -24120px 0px; } +.emoji-26AB { background-position: -24140px 0px; } +.emoji-26BD { background-position: -24160px 0px; } +.emoji-26BE { background-position: -24180px 0px; } +.emoji-26C4 { background-position: -24200px 0px; } +.emoji-26C5 { background-position: -24220px 0px; } +.emoji-26CE { background-position: -24240px 0px; } +.emoji-26D4 { background-position: -24260px 0px; } +.emoji-26EA { background-position: -24280px 0px; } +.emoji-26F2 { background-position: -24300px 0px; } +.emoji-26F3 { background-position: -24320px 0px; } +.emoji-26F5 { background-position: -24340px 0px; } +.emoji-26FA { background-position: -24360px 0px; } +.emoji-26FD { background-position: -24380px 0px; } +.emoji-2702 { background-position: -24400px 0px; } +.emoji-2705 { background-position: -24420px 0px; } +.emoji-2708 { background-position: -24440px 0px; } +.emoji-2709 { background-position: -24460px 0px; } +.emoji-270A { background-position: -24480px 0px; } +.emoji-270B { background-position: -24500px 0px; } +.emoji-270C { background-position: -24520px 0px; } +.emoji-270F { background-position: -24540px 0px; } +.emoji-2712 { background-position: -24560px 0px; } +.emoji-2714 { background-position: -24580px 0px; } +.emoji-2716 { background-position: -24600px 0px; } +.emoji-2728 { background-position: -24620px 0px; } +.emoji-2733 { background-position: -24640px 0px; } +.emoji-2734 { background-position: -24660px 0px; } +.emoji-2744 { background-position: -24680px 0px; } +.emoji-2747 { background-position: -24700px 0px; } +.emoji-274C { background-position: -24720px 0px; } +.emoji-274E { background-position: -24740px 0px; } +.emoji-2753 { background-position: -24760px 0px; } +.emoji-2754 { background-position: -24780px 0px; } +.emoji-2755 { background-position: -24800px 0px; } +.emoji-2757 { background-position: -24820px 0px; } +.emoji-2764 { background-position: -24840px 0px; } +.emoji-2795 { background-position: -24860px 0px; } +.emoji-2796 { background-position: -24880px 0px; } +.emoji-2797 { background-position: -24900px 0px; } +.emoji-27A1 { background-position: -24920px 0px; } +.emoji-27B0 { background-position: -24940px 0px; } +.emoji-27BF { background-position: -24960px 0px; } +.emoji-2934 { background-position: -24980px 0px; } +.emoji-2935 { background-position: -25000px 0px; } +.emoji-2B05 { background-position: -25020px 0px; } +.emoji-2B06 { background-position: -25040px 0px; } +.emoji-2B07 { background-position: -25060px 0px; } +.emoji-2B1B { background-position: -25080px 0px; } +.emoji-2B1C { background-position: -25100px 0px; } +.emoji-2B50 { background-position: -25120px 0px; } +.emoji-2B55 { background-position: -25140px 0px; } +.emoji-3030 { background-position: -25160px 0px; } +.emoji-303D { background-position: -25180px 0px; } +.emoji-3297 { background-position: -25200px 0px; } +.emoji-3299 { background-position: -25220px 0px; }
\ No newline at end of file diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 4cf1a28c459..d86259f93fb 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -75,16 +75,15 @@ .common-note-form { margin: 0; - background: #F7F8FA; + background: #fff; padding: $gl-padding; margin-left: -$gl-padding; margin-right: -$gl-padding; - border-top: 1px solid $border-color; margin-bottom: -$gl-padding; } .note-form-actions { - background: #F9F9F9; + background: #fff; .note-form-option { margin-top: 8px; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 4dff87abaa4..72b0ed29a69 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -128,7 +128,7 @@ ul.notes { } &:last-child { - border-bottom: none; + border-bottom: 1px solid $border-color; } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 2ded32dba12..cff3edb7ed2 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -91,21 +91,83 @@ } } - .input-group { + .git-clone-holder { display: inline-table; position: relative; - top: 17px; } .project-repo-buttons { margin-top: 12px; margin-bottom: 0px; + .count-buttons { + display: block; + margin-bottom: 12px; + } + .btn { @include btn-gray; - + text-transform: none; + } + .count-with-arrow { + display: inline-block; + position: relative; + margin-left: 4px; + + .arrow { + &:before { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 50%; + left: 0; + margin-top: -6px; + border-width: 7px 5px 7px 0; + border-right-color: #dce0e5; + } + + &:after { + content: ''; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 50%; + left: 1px; + margin-top: -9px; + border-width: 10px 7px 10px 0; + border-right-color: #FFF; + } + } .count { + @include btn-gray; display: inline-block; + background: white; + border-radius: 2px; + border-width: 1px; + border-style: solid; + font-size: 13px; + font-weight: 600; + line-height: 20px; + padding: 11px 16px; + letter-spacing: .4px; + padding: 10px; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; + white-space: nowrap; + margin: 0 11px 0px 4px; + + &:hover { + background: #FFF; + } } } } @@ -125,6 +187,13 @@ margin-right: 45px; } + .clone-options { + display: table-cell; + a.btn { + width: 100%; + } + } + .form-control { cursor: auto; @extend .monospace; @@ -335,6 +404,38 @@ ul.nav.nav-projects-tabs { } } +.top-area { + border-bottom: 1px solid #EEE; + margin: 0 -16px; + padding: 0 $gl-padding; + + ul.left-top-menu { + display: inline-block; + width: 50%; + margin-bottom: 0px; + border-bottom: none; + } + + .projects-search-form { + width: 50%; + display: inline-block; + float: right; + padding-top: 7px; + text-align: right; + + .btn-green { + margin-top: -2px; + margin-left: 10px; + } + } + + @media (max-width: $screen-xs-max) { + .projects-search-form { + padding-top: 15px; + } + } +} + .fork-namespaces { .fork-thumbnail { text-align: center; @@ -412,11 +513,18 @@ pre.light-well { .projects-search-form { margin: -$gl-padding; - background-color: #f8fafc; padding: $gl-padding; margin-bottom: 0px; - border-top: 1px solid #e7e9ed; - border-bottom: 1px solid #e7e9ed; + + input { + display: inline-block; + width: calc(100% - 151px); + } + + .btn { + display: inline-block; + width: 135px; + } } .git-empty { diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb index d28614731f9..e383fe38ea6 100644 --- a/app/controllers/admin/identities_controller.rb +++ b/app/controllers/admin/identities_controller.rb @@ -1,6 +1,21 @@ class Admin::IdentitiesController < Admin::ApplicationController before_action :user - before_action :identity, except: :index + before_action :identity, except: [:index, :new, :create] + + def new + @identity = Identity.new + end + + def create + @identity = Identity.new(identity_params) + @identity.user_id = user.id + + if @identity.save + redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully created.' + else + render :new + end + end def index @identities = @user.identities diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0d182e8eb04..01e2e7b2f98 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base before_action :authenticate_user_from_token! before_action :authenticate_user! + before_action :validate_user_service_ticket! before_action :reject_blocked! before_action :check_password_expiration before_action :ldap_security_check @@ -202,6 +203,20 @@ class ApplicationController < ActionController::Base end end + def validate_user_service_ticket! + return unless signed_in? && session[:service_tickets] + + valid = session[:service_tickets].all? do |provider, ticket| + Gitlab::OAuth::Session.valid?(provider, ticket) + end + + unless valid + session[:service_tickets] = nil + sign_out current_user + redirect_to new_user_session_path + end + end + def check_password_expiration if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? redirect_to new_profile_password_path and return diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index 7ed78ff8e98..e782a51e7eb 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -19,8 +19,10 @@ module Ci @error = e.message @status = false rescue - @error = "Undefined error" + @error = 'Undefined error' @status = false + ensure + render :show end end end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb new file mode 100644 index 00000000000..62127a09081 --- /dev/null +++ b/app/controllers/concerns/creates_commit.rb @@ -0,0 +1,103 @@ +module CreatesCommit + extend ActiveSupport::Concern + + def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) + set_commit_variables + + commit_params = @commit_params.merge( + source_project: @project, + source_branch: @ref, + target_branch: @target_branch + ) + + result = service.new(@tree_edit_project, current_user, commit_params).execute + + if result[:status] == :success + flash[:notice] = success_notice || "Your changes have been successfully committed." + + if create_merge_request? + success_path = new_merge_request_path + target = different_project? ? "project" : "branch" + flash[:notice] << " You can now submit a merge request to get this change into the original #{target}." + end + + respond_to do |format| + format.html { redirect_to success_path } + format.json { render json: { message: "success", filePath: success_path } } + end + else + flash[:alert] = result[:message] + respond_to do |format| + format.html do + if failure_view + render failure_view + else + redirect_to failure_path + end + end + format.json { render json: { message: "failed", filePath: failure_path } } + end + end + end + + def authorize_edit_tree! + return if can?(current_user, :push_code, project) + return if current_user && current_user.already_forked?(project) + + access_denied! + end + + private + + def new_merge_request_path + new_namespace_project_merge_request_path( + @mr_source_project.namespace, + @mr_source_project, + merge_request: { + source_project_id: @mr_source_project.id, + target_project_id: @mr_target_project.id, + source_branch: @mr_source_branch, + target_branch: @mr_target_branch + } + ) + end + + def different_project? + @mr_source_project != @mr_target_project + end + + def different_branch? + @mr_source_branch != @mr_target_branch || different_project? + end + + def create_merge_request? + params[:create_merge_request].present? && different_branch? + end + + def set_commit_variables + @mr_source_branch = @target_branch + + if can?(current_user, :push_code, @project) + # Edit file in this project + @tree_edit_project = @project + @mr_source_project = @project + + if @project.forked? + # Merge request from this project to fork origin + @mr_target_project = @project.forked_from_project + @mr_target_branch = @mr_target_project.repository.root_ref + else + # Merge request to this project + @mr_target_project = @project + @mr_target_branch = @ref + end + else + # Edit file in fork + @tree_edit_project = current_user.fork_of(@project) + # Merge request from fork to this project + @mr_source_project = @tree_edit_project + @mr_target_project = @project + @mr_target_branch = @mr_target_project.repository.root_ref + end + end +end diff --git a/app/controllers/concerns/creates_merge_request_for_commit.rb b/app/controllers/concerns/creates_merge_request_for_commit.rb deleted file mode 100644 index c7527822158..00000000000 --- a/app/controllers/concerns/creates_merge_request_for_commit.rb +++ /dev/null @@ -1,28 +0,0 @@ -module CreatesMergeRequestForCommit - extend ActiveSupport::Concern - - def new_merge_request_path - if @project.forked? - target_project = @project.forked_from_project || @project - target_branch = target_project.repository.root_ref - else - target_project = @project - target_branch = @ref - end - - new_namespace_project_merge_request_path( - @project.namespace, - @project, - merge_request: { - source_project_id: @project.id, - target_project_id: target_project.id, - source_branch: @new_branch, - target_branch: target_branch - } - ) - end - - def create_merge_request? - params[:create_merge_request] && @new_branch != @ref - end -end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index f809fa7500a..4cad98b8e98 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,6 +1,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController - protect_from_forgery except: [:kerberos, :saml] + protect_from_forgery except: [:kerberos, :saml, :cas3] Gitlab.config.omniauth.providers.each do |provider| define_method provider['name'] do @@ -42,6 +42,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController render 'errors/omniauth_error', layout: "errors", status: 422 end + def cas3 + ticket = params['ticket'] + if ticket + handle_service_ticket oauth['provider'], ticket + end + handle_omniauth + end + private def handle_omniauth @@ -84,6 +92,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController redirect_to new_user_session_path end + def handle_service_ticket provider, ticket + Gitlab::OAuth::Session.create provider, ticket + session[:service_tickets] ||= {} + session[:service_tickets][provider] = ticket + end + def oauth @oauth ||= request.env['omniauth.auth'] end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 62163682936..c56a3497bb2 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -1,7 +1,7 @@ # Controller for viewing a file's blame class Projects::BlobController < Projects::ApplicationController include ExtractsPath - include CreatesMergeRequestForCommit + include CreatesCommit include ActionView::Helpers::SanitizeHelper # Raised when given an invalid file path @@ -9,21 +9,21 @@ class Projects::BlobController < Projects::ApplicationController before_action :require_non_empty_project, except: [:new, :create] before_action :authorize_download_code! - before_action :authorize_push_code!, only: [:destroy, :create] + before_action :authorize_edit_tree!, only: [:new, :create, :edit, :update, :destroy] before_action :assign_blob_vars before_action :commit, except: [:new, :create] before_action :blob, except: [:new, :create] before_action :from_merge_request, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] - before_action :after_edit_path, only: [:edit, :update] def new commit unless @repository.empty? end def create - create_commit(Files::CreateService, success_path: after_create_path, + create_commit(Files::CreateService, success_notice: "The file has been successfully created.", + success_path: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)), failure_view: :new, failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) end @@ -36,6 +36,14 @@ class Projects::BlobController < Projects::ApplicationController end def update + after_edit_path = + if from_merge_request && @target_branch == @ref + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + + "#file-path-#{hexdigest(@path)}" + else + namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) + end + create_commit(Files::UpdateService, success_path: after_edit_path, failure_view: :edit, failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) @@ -50,15 +58,10 @@ class Projects::BlobController < Projects::ApplicationController end def destroy - result = Files::DeleteService.new(@project, current_user, @commit_params).execute - - if result[:status] == :success - flash[:notice] = "Your changes have been successfully committed" - redirect_to after_destroy_path - else - flash[:alert] = result[:message] - render :show - end + create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.", + success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch), + failure_view: :show, + failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) end def diff @@ -108,74 +111,13 @@ class Projects::BlobController < Projects::ApplicationController render_404 end - def create_commit(service, success_path:, failure_view:, failure_path:) - result = service.new(@project, current_user, @commit_params).execute - - if result[:status] == :success - flash[:notice] = "Your changes have been successfully committed" - respond_to do |format| - format.html { redirect_to success_path } - format.json { render json: { message: "success", filePath: success_path } } - end - else - flash[:alert] = result[:message] - respond_to do |format| - format.html { render failure_view } - format.json { render json: { message: "failed", filePath: failure_path } } - end - end - end - - def after_create_path - @after_create_path ||= - if create_merge_request? - new_merge_request_path - else - namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path)) - end - end - - def after_edit_path - @after_edit_path ||= - if create_merge_request? - new_merge_request_path - elsif from_merge_request && @new_branch == @ref - diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + - "#file-path-#{hexdigest(@path)}" - else - namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path)) - end - end - - def after_destroy_path - @after_destroy_path ||= - if create_merge_request? - new_merge_request_path - else - namespace_project_tree_path(@project.namespace, @project, @new_branch) - end - end - def from_merge_request # If blob edit was initiated from merge request page @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) end - def sanitized_new_branch_name - sanitize(strip_tags(params[:new_branch])) - end - def editor_variables - @current_branch = @ref - - @new_branch = - if params[:new_branch].present? - sanitized_new_branch_name - elsif ::Gitlab::GitAccess.new(current_user, @project).can_push_to_branch?(@ref) - @ref - else - @repository.next_patch_branch - end + @target_branch = params[:target_branch] @file_path = if action_name.to_s == 'create' @@ -194,8 +136,6 @@ class Projects::BlobController < Projects::ApplicationController @commit_params = { file_path: @file_path, - current_branch: @current_branch, - target_branch: @new_branch, commit_message: params[:commit_message], file_content: params[:content], file_content_encoding: params[:encoding] diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 8a785076bb7..750181f0c19 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -10,19 +10,35 @@ class Projects::ForksController < Projects::ApplicationController def create namespace = Namespace.find(params[:namespace_key]) - @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute + + @forked_project = namespace.projects.find_by(path: project.path) + @forked_project = nil unless @forked_project && @forked_project.forked_from_project == project + + @forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute if @forked_project.saved? && @forked_project.forked? if @forked_project.import_in_progress? - redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project) + redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params) else - redirect_to( - namespace_project_path(@forked_project.namespace, @forked_project), - notice: 'Project was successfully forked.' - ) + if continue_params + redirect_to continue_params[:to], notice: continue_params[:notice] + else + redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project was successfully forked." + end end else render :error end end + + private + + def continue_params + continue_params = params[:continue] + if continue_params + continue_params.permit(:to, :notice, :notice_now) + else + nil + end + end end diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index fb8788f0818..8d8035ef5ff 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -1,7 +1,7 @@ class Projects::ImportsController < Projects::ApplicationController # Authorize before_action :authorize_admin_project! - before_action :require_no_repo + before_action :require_no_repo, except: :show before_action :redirect_if_progress, except: :show def new @@ -24,21 +24,36 @@ class Projects::ImportsController < Projects::ApplicationController end def show - unless @project.import_in_progress? - if @project.import_finished? - redirect_to(project_path(@project)) and return + if @project.repository_exists? || @project.import_finished? + if continue_params + redirect_to continue_params[:to], notice: continue_params[:notice] else - redirect_to(new_namespace_project_import_path(@project.namespace, - @project)) and return + redirect_to project_path(@project), notice: "The project was successfully forked." end + elsif @project.import_failed? + redirect_to new_namespace_project_import_path(@project.namespace, @project) + else + if continue_params && continue_params[:notice_now] + flash.now[:notice] = continue_params[:notice_now] + end + # Render end end private + def continue_params + continue_params = params[:continue] + if continue_params + continue_params.permit(:to, :notice, :notice_now) + else + nil + end + end + def require_no_repo if @project.repository_exists? && !@project.import_in_progress? - redirect_to(namespace_project_path(@project.namespace, @project)) and return + redirect_to(namespace_project_path(@project.namespace, @project)) end end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index ee705f32e81..6f1e186d408 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -139,7 +139,6 @@ class Projects::NotesController < Projects::ApplicationController discussion_id: note.discussion_id, html: note_to_html(note), award: note.is_award, - emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "", note: note.note, discussion_html: note_to_discussion_html(note), discussion_with_diff_html: note_to_discussion_with_diff_html(note) diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 6e7590260ff..8b2577aebe1 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -1,5 +1,5 @@ class Projects::ServicesController < Projects::ApplicationController - ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain, + ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain, :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :drone_url, :build_type, @@ -10,7 +10,8 @@ class Projects::ServicesController < Projects::ApplicationController :notify_only_broken_builds, :add_pusher, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, - :server_host, :server_port, :default_irc_uri, :enable_ssl_verification] + :server_host, :server_port, :default_irc_uri, :enable_ssl_verification, + :jira_issue_transition_id] # Parameters to ignore if no value is specified FILTER_BLANK_PARAMS = [:password] diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 8f272ad1281..cb3ed0f6f9c 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -1,14 +1,14 @@ # Controller for viewing a repository's file structure class Projects::TreeController < Projects::ApplicationController include ExtractsPath - include CreatesMergeRequestForCommit + include CreatesCommit include ActionView::Helpers::SanitizeHelper before_action :require_non_empty_project, except: [:new, :create] before_action :assign_ref_vars before_action :assign_dir_vars, only: [:create_dir] before_action :authorize_download_code! - before_action :authorize_push_code!, only: [:create_dir] + before_action :authorize_edit_tree!, only: [:create_dir] def show return render_404 unless @repository.commit(@ref) @@ -34,44 +34,20 @@ class Projects::TreeController < Projects::ApplicationController def create_dir return render_404 unless @commit_params.values.all? - begin - result = Files::CreateDirService.new(@project, current_user, @commit_params).execute - message = result[:message] - rescue => e - message = e.to_s - end - - if result && result[:status] == :success - flash[:notice] = "The directory has been successfully created" - respond_to do |format| - format.html { redirect_to after_create_dir_path } - end - else - flash[:alert] = message - respond_to do |format| - format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, @new_branch) } - end - end + create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.", + success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)), + failure_path: namespace_project_tree_path(@project.namespace, @project, @ref)) end private def assign_dir_vars - @new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref + @target_branch = params[:target_branch] + @dir_name = File.join(@path, params[:dir_name]) @commit_params = { file_path: @dir_name, - current_branch: @ref, - target_branch: @new_branch, commit_message: params[:commit_message], } end - - def after_create_dir_path - if create_merge_request? - new_merge_request_path - else - namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name)) - end - end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index bf5e25ff895..2dab04f2a7c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -171,7 +171,7 @@ class ProjectsController < ApplicationController @project.reload render json: { - html: view_to_html_string("projects/buttons/_star") + star_count: @project.star_count } end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 68e5d5be600..d31d4cde08f 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -22,32 +22,90 @@ module BlobHelper %w(credits changelog news copying copyright license authors) end - def edit_blob_link(project, ref, path, options = {}) - blob = - begin - project.repository.blob_at(ref, path) - rescue - nil - end - - return unless blob && blob.text? && blob_editable?(blob) - - text = 'Edit' - after = options[:after] || '' + def edit_blob_link(project = @project, ref = @ref, path = @path, options = {}) + return unless current_user + + blob = project.repository.blob_at(ref, path) rescue nil + + return unless blob && blob_text_viewable?(blob) + from_mr = options[:from_merge_request_id] link_opts = {} link_opts[:from_merge_request_id] = from_mr if from_mr - cls = 'btn btn-small' - link_to(text, - namespace_project_edit_blob_path(project.namespace, project, - tree_join(ref, path), - link_opts), - class: cls - ) + after.html_safe + + edit_path = namespace_project_edit_blob_path(project.namespace, project, + tree_join(ref, path), + link_opts) + + if !on_top_of_branch? + button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } + elsif can_edit_blob?(blob) + link_to "Edit", edit_path, class: 'btn btn-small' + elsif can?(current_user, :fork_project, project) + continue_params = { + to: edit_path, + notice: edit_in_new_fork_notice, + notice_now: edit_in_new_fork_notice_now + } + fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id, + continue: continue_params) + + link_to "Edit", fork_path, class: 'btn btn-small', method: :post + end + end + + def modify_file_link(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:) + return unless current_user + + blob = project.repository.blob_at(ref, path) rescue nil + + return unless blob + + if !on_top_of_branch? + button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } + elsif blob.lfs_pointer? + button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } + elsif can_edit_blob?(blob) + button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' + elsif can?(current_user, :fork_project, project) + continue_params = { + to: request.fullpath, + notice: edit_in_new_fork_notice + " Try to #{action} this file again.", + notice_now: edit_in_new_fork_notice_now + } + fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id, + continue: continue_params) + + link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post + end + end + + def replace_blob_link(project = @project, ref = @ref, path = @path) + modify_file_link( + project, + ref, + path, + label: "Replace", + action: "replace", + btn_class: "default", + modal_type: "upload" + ) + end + + def delete_blob_link(project = @project, ref = @ref, path = @path) + modify_file_link( + project, + ref, + path, + label: "Delete", + action: "delete", + btn_class: "remove", + modal_type: "remove" + ) end - def blob_editable?(blob, project = @project, ref = @ref) - !blob.lfs_pointer? && allowed_tree_edit?(project, ref) + def can_edit_blob?(blob, project = @project, ref = @ref) + !blob.lfs_pointer? && can_edit_tree?(project, ref) end def leave_edit_message @@ -70,7 +128,7 @@ module BlobHelper icon("#{file_type_icon_class('file', mode, name)} fw") end - def blob_viewable?(blob) + def blob_text_viewable?(blob) blob && blob.text? && !blob.lfs_pointer? end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index d2186427dba..4fe84322199 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -94,11 +94,14 @@ module IssuesHelper end.sort.to_sentence(last_word_connector: ', or ') end - def url_to_emoji(name) - emoji_path = ::AwardEmoji.path_to_emoji_image(name) - url_to_image(emoji_path) - rescue StandardError - "" + def emoji_icon(name, unicode = nil, aliases = []) + unicode ||= Emoji.emoji_filename(name) + + content_tag :div, "", + class: "icon emoji-icon emoji-#{unicode}", + "data-emoji" => name, + "data-aliases" => aliases.join(" "), + "data-unicode-name" => unicode end def emoji_author_list(notes, current_user) @@ -109,10 +112,6 @@ module IssuesHelper list.join(", ") end - def emoji_list - ::AwardEmoji::EMOJI_LIST - end - def note_active_class(notes, current_user) if current_user && notes.pluck(:author_id).include?(current_user.id) "active" diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 6c32647594d..1dd07a2a220 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -27,7 +27,16 @@ module MergeRequestsHelper end def ci_build_details_path(merge_request) - merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) + build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) + return nil unless build_url + + parsed_url = URI.parse(build_url) + + unless parsed_url.userinfo.blank? + parsed_url.userinfo = '' + end + + parsed_url.to_s end def merge_path_description(merge_request, separator) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 777817e24aa..77ba612548a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -105,6 +105,14 @@ module ProjectsHelper end end + def user_max_access_in_project(user_id, project) + level = project.team.max_member_access(user_id) + + if level + Gitlab::Access.options_with_owner.key(level) + end + end + private def get_project_nav_tabs(project, current_user) @@ -277,14 +285,6 @@ module ProjectsHelper end end - def user_max_access_in_project(user, project) - level = project.team.max_member_access(user) - - if level - Gitlab::Access.options_with_owner.key(level) - end - end - def leave_project_message(project) "Are you sure you want to leave \"#{project.name}\" project?" end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index f448dd0ab61..2ad7c80dae0 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -50,24 +50,49 @@ module TreeHelper project.repository.branch_names.include?(ref) end - def allowed_tree_edit?(project = nil, ref = nil) + def can_edit_tree?(project = nil, ref = nil) project ||= @project ref ||= @ref + return false unless on_top_of_branch?(project, ref) - can?(current_user, :push_code, project) + can?(current_user, :push_code, project) || + (current_user && current_user.already_forked?(project)) end def tree_edit_branch(project = @project, ref = @ref) - if allowed_tree_edit?(project, ref) - if can_push_branch?(project, ref) - ref - else - project.repository.next_patch_branch - end + return unless can_edit_tree?(project, ref) + + if can_push_branch?(project, ref) + ref + else + project = tree_edit_project(project) + project.repository.next_patch_branch + end + end + + def tree_edit_project(project = @project) + if can?(current_user, :push_code, project) + project + elsif current_user && current_user.already_forked?(project) + current_user.fork_of(project) end end + def edit_in_new_fork_notice_now + "You're not allowed to make changes to this project directly." + + " A fork of this project is being created that you can make changes in, so you can submit a merge request." + end + + def edit_in_new_fork_notice + "You're not allowed to make changes to this project directly." + + " A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + + def commit_in_fork_help + "A new branch will be created in your fork and a new merge request will be started." + end + def tree_breadcrumbs(tree, max_links = 2) if @path.present? part_path = "" diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 2e69ce923a2..71d33b445c2 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -69,7 +69,6 @@ module VisibilityLevelHelper def skip_level?(form_model, level) form_model.is_a?(Project) && - form_model.forked? && - !Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level) + !form_model.visibility_level_allowed?(level) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index cd5ae0fb0fd..1b3ee757040 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -132,14 +132,14 @@ class Ability end def public_project_rules - project_guest_rules + [ + @public_project_rules ||= project_guest_rules + [ :download_code, :fork_project ] end def project_guest_rules - [ + @project_guest_rules ||= [ :read_project, :read_wiki, :read_issue, @@ -157,7 +157,7 @@ class Ability end def project_report_rules - project_guest_rules + [ + @project_report_rules ||= project_guest_rules + [ :create_commit_status, :read_commit_statuses, :download_code, @@ -170,7 +170,7 @@ class Ability end def project_dev_rules - project_report_rules + [ + @project_dev_rules ||= project_report_rules + [ :admin_merge_request, :create_merge_request, :create_wiki, @@ -181,7 +181,7 @@ class Ability end def project_archived_rules - [ + @project_archived_rules ||= [ :create_merge_request, :push_code, :push_code_to_protected_branches, @@ -191,7 +191,7 @@ class Ability end def project_master_rules - project_dev_rules + [ + @project_master_rules ||= project_dev_rules + [ :push_code_to_protected_branches, :update_project_snippet, :update_merge_request, @@ -206,7 +206,7 @@ class Ability end def project_admin_rules - project_master_rules + [ + @project_admin_rules ||= project_master_rules + [ :change_namespace, :change_visibility_level, :rename_project, @@ -332,7 +332,7 @@ class Ability end if snippet.public? || snippet.internal? - rules << :read_personal_snippet + rules << :read_personal_snippet end rules diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 1f4e8b3ef24..724429e7558 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -134,4 +134,8 @@ class ApplicationSetting < ActiveRecord::Base /x) self.restricted_signup_domains.reject! { |d| d.empty? } end + + def runners_registration_token + ensure_runners_registration_token! + end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6d9cdb95295..7b89fe069ea 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -135,6 +135,16 @@ module Ci predefined_variables + yaml_variables + project_variables + trigger_variables end + def merge_request + merge_requests = MergeRequest.includes(:merge_request_diff) + .where(source_branch: ref, source_project_id: commit.gl_project_id) + .reorder(iid: :asc) + + merge_requests.find do |merge_request| + merge_request.commits.any? { |ci| ci.id == commit.sha } + end + end + def project commit.project end @@ -170,7 +180,8 @@ module Ci def extract_coverage(text, regex) begin - matches = text.gsub(Regexp.new(regex)).to_a.last + matches = text.scan(Regexp.new(regex)).last + matches = matches.last if matches.kind_of?(Array) coverage = matches.gsub(/\d+(\.\d+)?/).first if coverage.present? diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 808d80b0530..fc6f83b918b 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -37,7 +37,7 @@ module Participable # Be aware that this method makes a lot of sql queries. # Save result into variable if you are going to reuse it inside same request - def participants(current_user = self.author, load_lazy_references: true) + def participants(current_user = self.author) participants = Gitlab::ReferenceExtractor.lazily do self.class.participant_attrs.flat_map do |attr| diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 488ff8c31b7..885deaf78d2 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -18,15 +18,16 @@ module TokenAuthenticatable define_method("ensure_#{token_field}") do current_token = read_attribute(token_field) - if current_token.blank? - write_attribute(token_field, generate_token_for(token_field)) - else - current_token - end + current_token.blank? ? write_new_token(token_field) : current_token + end + + define_method("ensure_#{token_field}!") do + send("reset_#{token_field}!") if read_attribute(token_field).blank? + read_attribute(token_field) end define_method("reset_#{token_field}!") do - write_attribute(token_field, generate_token_for(token_field)) + write_new_token(token_field) save! end end @@ -34,7 +35,12 @@ module TokenAuthenticatable private - def generate_token_for(token_field) + def write_new_token(token_field) + new_token = generate_token(token_field) + write_attribute(token_field, new_token) + end + + def generate_token(token_field) loop do token = Devise.friendly_token break token unless self.class.unscoped.find_by(token_field => token) diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index 8bfc79d88f8..af1d7562ebe 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -16,7 +16,7 @@ class GlobalMilestone end def safe_title - @title.to_slug.to_s + @title.to_slug.normalize.to_s end def expired? diff --git a/app/models/identity.rb b/app/models/identity.rb index ad60154be71..8bcdc194953 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -12,6 +12,7 @@ class Identity < ActiveRecord::Base include Sortable + include CaseSensitivity belongs_to :user validates :provider, presence: true diff --git a/app/models/issue.rb b/app/models/issue.rb index 4571d7f0ee1..80ecd15077f 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -86,7 +86,7 @@ class Issue < ActiveRecord::Base def referenced_merge_requests Gitlab::ReferenceExtractor.lazily do [self, *notes].flat_map do |note| - note.all_references(load_lazy_references: false).merge_requests + note.all_references.merge_requests end end.sort_by(&:iid) end diff --git a/app/models/jira_issue.rb b/app/models/jira_issue.rb new file mode 100644 index 00000000000..5b21aac5e43 --- /dev/null +++ b/app/models/jira_issue.rb @@ -0,0 +1,2 @@ +class JiraIssue < ExternalIssue +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d7430d36c41..ac25d38eb63 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -335,7 +335,7 @@ class MergeRequest < ActiveRecord::Base issues = commits.flat_map { |c| c.closes_issues(current_user) } issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). closed_by_message(description)) - issues.uniq + issues.uniq(&:id) else [] end diff --git a/app/models/project.rb b/app/models/project.rb index 13fd383237c..75f85310d5f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -64,6 +64,19 @@ class Project < ActiveRecord::Base update_column(:last_activity_at, self.created_at) end + # update visibility_levet of forks + after_update :update_forks_visibility_level + def update_forks_visibility_level + return unless visibility_level < visibility_level_was + + forks.each do |forked_project| + if forked_project.visibility_level > visibility_level + forked_project.visibility_level = visibility_level + forked_project.save! + end + end + end + ActsAsTaggableOn.strict_case_match = true acts_as_taggable_on :tags @@ -100,9 +113,12 @@ class Project < ActiveRecord::Base has_one :gitlab_issue_tracker_service, dependent: :destroy has_one :external_wiki_service, dependent: :destroy - has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" + has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" + has_one :forked_from_project, through: :forked_project_link + + has_many :forked_project_links, foreign_key: "forked_from_project_id" + has_many :forks, through: :forked_project_links, source: :forked_to_project - has_one :forked_from_project, through: :forked_project_link # Merge Requests for target project should be removed with it has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' # Merge requests from source project should be kept when source project was removed @@ -499,6 +515,10 @@ class Project < ActiveRecord::Base @ci_service ||= ci_services.find(&:activated?) end + def jira_tracker? + issues_tracker.to_param == 'jira' + end + def avatar_type unless self.avatar.image? self.errors.add :avatar, 'only images allowed' @@ -764,7 +784,7 @@ class Project < ActiveRecord::Base end def forks_count - ForkedProjectLink.where(forked_from_project_id: self.id).count + forks.count end def find_label(name) @@ -799,6 +819,10 @@ class Project < ActiveRecord::Base false end + def jira_tracker_active? + jira_tracker? && jira_service.active + end + def ci_commit(sha) ci_commits.find_by(sha: sha) end @@ -854,4 +878,9 @@ class Project < ActiveRecord::Base def open_issues_count issues.opened.count end + + def visibility_level_allowed?(level) + return true unless forked? + Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i) + end end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index d73182d40ac..b64d97ce75d 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -18,6 +18,11 @@ # note_events :boolean default(TRUE), not null # +# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed class GitlabCiService < CiService - # this is no longer used + # We override the active accessor to always make GitLabCiService disabled + # Otherwise the GitLabCiService can be picked, but should never be since it's deprecated + def active + false + end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 35e30b1cb0b..e216f406e1c 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -19,9 +19,24 @@ # class JiraService < IssueTrackerService + include HTTParty include Gitlab::Application.routes.url_helpers - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + DEFAULT_API_VERSION = 2 + + prop_accessor :username, :password, :api_url, :jira_issue_transition_id, + :title, :description, :project_url, :issues_url, :new_issue_url + + before_validation :set_api_url, :set_jira_issue_transition_id + + before_update :reset_password + + def reset_password + # don't reset the password if a new one is provided + if api_url_changed? && !password_touched? + self.password = nil + end + end def help line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\ @@ -54,4 +69,228 @@ class JiraService < IssueTrackerService def to_param 'jira' end + + def fields + super.push( + { type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' }, + { type: 'text', name: 'username', placeholder: '' }, + { type: 'password', name: 'password', placeholder: '' }, + { type: 'text', name: 'jira_issue_transition_id', placeholder: '2' } + ) + end + + def execute(push, issue = nil) + if issue.nil? + # No specific issue, that means + # we just want to test settings + test_settings + else + close_issue(push, issue) + end + end + + def create_cross_reference_note(mentioned, noteable, author) + issue_name = mentioned.id + project = self.project + noteable_name = noteable.class.name.underscore.downcase + noteable_id = if noteable.is_a?(Commit) + noteable.id + else + noteable.iid + end + + entity_url = build_entity_url(noteable_name.to_sym, noteable_id) + + data = { + user: { + name: author.name, + url: resource_url(user_path(author)), + }, + project: { + name: project.path_with_namespace, + url: resource_url(namespace_project_path(project.namespace, project)) + }, + entity: { + name: noteable_name.humanize.downcase, + url: entity_url + } + } + + add_comment(data, issue_name) + end + + def test_settings + result = JiraService.get( + jira_api_test_url, + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{auth}" + } + ) + + case result.code + when 201, 200 + Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.") + true + else + Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}") + false + end + rescue Errno::ECONNREFUSED => e + Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}." + false + end + + private + + def build_api_url_from_project_url + server = URI(project_url) + default_ports = [["http",80],["https",443]].include?([server.scheme,server.port]) + server_url = "#{server.scheme}://#{server.host}" + server_url.concat(":#{server.port}") unless default_ports + "#{server_url}/rest/api/#{DEFAULT_API_VERSION}" + rescue + "" # looks like project URL was not valid + end + + def set_api_url + self.api_url = build_api_url_from_project_url if self.api_url.blank? + end + + def set_jira_issue_transition_id + self.jira_issue_transition_id ||= "2" + end + + def close_issue(entity, issue) + commit_id = if entity.is_a?(Commit) + entity.id + elsif entity.is_a?(MergeRequest) + entity.last_commit.id + end + commit_url = build_entity_url(:commit, commit_id) + + # Depending on the JIRA project's workflow, a comment during transition + # may or may not be allowed. Split the operation in to two calls so the + # comment always works. + transition_issue(issue) + add_issue_solved_comment(issue, commit_id, commit_url) + end + + def transition_issue(issue) + message = { + transition: { + id: jira_issue_transition_id + } + } + send_message(close_issue_url(issue.iid), message.to_json) + end + + def add_issue_solved_comment(issue, commit_id, commit_url) + comment = { + body: "Issue solved with [#{commit_id}|#{commit_url}]." + } + + send_message(comment_url(issue.iid), comment.to_json) + end + + def add_comment(data, issue_name) + url = comment_url(issue_name) + user_name = data[:user][:name] + user_url = data[:user][:url] + entity_name = data[:entity][:name] + entity_url = data[:entity][:url] + project_name = data[:project][:name] + + message = { + body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]." + } + + unless existing_comment?(issue_name, message[:body]) + send_message(url, message.to_json) + end + end + + + def auth + require 'base64' + Base64.urlsafe_encode64("#{self.username}:#{self.password}") + end + + def send_message(url, message) + result = JiraService.post( + url, + body: message, + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{auth}" + } + ) + + message = case result.code + when 201, 200, 204 + "#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}." + when 401 + "#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again." + else + "#{self.class.name} ERROR #{result.code}: #{result.parsed_response}" + end + + Rails.logger.info(message) + message + rescue URI::InvalidURIError, Errno::ECONNREFUSED => e + Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}." + end + + def existing_comment?(issue_name, new_comment) + result = JiraService.get( + comment_url(issue_name), + headers: { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{auth}" + } + ) + + case result.code + when 201, 200 + existing_comments = JSON.parse(result.body)['comments'] + + if existing_comments.present? + return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any? + end + end + + false + rescue JSON::ParserError + false + end + + def resource_url(resource) + "#{Settings.gitlab['url'].chomp("/")}#{resource}" + end + + def build_entity_url(entity_name, entity_id) + resource_url( + polymorphic_url( + [ + self.project.namespace.becomes(Namespace), + self.project, + entity_name + ], + id: entity_id, + routing_type: :path + ) + ) + end + + def close_issue_url(issue_name) + "#{self.api_url}/issue/#{issue_name}/transitions" + end + + def comment_url(issue_name) + "#{self.api_url}/issue/#{issue_name}/comment" + end + + def jira_api_test_url + "#{self.api_url}/myself" + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 2c25f4ce451..9f688e3b45b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -592,47 +592,54 @@ class Repository Gitlab::Popen.popen(args, path_to_repo) end - def commit_with_hooks(current_user, branch) - oldrev = Gitlab::Git::BLANK_SHA - ref = Gitlab::Git::BRANCH_REF_PREFIX + branch - was_empty = empty? - - # Create temporary ref + def with_tmp_ref(oldrev = nil) random_string = SecureRandom.hex tmp_ref = "refs/tmp/#{random_string}/head" - unless was_empty - oldrev = find_branch(branch).target + if oldrev && !Gitlab::Git.blank_ref?(oldrev) rugged.references.create(tmp_ref, oldrev) end # Make commit in tmp ref - newrev = yield(tmp_ref) + yield(tmp_ref) + ensure + rugged.references.delete(tmp_ref) rescue nil + end + + def commit_with_hooks(current_user, branch) + oldrev = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch + was_empty = empty? - unless newrev - raise CommitError.new('Failed to create commit') + unless was_empty + oldrev = find_branch(branch).target end - GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do - if was_empty - # Create branch - rugged.references.create(ref, newrev) - else - # Update head - current_head = find_branch(branch).target + with_tmp_ref(oldrev) do |tmp_ref| + # Make commit in tmp ref + newrev = yield(tmp_ref) + + unless newrev + raise CommitError.new('Failed to create commit') + end - # Make sure target branch was not changed during pre-receive hook - if current_head == oldrev - rugged.references.update(ref, newrev) + GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do + if was_empty + # Create branch + rugged.references.create(ref, newrev) else - raise CommitError.new('Commit was rejected because branch received new push') + # Update head + current_head = find_branch(branch).target + + # Make sure target branch was not changed during pre-receive hook + if current_head == oldrev + rugged.references.update(ref, newrev) + else + raise CommitError.new('Commit was rejected because branch received new push') + end end end end - rescue GitHooksService::PreReceiveError - # Remove tmp ref and return error to user - rugged.references.delete(tmp_ref) - raise end private diff --git a/app/models/user.rb b/app/models/user.rb index e0ce091c54e..df87f3b79bd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,6 +26,7 @@ # bio :string(255) # failed_attempts :integer default(0) # locked_at :datetime +# unlock_token :string(255) # username :string(255) # can_create_group :boolean default(TRUE), not null # can_create_team :boolean default(TRUE), not null diff --git a/app/services/base_service.rb b/app/services/base_service.rb index f00ec7408b6..b48ca67d4d2 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -39,10 +39,7 @@ class BaseService def deny_visibility_level(model, denied_visibility_level = nil) denied_visibility_level ||= model.visibility_level - level_name = 'Unknown' - Gitlab::VisibilityLevel.options.each do |name, level| - level_name = name if level == denied_visibility_level - end + level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level) model.errors.add( :visibility_level, diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index de18f3bc556..f139872c728 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,10 +1,10 @@ require_relative 'base_service' class CreateBranchService < BaseService - def execute(branch_name, ref) + def execute(branch_name, ref, source_project: @project) valid_branch = Gitlab::GitRefValidator.validate(branch_name) if valid_branch == false - return error('Branch name invalid') + return error('Branch name is invalid') end repository = project.repository @@ -13,7 +13,20 @@ class CreateBranchService < BaseService return error('Branch already exists') end - new_branch = repository.add_branch(current_user, branch_name, ref) + new_branch = nil + if source_project != @project + repository.with_tmp_ref do |tmp_ref| + repository.fetch_ref( + source_project.repository.path_to_repo, + "refs/heads/#{ref}", + tmp_ref + ) + + new_branch = repository.add_branch(current_user, branch_name, tmp_ref) + end + else + new_branch = repository.add_branch(current_user, branch_name, ref) + end if new_branch push_data = build_push_data(project, current_user, new_branch) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 9a67b160940..0326a8823e9 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -3,8 +3,10 @@ module Files class ValidationError < StandardError; end def execute - @current_branch = params[:current_branch] + @source_project = params[:source_project] || @project + @source_branch = params[:source_branch] @target_branch = params[:target_branch] + @commit_message = params[:commit_message] @file_path = params[:file_path] @file_content = if params[:file_content_encoding] == 'base64' @@ -16,8 +18,8 @@ module Files # Validate parameters validate - # Create new branch if it different from current_branch - if @target_branch != @current_branch + # Create new branch if it different from source_branch + if different_branch? create_target_branch end @@ -26,18 +28,14 @@ module Files else error("Something went wrong. Your changes were not committed") end - rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex + rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex error(ex.message) end private - def current_branch - @current_branch ||= params[:current_branch] - end - - def target_branch - @target_branch ||= params[:target_branch] + def different_branch? + @source_branch != @target_branch || @source_project != @project end def raise_error(message) @@ -52,11 +50,11 @@ module Files end unless project.empty_repo? - unless repository.branch_names.include?(@current_branch) + unless @source_project.repository.branch_names.include?(@source_branch) raise_error("You can only create or edit files when you are on a branch") end - if @current_branch != @target_branch + if different_branch? if repository.branch_names.include?(@target_branch) raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes") end @@ -65,10 +63,10 @@ module Files end def create_target_branch - result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch) + result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project) unless result[:status] == :success - raise_error("Something went wrong when we tried to create #{@target_branch} for you") + raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}") end end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 2348920cc58..e4cde4a2fd8 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -26,7 +26,7 @@ module Files unless project.empty_repo? @file_path.slice!(0) if @file_path.start_with?('/') - blob = repository.blob_at_branch(@current_branch, @file_path) + blob = repository.blob_at_branch(@source_branch, @file_path) if blob raise_error("Your changes could not be committed because a file with the same name already exists") diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index 3d85f97b7e5..a1a20e47681 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -1,6 +1,11 @@ module Issues class CloseService < Issues::BaseService def execute(issue, commit = nil) + if project.jira_tracker? && project.jira_service.active + project.jira_service.execute(commit, issue) + return issue + end + if project.default_issues_tracker? && issue.close event_service.close_issue(issue, current_user) create_note(issue, commit) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 69bdd045ddf..895e089bea3 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -3,12 +3,16 @@ module Projects def execute # check that user is allowed to set specified visibility_level new_visibility = params[:visibility_level] - if new_visibility && new_visibility.to_i != project.visibility_level - unless can?(current_user, :change_visibility_level, project) && - Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) - deny_visibility_level(project, new_visibility) - return project + if new_visibility + if new_visibility.to_i != project.visibility_level + unless can?(current_user, :change_visibility_level, project) && + Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) + deny_visibility_level(project, new_visibility) + return project + end end + + return false unless visibility_level_allowed?(new_visibility) end new_branch = params[:default_branch] @@ -23,5 +27,19 @@ module Projects end end end + + private + + def visibility_level_allowed?(level) + return true if project.visibility_level_allowed?(level) + + level_name = Gitlab::VisibilityLevel.level_name(level) + project.errors.add( + :visibility_level, + "#{level_name} could not be set as visibility level of this project - parent project settings are more restrictive" + ) + + false + end end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 6975b2ee55b..98a71cbf1ad 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -241,9 +241,14 @@ class SystemNoteService note_options.merge!(noteable: noteable) end - create_note(note_options) + if noteable.is_a?(ExternalIssue) + noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author) + else + create_note(note_options) + end end + def self.cross_reference?(note_text) note_text.start_with?(cross_reference_note_prefix) end @@ -259,7 +264,7 @@ class SystemNoteService # # Returns Boolean def self.cross_reference_disallowed?(noteable, mentioner) - return true if noteable.is_a?(ExternalIssue) + return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active? return false unless mentioner.is_a?(MergeRequest) return false unless noteable.is_a?(Commit) diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 8657d2c71fe..531247e9148 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -80,6 +80,10 @@ %span.pull-right = API::API::version %p + Git + %span.pull-right + = Gitlab::Git.version + %p Ruby %span.pull-right #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml index 8358a14445b..741d111fb7d 100644 --- a/app/views/admin/identities/index.html.haml +++ b/app/views/admin/identities/index.html.haml @@ -1,6 +1,7 @@ - page_title "Identities", @user.name, "Users" = render 'admin/users/head' += link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new' - if @identities.present? .table-holder %table.table diff --git a/app/views/admin/identities/new.html.haml b/app/views/admin/identities/new.html.haml new file mode 100644 index 00000000000..e30bf0ef0ee --- /dev/null +++ b/app/views/admin/identities/new.html.haml @@ -0,0 +1,4 @@ +- page_title "New Identity" +%h3.page-title New identity +%hr += render 'form' diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index c5fb3c95506..c407972cd08 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -3,7 +3,7 @@ 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. Registration token is - %code{ id: 'runners-token' } #{current_application_settings.ensure_runners_registration_token} + %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token} .bs-callout.clearfix .pull-left diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index 77f78caa8d8..f7875e68b7e 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -41,5 +41,3 @@ %i.fa.fa-remove.incorrect-syntax %b Error: = @error - - diff --git a/app/views/ci/lints/create.js.haml b/app/views/ci/lints/create.js.haml deleted file mode 100644 index a96c0b11b6e..00000000000 --- a/app/views/ci/lints/create.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -:plain - $(".results").html("#{escape_javascript(render "create")}")
\ No newline at end of file diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index fb9057e4882..a144c43be47 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -1,27 +1,17 @@ %h2 Check your .gitlab-ci.yml %hr -= form_tag ci_lint_path, method: :post, remote: true do - .control-group - = label_tag :content, "Content of .gitlab-ci.yml", class: 'control-label' - .controls - = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true +.row + = form_tag ci_lint_path, method: :post do + .form-group + = label_tag :content, 'Content of .gitlab-ci.yml', class: 'control-label text-nowrap' + .col-sm-12 + = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true + .col-sm-12 + .pull-left.prepend-top-10 + = submit_tag 'Validate', class: 'btn btn-success submit-yml' - .control-group.clearfix - .controls.pull-left.prepend-top-10 - = submit_tag "Validate", class: 'btn btn-success submit-yml' - - -%p.text-center.loading - %i.fa.fa-refresh.fa-spin - -.results.prepend-top-20 - -:javascript - $(".loading").hide(); - $('form').bind('ajax:beforeSend', function() { - $(".loading").show(); - }); - $('form').bind('ajax:complete', function() { - $(".loading").hide(); - }); +.row.prepend-top-20 + .col-sm-12 + .results + = render partial: 'create' if defined?(@status) diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 2e77afb7525..f4a3e3162bf 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,13 +1,20 @@ = content_for :flash_message do = render 'shared/project_limit' +.top-area + %ul.left-top-menu + = nav_link(page: [dashboard_projects_path, root_path]) do + = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do + Your Projects + = nav_link(page: starred_dashboard_projects_path) do + = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do + Starred Projects + = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do + = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do + Explore Projects -%ul.center-top-menu - = nav_link(page: [dashboard_projects_path, root_path]) do - = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do - Your Projects - = nav_link(page: starred_dashboard_projects_path) do - = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do - Starred Projects - = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do - = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do - Explore Projects + .projects-search-form + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false + - if current_user.can_create_project? + = link_to new_project_path, class: 'btn btn-green' do + %i.fa.fa-plus + New Project diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml index 81a5909e2d2..cea9ffcc748 100644 --- a/app/views/dashboard/projects/_projects.html.haml +++ b/app/views/dashboard/projects/_projects.html.haml @@ -1,11 +1,3 @@ .projects-list-holder - .projects-search-form - .input-group - = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false - - if current_user.can_create_project? - %span.input-group-btn - = link_to new_project_path, class: 'btn btn-green' do - %i.fa.fa-plus - New Project = render 'shared/projects/list', projects: @projects, ci: true diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb deleted file mode 100644 index 79d6c761d8f..00000000000 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<p>Hello <%= @resource.email %>!</p> - -<p>Your account has been locked due to an excessive amount of unsuccessful sign in attempts.</p> - -<p>Click the link below to unlock your account:</p> - -<p><%= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) %></p> diff --git a/app/views/devise/mailer/unlock_instructions.html.haml b/app/views/devise/mailer/unlock_instructions.html.haml new file mode 100644 index 00000000000..52b327e20c5 --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.haml @@ -0,0 +1,10 @@ +%p +Hello #{@resource.name}! + +%p + Your GitLab account has been locked due to an excessive amount of unsuccessful + sign in attempts. Your account will automatically unlock in + = time_ago_in_words(Devise.unlock_in.from_now) + or you may click the link below to unlock now. + +%p= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb deleted file mode 100644 index f9277d1673f..00000000000 --- a/app/views/devise/unlocks/new.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -<h2>Resend unlock instructions</h2> - -<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> - <%= devise_error_messages! %> - - <div><%= f.label :email %><br /> - <%= f.email_field :email %></div> - - <div><%= f.submit "Resend unlock instructions" %></div> -<% end %> - -<%= render partial: "devise/shared/links" %> diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml new file mode 100644 index 00000000000..49c087c0646 --- /dev/null +++ b/app/views/devise/unlocks/new.html.haml @@ -0,0 +1,14 @@ +.login-box + .login-heading + %h3 Resend unlock email + .login-body + = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| + .devise-errors + = devise_error_messages! + .clearfix.append-bottom-20 + = f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off' + .clearfix + = f.submit 'Resend unlock instructions', class: 'btn btn-success' + +.clearfix.prepend-top-20 + = render 'devise/shared/sign_in_link' diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 76bdd68fd76..b9a958fbe7b 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -6,7 +6,7 @@ - else = render 'explore/head' -.gray-content-block.clearfix +.gray-content-block.clearfix.second-block = render 'filter' = render 'projects', projects: @projects = paginate @projects, theme: "gitlab" diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index e30c3633223..95d46e331f8 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -7,7 +7,7 @@ = render 'explore/head' .explore-trending-block - .gray-content-block + .gray-content-block.second-block .pull-right = render 'explore/projects/dropdown' .oneline diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 1412b19acde..fa0b718e48b 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -7,7 +7,7 @@ = render 'explore/head' .explore-trending-block - .gray-content-block + .gray-content-block.second-block .pull-right = render 'explore/projects/dropdown' .oneline diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 11d69977ef9..bbafc08435a 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -1,5 +1,5 @@ -.panel.panel-default.projects-list-holder - .panel-heading.clearfix +.projects-list-holder + .projects-search-form .input-group = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false - if can? current_user, :create_projects, @group diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index dc8e81323a6..c2c7c581b3e 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -5,37 +5,47 @@ - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") -.dashboard - .header-with-avatar.clearfix - = image_tag group_icon(@group), class: "avatar group-avatar s90" - %h3 - = @group.name - .username - @#{@group.path} - - if @group.description.present? - .description - = markdown(@group.description, pipeline: :description) - %hr - - = render 'shared/show_aside' - - - if can?(current_user, :read_group, @group) - .row - %section.activities.col-md-7 - .hidden-xs - - if current_user - = render "events/event_last_push", event: @last_push - .pull-right - = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do - %i.fa.fa-rss - - = render 'shared/event_filter' - %hr - - .content_list - = spinner - %aside.side.col-md-5 - = render "projects", projects: @projects - - else - %p - This group does not have public projects +.cover-block + .avatar-holder + = link_to group_icon(@group), target: '_blank' do + = image_tag group_icon(@group), class: "avatar group-avatar s90" + .cover-title + = @group.name + + .cover-desc.username + @#{@group.path} + + - if @group.description.present? + .cover-desc.description + = markdown(@group.description, pipeline: :description) + +- if can?(current_user, :read_group, @group) + %ul.center-top-menu.no-top + %li.active + = link_to "#activity", 'data-toggle' => 'tab' do + Activity + - if @projects.present? + %li + = link_to "#projects", 'data-toggle' => 'tab' do + Projects + + .tab-content + .tab-pane.active#activity + .gray-content-block.activity-filter-block + - if current_user + = render "events/event_last_push", event: @last_push + .pull-right + = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do + %i.fa.fa-rss + + = render 'shared/event_filter' + + .content_list + = spinner + + .tab-pane#projects + = render "projects", projects: @projects + +- else + %p + This group does not have public projects diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml index 11166dc6d99..13a18269d11 100644 --- a/app/views/profiles/keys/new.html.haml +++ b/app/views/profiles/keys/new.html.haml @@ -12,6 +12,6 @@ comment = val.match(/^\S+ \S+ (.+)\n?$/); if( comment && comment.length > 1 && title.val() == '' ){ - $('#key_title').val( comment[1] ); + $('#key_title').val( comment[1] ).change(); } }); diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml index 2fd3d9e1be4..640612ca433 100644 --- a/app/views/projects/_commit_button.html.haml +++ b/app/views/projects/_commit_button.html.haml @@ -2,3 +2,7 @@ = button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create' = link_to 'Cancel', cancel_path, class: 'btn btn-cancel', data: {confirm: leave_edit_message} + + - unless can?(current_user, :push_code, @project) + .inline.prepend-left-10 + = commit_in_fork_help diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index c1669ac046b..e92115b9b98 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -27,7 +27,7 @@ = icon('rss') .project-repo-buttons - .split-one + .split-one.count-buttons = render 'projects/buttons/star' = render 'projects/buttons/fork' @@ -38,3 +38,6 @@ = render 'projects/buttons/dropdown' = render 'projects/buttons/notifications' + +:coffeescript + new Star()
\ No newline at end of file diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index b1df8d19938..cdac50f7a8d 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -2,7 +2,7 @@ = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), class: 'btn btn-sm', target: '_blank' -# only show normal/blame view links for text files - - if blob_viewable?(@blob) + - if blob_text_viewable?(@blob) - if current_page? namespace_project_blame_path(@project.namespace, @project, @id) = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id), class: 'btn btn-sm' @@ -14,13 +14,8 @@ = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.sha, @path)), class: 'btn btn-sm' -- if blob_editable?(@blob) +- if current_user .btn-group{ role: "group" } - = edit_blob_link(@project, @ref, @path) - %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace - %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete -- elsif !on_top_of_branch? - .btn-group{ role: "group" } - %button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on a branch.", data: {container: 'body'}} Edit - %button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on a branch.", data: {container: 'body'}} Replace - %button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on a branch.", data: {container: 'body'}} Delete + = edit_blob_link + = replace_blob_link + = delete_blob_link diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index fc6c9f5fd09..084608bbba3 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -17,5 +17,9 @@ = submit_tag "Create directory", class: 'btn btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" + - unless can?(current_user, :push_code, @project) + .inline.prepend-left-10 + = commit_in_fork_help + :javascript new NewCommitForm($('.js-create-dir-form')) diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index ecc90a30e78..676924dc6ca 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -20,6 +20,11 @@ = button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" + - unless can?(current_user, :push_code, @project) + .inline.prepend-left-10 + = commit_in_fork_help + + :javascript disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file'); new BlobFileDropzone($('.js-upload-blob-form'), '#{method}'); diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index a47fe7ede80..09fa148b129 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -20,7 +20,7 @@ = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - = render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path + = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) :javascript blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 3f8d11ed8c8..6988039b6c7 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -6,7 +6,7 @@ %div#tree-holder.tree-holder = render 'blob', blob: @blob -- if blob_editable?(@blob) +- if can_edit_blob?(@blob) = render 'projects/blob/remove' - title = "Replace #{@blob.name}" diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 31943a2407a..c659af6338c 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -9,11 +9,12 @@ New Branch %hr -= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do += form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-create-branch-form js-requires-input" do .form-group = label_tag :branch_name, nil, class: 'control-label' .col-sm-10 - = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control' + = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name' + .help-block.text-danger.js-branch-name-error .form-group = label_tag :ref, 'Create from', class: 'control-label' .col-sm-10 @@ -26,7 +27,4 @@ :javascript var availableRefs = #{@project.repository.ref_names.to_json}; - $("#ref").autocomplete({ - source: availableRefs, - minLength: 1 - }); + new NewBranchForm($('.js-create-branch-form'), availableRefs) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 20a5b6a66e7..5b7ecce86ab 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -7,6 +7,10 @@ %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) from = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) + - merge_request = @build.merge_request + - if merge_request + via + = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request) #up-build-trace - if @commit.matrix_for_ref?(@build.ref) diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index b277b765b6b..1f639fecc30 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -18,10 +18,11 @@ = link_to new_namespace_project_snippet_path(@project.namespace, @project) do = icon('file-text-o fw') New snippet + - if can?(current_user, :push_code, @project) %li.divider %li - = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), title: 'New file' do + = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do = icon('file fw') New file %li @@ -32,3 +33,20 @@ = link_to new_namespace_project_tag_path(@project.namespace, @project) do = icon('tags fw') New tag + - elsif current_user && current_user.already_forked?(@project) + %li.divider + %li + = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do + = icon('file fw') + New file + - elsif can?(current_user, :fork_project, @project) + %li.divider + %li + - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), + notice: edit_in_new_fork_notice, + notice_now: edit_in_new_fork_notice_now } + - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + continue: continue_params) + = link_to fork_path, method: :post do + = icon('file fw') + New file diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 2d3abf09051..133531887a2 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -4,10 +4,15 @@ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do = icon('code-fork fw') Fork + %div.count-with-arrow + %span.arrow %span.count = @project.forks_count - else = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do = icon('code-fork fw') + Fork + %div.count-with-arrow + %span.arrow %span.count = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 41a3ec6d90f..21ba426aaa1 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,19 +1,21 @@ - if current_user = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do - = icon('star fw') - %span.count + - if current_user.starred?(@project) + = icon('star fw') + %span.starred Unstar + - else + = icon('star-o fw') + %span Star + %div.count-with-arrow + %span.arrow + %span.count.star-count = @project.star_count - :javascript - $('.project-home-panel .toggle-star').on('ajax:success', function (e, data, status, xhr) { - $(this).replaceWith(data.html); - }) - .on('ajax:error', function (e, xhr, status, error) { - new Flash('Star toggle failed. Try again later.', 'alert'); - }); - - else = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do = icon('star fw') + Star + %div.count-with-arrow + %span.arrow %span.count = @project.star_count diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 327e7d9245a..517f6aef7c5 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -24,7 +24,7 @@ = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" .diff-controls - - if blob_viewable?(blob) + - if blob_text_viewable?(blob) = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do %i.fa.fa-comments @@ -32,14 +32,15 @@ - if editable_diff?(diff_file) = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, - after: ' ', from_merge_request_id: @merge_request.id) + from_merge_request_id: @merge_request.id) + = view_file_btn(diff_commit.id, diff_file, project) .diff-content.diff-wrap-lines -# Skipp all non non-supported blobs - return unless blob.respond_to?('text?') - - if blob_viewable?(blob) + - if blob_text_viewable?(blob) - if diff_view == 'parallel' = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - else diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml index f0b0a11c04a..8a2c027a455 100644 --- a/app/views/projects/forks/new.html.haml +++ b/app/views/projects/forks/new.html.haml @@ -43,4 +43,3 @@ %i.fa.fa-spinner.fa-spin Forking repository %p Please wait a moment, this page will automatically refresh when ready. - diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 86d3dc546ba..dc434cf38c4 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -1,9 +1,9 @@ - content_for :note_actions do - if can?(current_user, :update_issue, @issue) - if @issue.closed? - = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue' + = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue' - else - = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue' + = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close js-note-target-close', title: 'Close Issue' #notes = render 'projects/notes/notes_with_form' diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 2fe6f88b2a9..b6efa05a1ae 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -23,16 +23,16 @@ .pull-right - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do = icon('plus') New Issue - if can?(current_user, :update_issue, @issue) - if @issue.closed? - = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen' + = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen' - else - = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue' + = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close Issue' - = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do + = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do = icon('pencil-square-o') Edit diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 399e9cc1e1b..bff3c3b283d 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -1,8 +1,8 @@ - content_for :note_actions do - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? - = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" + = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" - if @merge_request.closed? - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" #notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 105c731c7e1..a051729dc32 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -17,7 +17,7 @@ - if merge_request.open? && merge_request.broken? %li - = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: {container: 'body'} do + = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do = icon('exclamation-triangle') - if merge_request.assignee diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 473124480ac..fc6fb2a0d42 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -17,9 +17,9 @@ .issue-btn-group.pull-right - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? - = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-grouped btn-close', title: 'Close merge request' - = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-grouped issuable-edit', id: 'edit_merge_request' do + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request' + = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do %i.fa.fa-pencil-square-o Edit - if @merge_request.closed? - = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request' + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request' diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 88e711ab534..acb6dc52a8e 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -13,6 +13,6 @@ .error-alert .note-form-actions.clearfix - = f.submit 'Add Comment', class: "btn btn-create comment-btn btn-grouped js-comment-button" + = f.submit 'Add Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button" = yield(:note_actions) %a.btn.btn-cancel.js-close-discussion-note-form Cancel diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 9c7a5584da9..7466a098e24 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -71,7 +71,7 @@ = render default_project_view - if current_user - - access = user_max_access_in_project(current_user, @project) + - access = user_max_access_in_project(current_user.id, @project) - if access .prepend-top-20.project-footer .gray-content-block.footer-block.center diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 1bc90edd8f0..1927883513a 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -29,7 +29,7 @@ - if tree.readme = render "projects/tree/readme", readme: tree.readme -- if allowed_tree_edit? +- if can_edit_tree? = render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post = render 'projects/blob/new_dir' diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 89b072cea92..3343288ad2b 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -11,34 +11,65 @@ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path) - else = link_to title, '#' - - if allowed_tree_edit? + + - if current_user %li - %span.dropdown - %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"} + - if !on_top_of_branch? + %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }} = icon('plus') - %ul.dropdown-menu - %li - = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do - = icon('pencil fw') - New file - %li - = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do - = icon('file fw') - Upload file - %li - = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do - = icon('folder fw') - New directory - %li.divider - %li - = link_to new_namespace_project_branch_path(@project.namespace, @project) do - = icon('code-fork fw') - New branch - %li - = link_to new_namespace_project_tag_path(@project.namespace, @project) do - = icon('tags fw') - New tag - - elsif !on_top_of_branch? - %li - %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}} - = icon('plus') + - else + %span.dropdown + %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"} + = icon('plus') + %ul.dropdown-menu + - if can_edit_tree? + %li + = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do + = icon('pencil fw') + New file + %li + = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do + = icon('file fw') + Upload file + %li + = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do + = icon('folder fw') + New directory + - elsif can?(current_user, :fork_project, @project) + %li + - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id), + notice: edit_in_new_fork_notice, + notice_now: edit_in_new_fork_notice_now } + - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + continue: continue_params) + = link_to fork_path, method: :post do + = icon('pencil fw') + New file + %li + - continue_params = { to: request.fullpath, + notice: edit_in_new_fork_notice + " Try to upload a file again.", + notice_now: edit_in_new_fork_notice_now } + - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + continue: continue_params) + = link_to fork_path, method: :post do + = icon('file fw') + Upload file + %li + - continue_params = { to: request.fullpath, + notice: edit_in_new_fork_notice + " Try to create a new directory again.", + notice_now: edit_in_new_fork_notice_now } + - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id, + continue: continue_params) + = link_to fork_path, method: :post do + = icon('folder fw') + New directory + + %li.divider + %li + = link_to new_namespace_project_branch_path(@project.namespace, @project) do + = icon('code-fork fw') + New branch + %li + = link_to new_namespace_project_tag_path(@project.namespace, @project) do + = icon('tags fw') + New tag diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index ce8ddff9556..45d700781f3 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -6,7 +6,7 @@ - if issue.description.present? .description.term = preserve do - = search_md_sanitize(markdown(issue.description)) + = search_md_sanitize(markdown(issue.description, { project: issue.project })) %span.light #{issue.project.name_with_namespace} - if issue.closed? diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index edb5778f424..687a59c270f 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,10 +1,27 @@ - project = project || @project -.git-clone-holder.input-group - .input-group-addon.git-protocols - .input-group-btn - = ssh_clone_button(project) - .input-group-btn - = http_clone_button(project) + +.git-clone-holder + .btn-group.clone-options + %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'} + %span + = default_clone_protocol.upcase + = icon('angle-down') + %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown + %li + %a#ssh-selector{href: @project.ssh_url_to_repo} + SSH + %li + %a#http-selector{href: @project.http_url_to_repo} + HTTPS + = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true .input-group-btn = clipboard_button(clipboard_target: '#project_clone') + +:javascript + $('ul.clone-options-dropdown a').on('click',function(e){ + e.preventDefault(); + var $this = $(this); + $('a.clone-dropdown-btn span').text($this.text()); + $('#project_clone').val($this.attr('href')); + }); diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml index 111219f2064..0c8ac48bb58 100644 --- a/app/views/shared/_new_commit_form.html.haml +++ b/app/views/shared/_new_commit_form.html.haml @@ -1,16 +1,22 @@ = render 'shared/commit_message_container', placeholder: placeholder -- unless @project.empty_repo? - .form-group.branch - = label_tag 'new_branch', 'Target branch', class: 'control-label' - .col-sm-10 - = text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch" +- if @project.empty_repo? + = hidden_field_tag 'target_branch', @ref +- else + - if can?(current_user, :push_code, @project) + .form-group.branch + = label_tag 'target_branch', 'Target branch', class: 'control-label' + .col-sm-10 + = text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch" - .js-create-merge-request-container - .checkbox - - nonce = SecureRandom.hex - = label_tag "create_merge_request-#{nonce}" do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" - Start a <strong>new merge request</strong> with these changes + .js-create-merge-request-container + .checkbox + - nonce = SecureRandom.hex + = label_tag "create_merge_request-#{nonce}" do + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" + Start a <strong>new merge request</strong> with these changes + - else + = hidden_field_tag 'target_branch', @target_branch || tree_edit_branch + = hidden_field_tag 'create_merge_request', 1 = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch' diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 6071f1484c6..e16187bb42f 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,33 +1,46 @@ .awards.votes-block - votable.notes.awards.grouped_awards.each do |emoji, notes| .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)} - .icon{"data-emoji" => "#{emoji}"} - = image_tag url_to_emoji(emoji), height: "20px", width: "20px" + = emoji_icon(emoji) .counter = notes.count - if current_user - .dropdown.awards-controls + .awards-controls %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} = icon('smile-o') - %ul.dropdown-menu.awards-menu - - emoji_list.each do |emoji| - %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" + .emoji-menu + .emoji-menu-content + = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control" + - AwardEmoji.emoji_by_category.each do |category, emojis| + %h5= AwardEmoji::CATEGORIES[category] + %ul + - emojis.each do |emoji| + %li + = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"]) - if current_user :coffeescript post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" noteable_type = "#{votable.class.name.underscore}" noteable_id = "#{votable.id}" - aliases = #{AwardEmoji::ALIASES.to_json} - window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id, aliases) + aliases = #{AwardEmoji.aliases.to_json} - $(".awards-menu li").click (e)-> - emoji = $(this).data("emoji") + window.awards_handler = new AwardsHandler( + post_emoji_url, + noteable_type, + noteable_id, + aliases + ) + + $(".awards").on "click", ".emoji-menu-content li", (e) -> + emoji = $(this).find(".emoji-icon").data("emoji") awards_handler.addAward(emoji) - $(".awards").on "click", ".award", (e)-> + $(".awards").on "click", ".award", (e) -> emoji = $(this).find(".icon").data("emoji") awards_handler.addAward(emoji) $(".award").tooltip() + + $(".emoji-menu-content").niceScroll({cursorwidth: "7px", autohidemode: false}) |