Commit a695b855 authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge branch 'master' into droplab-templating-xss-fix

parents 9fe127ff 970f9624
...@@ -8,6 +8,7 @@ import { glEmojiTag } from './behaviors/gl_emoji'; ...@@ -8,6 +8,7 @@ import { glEmojiTag } from './behaviors/gl_emoji';
import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid'; import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const requestAnimationFrame = window.requestAnimationFrame || const requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
...@@ -105,8 +106,9 @@ function AwardsHandler() { ...@@ -105,8 +106,9 @@ function AwardsHandler() {
const $glEmojiElement = $target.find('gl-emoji'); const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon'); const $spriteIconElement = $target.find('.icon');
const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
$target.closest('.js-awards-block').addClass('current'); $target.closest('.js-awards-block').addClass('current');
return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
}); });
} }
...@@ -132,12 +134,12 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { ...@@ -132,12 +134,12 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
if ($menu.is('.is-visible')) { if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active'); $addBtn.removeClass('is-active');
$menu.removeClass('is-visible'); $menu.removeClass('is-visible');
$('#emoji_search').blur(); $('.js-emoji-menu-search').blur();
} else { } else {
$addBtn.addClass('is-active'); $addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn); this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible'); $menu.addClass('is-visible');
$('#emoji_search').focus(); $('.js-emoji-menu-search').focus();
} }
} else { } else {
$addBtn.addClass('is-loading is-active'); $addBtn.addClass('is-loading is-active');
...@@ -147,7 +149,7 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { ...@@ -147,7 +149,7 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
this.positionMenu($createdMenu, $addBtn); this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => { return setTimeout(() => {
$createdMenu.addClass('is-visible'); $createdMenu.addClass('is-visible');
$('#emoji_search').focus(); $('.js-emoji-menu-search').focus();
}, 200); }, 200);
}); });
} }
...@@ -180,7 +182,7 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) { ...@@ -180,7 +182,7 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
const emojiMenuMarkup = ` const emojiMenuMarkup = `
<div class="emoji-menu"> <div class="emoji-menu">
<input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" placeholder="Search emoji" /> <input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
<div class="emoji-menu-content"> <div class="emoji-menu-content">
${frequentlyUsedCatgegory} ${frequentlyUsedCatgegory}
...@@ -500,24 +502,41 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj ...@@ -500,24 +502,41 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj
}; };
AwardsHandler.prototype.setupSearch = function setupSearch() { AwardsHandler.prototype.setupSearch = function setupSearch() {
this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => { const $search = $('.js-emoji-menu-search');
this.registerEventListener('on', $search, 'input', (e) => {
const term = $(e.target).val().trim(); const term = $(e.target).val().trim();
this.searchEmojis(term);
});
const $menu = $('.emoji-menu');
this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
if (e.target === e.currentTarget) {
// Clear the search
this.searchEmojis('');
}
});
};
AwardsHandler.prototype.searchEmojis = function searchEmojis(term) {
const $search = $('.js-emoji-menu-search');
$search.val(term);
// Clean previous search results // Clean previous search results
$('ul.emoji-menu-search, h5.emoji-search-title').remove(); $('ul.emoji-menu-search, h5.emoji-search-title').remove();
if (term.length > 0) { if (term.length > 0) {
// Generate a search result block // Generate a search result block
const h5 = $('<h5 class="emoji-search-title"/>').text('Search results'); const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
const foundEmojis = this.searchEmojis(term).show(); const foundEmojis = this.findMatchingEmojiElements(term).show();
const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide(); $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
$('.emoji-menu-content').append(h5).append(ul); $('.emoji-menu-content').append(h5).append(ul);
} else { } else {
$('.emoji-menu-content').children().show(); $('.emoji-menu-content').children().show();
} }
});
}; };
AwardsHandler.prototype.searchEmojis = function searchEmojis(term) { AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) {
const safeTerm = term.toLowerCase(); const safeTerm = term.toLowerCase();
const namesMatchingAlias = []; const namesMatchingAlias = [];
......
...@@ -20,8 +20,7 @@ import Vue from 'vue'; ...@@ -20,8 +20,7 @@ import Vue from 'vue';
data: function () { data: function () {
return { return {
discussions: CommentsStore.state, discussions: CommentsStore.state,
loading: false, loading: false
note: {},
}; };
}, },
watch: { watch: {
...@@ -34,6 +33,9 @@ import Vue from 'vue'; ...@@ -34,6 +33,9 @@ import Vue from 'vue';
discussion: function () { discussion: function () {
return this.discussions[this.discussionId]; return this.discussions[this.discussionId];
}, },
note: function () {
return this.discussion ? this.discussion.getNote(this.noteId) : {};
},
buttonText: function () { buttonText: function () {
if (this.isResolved) { if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`; return `Resolved by ${this.resolvedByName}`;
...@@ -112,8 +114,6 @@ import Vue from 'vue'; ...@@ -112,8 +114,6 @@ import Vue from 'vue';
authorAvatar: this.authorAvatar, authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated, noteTruncated: this.noteTruncated,
}); });
this.note = this.discussion.getNote(this.noteId);
} }
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import IssueTitle from './issue_title'; import IssueTitle from './issue_title.vue';
import '../vue_shared/vue_resource_interceptor'; import '../vue_shared/vue_resource_interceptor';
const vueOptions = () => ({ (() => {
el: '.issue-title-entrypoint',
components: {
IssueTitle,
},
data() {
const issueTitleData = document.querySelector('.issue-title-data').dataset; const issueTitleData = document.querySelector('.issue-title-data').dataset;
const { initialTitle, endpoint } = issueTitleData;
return { const vm = new Vue({
initialTitle: issueTitleData.initialTitle, el: '.issue-title-entrypoint',
endpoint: issueTitleData.endpoint, render: createElement => createElement(IssueTitle, {
}; props: {
initialTitle,
endpoint,
}, },
template: ` }),
<IssueTitle });
:initialTitle="initialTitle"
:endpoint="endpoint"
/>
`,
});
(() => new Vue(vueOptions()))(); return vm;
})();
<script>
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Poll from './../lib/utils/poll'; import Poll from './../lib/utils/poll';
import Service from './services/index'; import Service from './services/index';
...@@ -72,7 +73,9 @@ export default { ...@@ -72,7 +73,9 @@ export default {
created() { created() {
this.fetch(); this.fetch();
}, },
template: `
<h2 class='title' v-html='title'></h2>
`,
}; };
</script>
<template>
<h2 class="title" v-html="title"></h2>
</template>
...@@ -79,4 +79,5 @@ ...@@ -79,4 +79,5 @@
= render 'shared/issuable/sidebar', issuable: @issue = render 'shared/issuable/sidebar', issuable: @issue
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('issue_show') = page_specific_javascript_bundle_tag('issue_show')
---
title: Clear emoji search in awards menu after picking emoji
merge_request:
author:
---
title: Fix another case where trace does not have proper encoding set
merge_request: 10728
author:
...@@ -125,6 +125,7 @@ var config = { ...@@ -125,6 +125,7 @@ var config = {
'notebook_viewer', 'notebook_viewer',
'pdf_viewer', 'pdf_viewer',
'vue_pipelines', 'vue_pipelines',
'issue_show',
], ],
minChunks: function(module, count) { minChunks: function(module, count) {
return module.resource && (/vue_shared/).test(module.resource); return module.resource && (/vue_shared/).test(module.resource);
......
...@@ -87,7 +87,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -87,7 +87,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end end
step 'I search "hand"' do step 'I search "hand"' do
fill_in 'emoji_search', with: 'hand' fill_in 'emoji-menu-search', with: 'hand'
end end
step 'I see search result for "hand"' do step 'I see search result for "hand"' do
...@@ -101,7 +101,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -101,7 +101,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end end
step 'The search field is focused' do step 'The search field is focused' do
expect(page).to have_selector('#emoji_search') expect(page).to have_selector('.js-emoji-menu-search')
expect(page.evaluate_script('document.activeElement.id')).to eq('emoji_search') expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true)
end end
end end
...@@ -136,7 +136,8 @@ module Banzai ...@@ -136,7 +136,8 @@ module Banzai
nodes.each_with_object({}) do |node, hash| nodes.each_with_object({}) do |node, hash|
if node.has_attribute?(attribute) if node.has_attribute?(attribute)
hash[node] = objects_by_id[node.attr(attribute).to_i] obj = objects_by_id[node.attr(attribute).to_i]
hash[node] = obj if obj
end end
end end
end end
......
...@@ -14,6 +14,14 @@ module Gitlab ...@@ -14,6 +14,14 @@ module Gitlab
def initialize def initialize
@stream = yield @stream = yield
if @stream
@stream.binmode
# Ci::Ansi2html::Converter would read from @stream directly,
# using @stream.each_line to be specific. It's safe to set
# the encoding here because IO#seek(bytes) and IO#read(bytes)
# are not characters based, so encoding doesn't matter to them.
@stream.set_encoding(Encoding.default_external)
end
end end
def valid? def valid?
...@@ -51,7 +59,7 @@ module Gitlab ...@@ -51,7 +59,7 @@ module Gitlab
read_last_lines(last_lines) read_last_lines(last_lines)
else else
stream.read stream.read
end end.force_encoding(Encoding.default_external)
end end
def html_with_state(state = nil) def html_with_state(state = nil)
...@@ -113,7 +121,6 @@ module Gitlab ...@@ -113,7 +121,6 @@ module Gitlab
end end
chunks.join.lines.last(last_lines).join chunks.join.lines.last(last_lines).join
.force_encoding(Encoding.default_external)
end end
end end
end end
......
...@@ -65,7 +65,7 @@ require('~/lib/utils/common_utils'); ...@@ -65,7 +65,7 @@ require('~/lib/utils/common_utils');
$emojiMenu = $('.emoji-menu'); $emojiMenu = $('.emoji-menu');
expect($emojiMenu.length).toBe(1); expect($emojiMenu.length).toBe(1);
expect($emojiMenu.hasClass('is-visible')).toBe(true); expect($emojiMenu.hasClass('is-visible')).toBe(true);
expect($emojiMenu.find('#emoji_search').length).toBe(1); expect($emojiMenu.find('.js-emoji-menu-search').length).toBe(1);
return expect($('.js-awards-block.current').length).toBe(1); return expect($('.js-awards-block.current').length).toBe(1);
}); });
}); });
...@@ -217,23 +217,43 @@ require('~/lib/utils/common_utils'); ...@@ -217,23 +217,43 @@ require('~/lib/utils/common_utils');
return expect($thumbsUpEmoji.data("original-title")).toBe('sam'); return expect($thumbsUpEmoji.data("original-title")).toBe('sam');
}); });
}); });
describe('search', function() { describe('::searchEmojis', () => {
return it('should filter the emoji', function(done) { it('should filter the emoji', function(done) {
return openAndWaitForEmojiMenu() return openAndWaitForEmojiMenu()
.then(() => { .then(() => {
expect($('[data-name=angel]').is(':visible')).toBe(true); expect($('[data-name=angel]').is(':visible')).toBe(true);
expect($('[data-name=anger]').is(':visible')).toBe(true); expect($('[data-name=anger]').is(':visible')).toBe(true);
$('#emoji_search').val('ali').trigger('input'); awardsHandler.searchEmojis('ali');
expect($('[data-name=angel]').is(':visible')).toBe(false); expect($('[data-name=angel]').is(':visible')).toBe(false);
expect($('[data-name=anger]').is(':visible')).toBe(false); expect($('[data-name=anger]').is(':visible')).toBe(false);
expect($('[data-name=alien]').is(':visible')).toBe(true); expect($('[data-name=alien]').is(':visible')).toBe(true);
expect($('.js-emoji-menu-search').val()).toBe('ali');
}) })
.then(done) .then(done)
.catch((err) => { .catch((err) => {
done.fail(`Failed to open and build emoji menu: ${err.message}`); done.fail(`Failed to open and build emoji menu: ${err.message}`);
}); });
}); });
it('should clear the search when searching for nothing', function(done) {
return openAndWaitForEmojiMenu()
.then(() => {
awardsHandler.searchEmojis('ali');
expect($('[data-name=angel]').is(':visible')).toBe(false);
expect($('[data-name=anger]').is(':visible')).toBe(false);
expect($('[data-name=alien]').is(':visible')).toBe(true);
awardsHandler.searchEmojis('');
expect($('[data-name=angel]').is(':visible')).toBe(true);
expect($('[data-name=anger]').is(':visible')).toBe(true);
expect($('[data-name=alien]').is(':visible')).toBe(true);
expect($('.js-emoji-menu-search').val()).toBe('');
})
.then(done)
.catch((err) => {
done.fail(`Failed to open and build emoji menu: ${err.message}`);
}); });
});
});
describe('emoji menu', function() { describe('emoji menu', function() {
const emojiSelector = '[data-name="sunglasses"]'; const emojiSelector = '[data-name="sunglasses"]';
const openEmojiMenuAndAddEmoji = function() { const openEmojiMenuAndAddEmoji = function() {
......
import Vue from 'vue'; import Vue from 'vue';
import issueTitle from '~/issue_show/issue_title'; import issueTitle from '~/issue_show/issue_title.vue';
describe('Issue Title', () => { describe('Issue Title', () => {
let IssueTitleComponent; let IssueTitleComponent;
......
...@@ -114,8 +114,27 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do ...@@ -114,8 +114,27 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
expect(hash).to eq({ link => user }) expect(hash).to eq({ link => user })
end end
it 'returns an empty Hash when the list of nodes is empty' do it 'returns an empty Hash when entry does not exist in the database' do
expect(subject.grouped_objects_for_nodes([], User, 'data-user')).to eq({}) link = double(:link)
expect(link).to receive(:has_attribute?).
with('data-user').
and_return(true)
expect(link).to receive(:attr).
with('data-user').
and_return('1')
nodes = [link]
bad_id = user.id + 100
expect(subject).to receive(:unique_attribute_values).
with(nodes, 'data-user').
and_return([bad_id.to_s])
hash = subject.grouped_objects_for_nodes(nodes, User, 'data-user')
expect(hash).to eq({})
end end
end end
......
...@@ -43,13 +43,29 @@ describe Gitlab::Ci::Trace::Stream do ...@@ -43,13 +43,29 @@ describe Gitlab::Ci::Trace::Stream do
it 'forwards to the next linefeed, case 1' do it 'forwards to the next linefeed, case 1' do
stream.limit(7) stream.limit(7)
expect(stream.raw).to eq('') result = stream.raw
expect(result).to eq('')
expect(result.encoding).to eq(Encoding.default_external)
end end
it 'forwards to the next linefeed, case 2' do it 'forwards to the next linefeed, case 2' do
stream.limit(29) stream.limit(29)
expect(stream.raw).to eq("\e[01;32m許功蓋\e[0m\n") result = stream.raw
expect(result).to eq("\e[01;32m許功蓋\e[0m\n")
expect(result.encoding).to eq(Encoding.default_external)
end
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/30796
it 'reads in binary, output as Encoding.default_external' do
stream.limit(52)
result = stream.html
expect(result).to eq("ヾ(´༎ຶД༎ຶ`)ノ<br><span class=\"term-fg-green\">許功蓋</span><br>")
expect(result.encoding).to eq(Encoding.default_external)
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment