FEATURE: Introduces new emoji-picker
This commit is contained in:
parent
87a1ff15fd
commit
6de258d4cf
|
@ -69,8 +69,6 @@
|
||||||
//= require ./discourse/components/notifications-button
|
//= require ./discourse/components/notifications-button
|
||||||
//= require ./discourse/lib/link-mentions
|
//= require ./discourse/lib/link-mentions
|
||||||
//= require ./discourse/components/site-header
|
//= require ./discourse/components/site-header
|
||||||
//= require ./discourse/lib/emoji/groups
|
|
||||||
//= require ./discourse/lib/emoji/toolbar
|
|
||||||
//= require ./discourse/components/d-editor
|
//= require ./discourse/components/d-editor
|
||||||
//= require ./discourse/lib/screen-track
|
//= require ./discourse/lib/screen-track
|
||||||
//= require ./discourse/routes/discourse
|
//= require ./discourse/routes/discourse
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/*global Mousetrap:true */
|
/*global Mousetrap:true */
|
||||||
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
|
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
|
||||||
import { showSelector } from "discourse/lib/emoji/toolbar";
|
|
||||||
import Category from 'discourse/models/category';
|
import Category from 'discourse/models/category';
|
||||||
import { categoryHashtagTriggerRule } from 'discourse/lib/category-hashtags';
|
import { categoryHashtagTriggerRule } from 'discourse/lib/category-hashtags';
|
||||||
import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags';
|
import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags';
|
||||||
|
@ -346,20 +345,8 @@ export default Ember.Component.extend({
|
||||||
if (v.code) {
|
if (v.code) {
|
||||||
return `${v.code}:`;
|
return `${v.code}:`;
|
||||||
} else {
|
} else {
|
||||||
showSelector({
|
$editorInput.autocomplete({cancel: true});
|
||||||
appendTo: self.$(),
|
self.set('emojiPickerIsActive', true);
|
||||||
register,
|
|
||||||
onSelect: title => {
|
|
||||||
// Remove the previously type characters when a new emoji is selected from the selector.
|
|
||||||
let selected = self._getSelected();
|
|
||||||
let newPre = selected.pre.replace(/:[^:]+$/, ":");
|
|
||||||
let numOfRemovedChars = selected.pre.length - newPre.length;
|
|
||||||
selected.pre = newPre;
|
|
||||||
selected.start -= numOfRemovedChars;
|
|
||||||
selected.end -= numOfRemovedChars;
|
|
||||||
self._addText(selected, `${title}:`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -614,6 +601,21 @@ export default Ember.Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
emojiSelected(code) {
|
||||||
|
let selected = this._getSelected();
|
||||||
|
const captures = selected.pre.match(/\B:(\w*)$/);
|
||||||
|
|
||||||
|
if(_.isEmpty(captures)) {
|
||||||
|
this._addText(selected, `:${code}:`);
|
||||||
|
} else {
|
||||||
|
let numOfRemovedChars = selected.pre.length - captures[1].length;
|
||||||
|
selected.pre = selected.pre.slice(0, selected.pre.length - captures[1].length);
|
||||||
|
selected.start -= numOfRemovedChars;
|
||||||
|
selected.end -= numOfRemovedChars;
|
||||||
|
this._addText(selected, `${code}:`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
toolbarButton(button) {
|
toolbarButton(button) {
|
||||||
const selected = this._getSelected(button.trimLeading);
|
const selected = this._getSelected(button.trimLeading);
|
||||||
const toolbarEvent = {
|
const toolbarEvent = {
|
||||||
|
@ -692,11 +694,7 @@ export default Ember.Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
emoji() {
|
emoji() {
|
||||||
showSelector({
|
this.set('emojiPickerIsActive', !this.get('emojiPickerIsActive'));
|
||||||
appendTo: this.$(),
|
|
||||||
register: this.register,
|
|
||||||
onSelect: title => this._addText(this._getSelected(), `:${title}:`)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,448 @@
|
||||||
|
import { observes } from "ember-addons/ember-computed-decorators";
|
||||||
|
import { findRawTemplate } from "discourse/lib/raw-templates";
|
||||||
|
import { emojiUrlFor } from "discourse/lib/text";
|
||||||
|
import KeyValueStore from "discourse/lib/key-value-store";
|
||||||
|
import { emojis } from "pretty-text/emoji/data";
|
||||||
|
import { extendedEmojiList, isSkinTonableEmoji } from "pretty-text/emoji";
|
||||||
|
|
||||||
|
const recentTemplate = findRawTemplate("emoji-picker-recent");
|
||||||
|
const pickerTemplate = findRawTemplate("emoji-picker");
|
||||||
|
export const keyValueStore = new KeyValueStore("discourse_emojis_");
|
||||||
|
export const EMOJI_USAGE = "emojiUsage";
|
||||||
|
export const EMOJI_SCROLL_Y = "emojiScrollY";
|
||||||
|
export const EMOJI_SELECTED_DIVERSITY = "emojiSelectedDiversity";
|
||||||
|
const PER_ROW = 11;
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
customEmojis: _.map(_.keys(extendedEmojiList()), function(code) {
|
||||||
|
return { code, src: emojiUrlFor(code) };
|
||||||
|
}),
|
||||||
|
|
||||||
|
$picker: Ember.computed("active", function() {
|
||||||
|
return this.$(".emoji-picker");
|
||||||
|
}),
|
||||||
|
|
||||||
|
$filter: Ember.computed("$picker", function() {
|
||||||
|
return this.get("$picker").find(".filter");
|
||||||
|
}),
|
||||||
|
|
||||||
|
$results: Ember.computed("$picker", function() {
|
||||||
|
return this.get("$picker").find(".results");
|
||||||
|
}),
|
||||||
|
|
||||||
|
$list: Ember.computed("$picker", function() {
|
||||||
|
return this.get("$picker").find(".list");
|
||||||
|
}),
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
this._super();
|
||||||
|
this._unbindEvents();
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super();
|
||||||
|
|
||||||
|
if (!keyValueStore.getObject(EMOJI_USAGE)) {
|
||||||
|
keyValueStore.setObject({ key: EMOJI_USAGE, value: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set("selectedDiversity", keyValueStore.getObject(EMOJI_SELECTED_DIVERSITY) || 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
didUpdateAttrs() {
|
||||||
|
this._super();
|
||||||
|
|
||||||
|
if (this.get("active")) {
|
||||||
|
this.show();
|
||||||
|
} else {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("filter")
|
||||||
|
filterChanged() {
|
||||||
|
this.get("$filter").find(".clear-filter").toggle(!_.isEmpty(this.get("filter")));
|
||||||
|
Ember.run.debounce(this, this._filterEmojisList, 250);
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("selectedDiversity")
|
||||||
|
selectedDiversityChanged() {
|
||||||
|
keyValueStore.setObject({key: EMOJI_SELECTED_DIVERSITY, value: this.get("selectedDiversity")});
|
||||||
|
|
||||||
|
$.each(this.get("$list").find(".emoji.diversity[src!='']"), (_, icon) => {
|
||||||
|
this._updateIconSrc(icon);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this.get("filter") !== "") {
|
||||||
|
$.each(this.get("$results").find(".emoji.diversity"), (_, icon) => {
|
||||||
|
this._updateIconSrc(icon);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes("recentEmojis")
|
||||||
|
recentEmojisChanged() {
|
||||||
|
const $recentSection = this.get("$list").find(".section[data-section='recent']");
|
||||||
|
const $recentSectionGroup = $recentSection.find(".section-group");
|
||||||
|
const $recentCategory = this.get("$picker").find(".category-icon a[title='recent']").parent();
|
||||||
|
if(_.isEmpty(this.get("recentEmojis"))) {
|
||||||
|
$recentCategory.hide();
|
||||||
|
$recentSection.css("height", 0).hide();
|
||||||
|
} else {
|
||||||
|
$recentCategory.show();
|
||||||
|
$recentSection.css("height", "auto").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
const recentEmojis = _.map(this.get("recentEmojis"), function(emoji) {
|
||||||
|
return { code: emoji.title, src: emojiUrlFor(emoji.title) };
|
||||||
|
});
|
||||||
|
const model = { recentEmojis };
|
||||||
|
const template = recentTemplate(model);
|
||||||
|
$recentSectionGroup.html(template);
|
||||||
|
this._bindHover($recentSectionGroup.find("a"));
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.get("$picker")
|
||||||
|
.css({width: "", left: "", bottom: ""})
|
||||||
|
.empty();
|
||||||
|
this.$(".emoji-picker-modal").removeClass("fadeIn");
|
||||||
|
|
||||||
|
this._unbindEvents();
|
||||||
|
},
|
||||||
|
|
||||||
|
show() {
|
||||||
|
const model = { customEmojis: this.get("customEmojis") };
|
||||||
|
const template = pickerTemplate(model);
|
||||||
|
this.get("$picker").html(template);
|
||||||
|
|
||||||
|
this._bindEvents();
|
||||||
|
|
||||||
|
Ember.run.later(this, function() {
|
||||||
|
this._setDiversity();
|
||||||
|
this._positionPicker();
|
||||||
|
this._scrollTo();
|
||||||
|
this.recentEmojisChanged();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindEvents() {
|
||||||
|
this._bindDiversityClick();
|
||||||
|
this._bindSectionsScroll();
|
||||||
|
this._bindEmojiClick();
|
||||||
|
this._bindClearRecentEmojisGroup();
|
||||||
|
this._bindResizing();
|
||||||
|
this._bindHover();
|
||||||
|
this._bindCategoryClick();
|
||||||
|
this._bindModalClick();
|
||||||
|
this._bindFilterInput();
|
||||||
|
this._bindEscape();
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindEscape() {
|
||||||
|
this.$().on("keydown", e => {
|
||||||
|
if (e.which === 27) {
|
||||||
|
this.set("active", false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindModalClick() {
|
||||||
|
this.$(".emoji-picker-modal").on("click", () => {
|
||||||
|
this.set("active", false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_unbindEvents() {
|
||||||
|
this.$(window).off("resize");
|
||||||
|
this.$(".emoji-picker-modal").off("click");
|
||||||
|
Ember.$("#reply-control").off("div-resized");
|
||||||
|
this.$().off("keydown");
|
||||||
|
},
|
||||||
|
|
||||||
|
_filterEmojisList() {
|
||||||
|
const $filter = this.get("$picker").find(".filter");
|
||||||
|
|
||||||
|
if (this.get("filter") === "") {
|
||||||
|
$filter.find("input[name='filter']").val("");
|
||||||
|
this.get("$results").empty().hide();
|
||||||
|
this.get("$list").show();
|
||||||
|
} else {
|
||||||
|
const regexp = new RegExp(this.get("filter"), "g");
|
||||||
|
const filteredCodes = _.filter(emojis, code => regexp.test(code)).slice(0, 30);
|
||||||
|
this.get("$results").empty().html(
|
||||||
|
_.map(filteredCodes, (code) => {
|
||||||
|
const hasDiversity = isSkinTonableEmoji(code);
|
||||||
|
const diversity = hasDiversity ? "diversity" : "";
|
||||||
|
const scaledCode = this._codeWithDiversity(code, hasDiversity);
|
||||||
|
return `<a title="${code}">
|
||||||
|
<img src="${emojiUrlFor(scaledCode)}" data-code="${code}" class="emoji ${diversity}" />
|
||||||
|
</a>`;
|
||||||
|
})
|
||||||
|
).show();
|
||||||
|
this._bindHover(this.get("$results").find("a"));
|
||||||
|
this._bindEmojiClick(this.get("$results"));
|
||||||
|
this.get("$list").hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindFilterInput() {
|
||||||
|
const $filter = this.get("$picker").find(".filter");
|
||||||
|
const $input = $filter.find("input");
|
||||||
|
|
||||||
|
$input.on("input", (event) => {
|
||||||
|
this.set("filter", event.currentTarget.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$filter.find(".clear-filter").on("click", () => {
|
||||||
|
$input.val("").focus();
|
||||||
|
this.set("filter", "");
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindCategoryClick() {
|
||||||
|
this.get("$picker").find(".category-icon").on("click", "a", (event) => {
|
||||||
|
this.set("filter", "");
|
||||||
|
this.get("$results").empty();
|
||||||
|
this.get("$list").show();
|
||||||
|
|
||||||
|
const section = $(event.currentTarget).attr("title");
|
||||||
|
const $section = this.get("$list").find(`.section[data-section="${section}"]`);
|
||||||
|
const scrollTop = this.get("$list").scrollTop() +
|
||||||
|
( $section.offset().top - this.get("$list").offset().top );
|
||||||
|
|
||||||
|
this._scrollTo(scrollTop);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindHover(hoverables) {
|
||||||
|
const replaceInfoContent = (html) => {
|
||||||
|
this.get("$picker").find(".footer .info").html(html || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
(hoverables || this.$(".section-group a")).hover(event => {
|
||||||
|
const $a = $(event.currentTarget);
|
||||||
|
const code = this._codeWithDiversity($a.attr("title"), $a.find("img").hasClass("diversity"));
|
||||||
|
const html = `<img src="${emojiUrlFor(code)}" class="emoji"> <span>:${code}:<span>`;
|
||||||
|
replaceInfoContent(html);
|
||||||
|
},
|
||||||
|
() => replaceInfoContent()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindResizing() {
|
||||||
|
this.$(window).on("resize", () => {
|
||||||
|
Ember.run.debounce(this, this._positionPicker, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ember.$("#reply-control").on("div-resized", () => {
|
||||||
|
Ember.run.debounce(this, this._positionPicker, 100);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindClearRecentEmojisGroup() {
|
||||||
|
const $recent = this.get("$picker").find(".section[data-section='recent'] .clear-recent");
|
||||||
|
$recent.on("click", () => {
|
||||||
|
keyValueStore.setObject({ key: EMOJI_USAGE, value: {} });
|
||||||
|
this.set("recentEmojis", {});
|
||||||
|
this._scrollTo(0);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindEmojiClick(emojisContainer) {
|
||||||
|
const $emojisContainer = emojisContainer || this.get("$list").find(".section-group");
|
||||||
|
$emojisContainer.off("click").on("click", "a", e => {
|
||||||
|
const $icon = $(e.currentTarget);
|
||||||
|
const title = $icon.attr("title");
|
||||||
|
const code = this._codeWithDiversity(title, $icon.find("img").hasClass("diversity"));
|
||||||
|
|
||||||
|
this._trackEmojiUsage(code);
|
||||||
|
|
||||||
|
if(this._isSmallViewport()) {
|
||||||
|
this.set("active", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindSectionsScroll() {
|
||||||
|
this.get("$list").on("scroll", () => {
|
||||||
|
Ember.run.debounce(this, this._checkVisibleSection, 150);
|
||||||
|
Ember.run.debounce(this, this._storeScrollPosition, 50);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_checkVisibleSection() {
|
||||||
|
const $sections = this.get("$list").find(".section");
|
||||||
|
const sections = [];
|
||||||
|
let cumulatedHeight = 0;
|
||||||
|
|
||||||
|
$.each($sections, (_, section) => {
|
||||||
|
const $section = $(section);
|
||||||
|
sections.push({$section, cumulatedHeight});
|
||||||
|
cumulatedHeight += $section.innerHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
let selectedSection;
|
||||||
|
const currentScrollTop = this.get("$list").scrollTop()
|
||||||
|
if (!_.isEmpty(this.get("recentEmojis")) && currentScrollTop === 0) {
|
||||||
|
selectedSection = _.first(sections);
|
||||||
|
} else if (!_.isEmpty(this.get("customEmojis")) &&
|
||||||
|
currentScrollTop === this.get("$list")[0].scrollHeight - this.get("$list").innerHeight())
|
||||||
|
{
|
||||||
|
selectedSection = _.last(sections);
|
||||||
|
} else {
|
||||||
|
selectedSection = _.last(_.reject(sections, (section) => {
|
||||||
|
return section.cumulatedHeight > currentScrollTop;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selectedSection) {
|
||||||
|
this.get("$picker").find(".category-icon").removeClass("current");
|
||||||
|
this.get("$picker").find(`.category-icon a[title='${selectedSection.$section.data("section")}']`)
|
||||||
|
.parent()
|
||||||
|
.addClass("current");
|
||||||
|
|
||||||
|
if(!selectedSection.$section.hasClass("loaded")) {
|
||||||
|
selectedSection.$section.addClass("loaded");
|
||||||
|
this._loadVisibleEmojis(selectedSection.$section.find(".emoji[src='']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//preload surrounding sections
|
||||||
|
const selectedSectionIndex = sections.indexOf(selectedSection);
|
||||||
|
const preloadedSection = sections[selectedSectionIndex + 1] || sections[selectedSectionIndex - 1];
|
||||||
|
if(preloadedSection && !preloadedSection.$section.hasClass("loaded")) {
|
||||||
|
preloadedSection.$section.addClass("loaded");
|
||||||
|
const $visibleEmojis = preloadedSection.$section.find(".emoji[src='']");
|
||||||
|
Ember.run.later(() => { this._loadVisibleEmojis($visibleEmojis) }, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindDiversityClick() {
|
||||||
|
const $diversityScales = this.get("$picker").find(".diversity-picker .diversity-scale");
|
||||||
|
$diversityScales.on("click", (event) => {
|
||||||
|
const $selectedDiversity = $(event.currentTarget);
|
||||||
|
$diversityScales.removeClass("selected");
|
||||||
|
$selectedDiversity.addClass("selected");
|
||||||
|
this.set("selectedDiversity", parseInt($selectedDiversity.data("level")));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_setDiversity() {
|
||||||
|
this.get("$picker")
|
||||||
|
.find(`.diversity-picker .diversity-scale[data-level="${this.get("selectedDiversity")}"]`)
|
||||||
|
.addClass("selected");
|
||||||
|
},
|
||||||
|
|
||||||
|
_isSmallViewport() {
|
||||||
|
return this.site.isMobileDevice || this.$(window).width() <= 1024 || this.$(window).height() <= 768;
|
||||||
|
},
|
||||||
|
|
||||||
|
_positionPicker(){
|
||||||
|
if(!this.get("active")) { return; }
|
||||||
|
|
||||||
|
let isLargePreview = this.$(window).height() -
|
||||||
|
Ember.$(".d-header").height() -
|
||||||
|
Ember.$("#reply-control").height() <
|
||||||
|
this.get("$picker").height() + 16;
|
||||||
|
|
||||||
|
if(this._isSmallViewport()) {
|
||||||
|
this.$(".emoji-picker-modal").addClass("fadeIn");
|
||||||
|
this.get("$picker").css({
|
||||||
|
width: this.site.isMobileDevice ? this.$(window).width() - 10 : 340,
|
||||||
|
marginLeft: this.site.isMobileDevice ? -(this.$(window).width() - 10)/2 : -170,
|
||||||
|
marginTop: -150,
|
||||||
|
left: "50%",
|
||||||
|
top: "50%"
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$(".emoji-picker-modal").removeClass("fadeIn");
|
||||||
|
|
||||||
|
let cssAttributes = { width: 400, marginLeft: "", marginTop: "", left: "", top: "" };
|
||||||
|
if(isLargePreview) {
|
||||||
|
cssAttributes.left = (Ember.$("#reply-control").width() - Ember.$(".d-editor").width() ) / 2 + Ember.$(".d-editor-preview-wrapper").position().left;
|
||||||
|
cssAttributes.bottom = 32;
|
||||||
|
} else {
|
||||||
|
cssAttributes.left = (Ember.$("#reply-control").width() - Ember.$(".d-editor").width() ) / 2 + Ember.$(".d-editor").position().left;
|
||||||
|
cssAttributes.bottom = Ember.$("#reply-control").height() - 48;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get("$picker").css(cssAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const infoMaxWidth = this.get("$picker").width() -
|
||||||
|
this.get("$picker").find(".categories-column").width() -
|
||||||
|
this.get("$picker").find(".diversity-picker").width() -
|
||||||
|
32;
|
||||||
|
this.get("$picker").find(".info").css("max-width", infoMaxWidth);
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadVisibleEmojis($visibleEmojis) {
|
||||||
|
$.each($visibleEmojis, (_, icon) => {
|
||||||
|
const $icon = $(icon);
|
||||||
|
const code = this._codeWithDiversity($icon.parents("a").attr("title"), $icon.hasClass("diversity"))
|
||||||
|
$icon.attr("src", emojiUrlFor(code));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_codeWithDiversity(code, diversity) {
|
||||||
|
if(diversity && this.get("selectedDiversity") !== 1) {
|
||||||
|
return `${code}:t${this.get("selectedDiversity")}`;
|
||||||
|
} else {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_storeScrollPosition() {
|
||||||
|
keyValueStore.setObject({
|
||||||
|
key: EMOJI_SCROLL_Y,
|
||||||
|
value: this.get("$list").scrollTop()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_trackEmojiUsage(code) {
|
||||||
|
const recent = keyValueStore.getObject(EMOJI_USAGE) || {};
|
||||||
|
|
||||||
|
if (!recent[code]) {
|
||||||
|
// keeping title here for legacy reasons, might migrate later
|
||||||
|
recent[code] = { title: code, usage: 0 };
|
||||||
|
}
|
||||||
|
recent[code]["usage"]++;
|
||||||
|
|
||||||
|
keyValueStore.setObject({ key: EMOJI_USAGE, value: recent });
|
||||||
|
|
||||||
|
this.set("recentEmojis", _.map(recent).sort(this._sortByUsage).slice(0, PER_ROW));
|
||||||
|
|
||||||
|
this.sendAction("emojiSelected", code);
|
||||||
|
},
|
||||||
|
|
||||||
|
_sortByUsage(a, b) {
|
||||||
|
if (a.usage > b.usage) { return -1; }
|
||||||
|
if (b.usage > a.usage) { return 1; }
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
},
|
||||||
|
|
||||||
|
_scrollTo(y) {
|
||||||
|
const yPosition = _.isUndefined(y) ? keyValueStore.getObject(EMOJI_SCROLL_Y) : y;
|
||||||
|
|
||||||
|
this.get("$list").scrollTop(yPosition);
|
||||||
|
|
||||||
|
// if we don’t actually scroll we need to force it
|
||||||
|
if(yPosition === 0) {
|
||||||
|
this.get("$list").scroll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateIconSrc(icon) {
|
||||||
|
const $icon = $(icon);
|
||||||
|
const code = this._codeWithDiversity($icon.parents("a").attr("title"), true)
|
||||||
|
$icon.attr("src", emojiUrlFor(code));
|
||||||
|
},
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -1,208 +0,0 @@
|
||||||
import groups from 'discourse/lib/emoji/groups';
|
|
||||||
import KeyValueStore from "discourse/lib/key-value-store";
|
|
||||||
import { emojiList, isSkinTonableEmoji } from 'pretty-text/emoji';
|
|
||||||
import { emojiUrlFor } from 'discourse/lib/text';
|
|
||||||
import { findRawTemplate } from 'discourse/lib/raw-templates';
|
|
||||||
|
|
||||||
const keyValueStore = new KeyValueStore("discourse_emojis_");
|
|
||||||
const EMOJI_USAGE = "emojiUsage";
|
|
||||||
|
|
||||||
let PER_ROW = 12;
|
|
||||||
const PER_PAGE = 60;
|
|
||||||
|
|
||||||
let ungroupedIcons, recentlyUsedIcons;
|
|
||||||
let selectedSkinTone = keyValueStore.getObject('selectedSkinTone') || 1;
|
|
||||||
|
|
||||||
if (!keyValueStore.getObject(EMOJI_USAGE)) {
|
|
||||||
keyValueStore.setObject({key: EMOJI_USAGE, value: {}});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSelector() {
|
|
||||||
$('.emoji-modal, .emoji-modal-wrapper').remove();
|
|
||||||
$('body, textarea').off('keydown.emoji');
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeUngroupedIcons() {
|
|
||||||
const groupedIcons = {};
|
|
||||||
|
|
||||||
groups.forEach(group => {
|
|
||||||
group.icons.forEach(icon => groupedIcons[icon] = true);
|
|
||||||
});
|
|
||||||
|
|
||||||
ungroupedIcons = [];
|
|
||||||
const emojis = emojiList();
|
|
||||||
emojis.forEach(emoji => {
|
|
||||||
if (groupedIcons[emoji] !== true) {
|
|
||||||
ungroupedIcons.push(emoji);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ungroupedIcons.length) {
|
|
||||||
groups.push({name: 'ungrouped', icons: ungroupedIcons});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function trackEmojiUsage(title) {
|
|
||||||
const recent = keyValueStore.getObject(EMOJI_USAGE) || {};
|
|
||||||
|
|
||||||
if (!recent[title]) { recent[title] = { title: title, usage: 0 }; }
|
|
||||||
recent[title]["usage"]++;
|
|
||||||
|
|
||||||
keyValueStore.setObject({key: EMOJI_USAGE, value: recent});
|
|
||||||
|
|
||||||
// clear the cache
|
|
||||||
recentlyUsedIcons = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortByUsage(a, b) {
|
|
||||||
if (a.usage > b.usage) { return -1; }
|
|
||||||
if (b.usage > a.usage) { return 1; }
|
|
||||||
return a.title.localeCompare(b.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeRecentlyUsedIcons() {
|
|
||||||
recentlyUsedIcons = [];
|
|
||||||
|
|
||||||
const usage = _.map(keyValueStore.getObject(EMOJI_USAGE)).sort(sortByUsage);
|
|
||||||
const recent = usage.slice(0, PER_ROW);
|
|
||||||
|
|
||||||
if (recent.length > 0) {
|
|
||||||
|
|
||||||
recent.forEach(emoji => recentlyUsedIcons.push(emoji.title));
|
|
||||||
|
|
||||||
const recentGroup = groups.findBy('name', 'recent');
|
|
||||||
if (recentGroup) {
|
|
||||||
recentGroup.icons = recentlyUsedIcons;
|
|
||||||
} else {
|
|
||||||
groups.push({ name: 'recent', icons: recentlyUsedIcons });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toolbar(selected) {
|
|
||||||
if (!ungroupedIcons) { initializeUngroupedIcons(); }
|
|
||||||
if (!recentlyUsedIcons) { initializeRecentlyUsedIcons(); }
|
|
||||||
|
|
||||||
return groups.map((g, i) => {
|
|
||||||
let icon = g.tabicon;
|
|
||||||
let title = g.fullname;
|
|
||||||
if (g.name === "recent") {
|
|
||||||
icon = "star";
|
|
||||||
title = "Recent";
|
|
||||||
} else if (g.name === "ungrouped") {
|
|
||||||
icon = g.icons[0];
|
|
||||||
title = "Custom";
|
|
||||||
}
|
|
||||||
|
|
||||||
return { src: emojiUrlFor(icon),
|
|
||||||
title,
|
|
||||||
groupId: i,
|
|
||||||
selected: i === selected };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindEvents(page, offset, options) {
|
|
||||||
$('.emoji-page a').click(e => {
|
|
||||||
const title = $(e.currentTarget).attr('title');
|
|
||||||
trackEmojiUsage(title);
|
|
||||||
options.onSelect(title);
|
|
||||||
closeSelector();
|
|
||||||
return false;
|
|
||||||
}).hover(e => {
|
|
||||||
const title = $(e.currentTarget).attr('title');
|
|
||||||
const html = "<img src='" + emojiUrlFor(title) + "' class='emoji'> <span>:" + title + ":<span>";
|
|
||||||
$('.emoji-modal .info').html(html);
|
|
||||||
}, () => $('.emoji-modal .info').html(""));
|
|
||||||
|
|
||||||
$('.emoji-modal .nav .next a').click(() => render(page, offset+PER_PAGE, options));
|
|
||||||
$('.emoji-modal .nav .prev a').click(() => render(page, offset-PER_PAGE, options));
|
|
||||||
|
|
||||||
$('.emoji-modal .toolbar a').click(function(){
|
|
||||||
const p = parseInt($(this).data('group-id'));
|
|
||||||
render(p, 0, options);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.emoji-modal .tones-button').click(function(){
|
|
||||||
selectedSkinTone = parseInt($(this).data('skin-tone'));
|
|
||||||
keyValueStore.setObject({key: 'selectedSkinTone', value: selectedSkinTone});
|
|
||||||
render(page, offset, options);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function render(page, offset, options) {
|
|
||||||
keyValueStore.set({key: "emojiPage", value: page});
|
|
||||||
keyValueStore.set({key: "emojiOffset", value: offset});
|
|
||||||
|
|
||||||
const toolbarItems = toolbar(page);
|
|
||||||
const rows = [];
|
|
||||||
let row = [];
|
|
||||||
const icons = groups[page].icons;
|
|
||||||
const max = offset + PER_PAGE;
|
|
||||||
|
|
||||||
for(let i=offset; i<max; i++){
|
|
||||||
if(!icons[i]){ break; }
|
|
||||||
if(row.length === (options.perRow || PER_ROW)){
|
|
||||||
rows.push(row);
|
|
||||||
row = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let code = icons[i];
|
|
||||||
if(selectedSkinTone !== 1 && isSkinTonableEmoji(code)) {
|
|
||||||
code = `${code}:t${selectedSkinTone}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
row.push({src: emojiUrlFor(code), title: code});
|
|
||||||
}
|
|
||||||
rows.push(row);
|
|
||||||
|
|
||||||
const skinTones = [];
|
|
||||||
const skinToneNames = ['default', 'light', 'medium-light', 'medium', 'medium-dark', 'dark'];
|
|
||||||
for(let i=1; i<skinToneNames.length+1; i++){
|
|
||||||
skinTones.push({
|
|
||||||
selected: selectedSkinTone === i,
|
|
||||||
level: i,
|
|
||||||
className: skinToneNames[i-1]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = {
|
|
||||||
toolbarItems,
|
|
||||||
skinTones,
|
|
||||||
rows,
|
|
||||||
prevDisabled: offset === 0,
|
|
||||||
nextDisabled: (max + 1) > icons.length,
|
|
||||||
modalClass: options.modalClass
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.emoji-modal', options.appendTo).remove();
|
|
||||||
const template = findRawTemplate('emoji-toolbar');
|
|
||||||
options.appendTo.append(template(model));
|
|
||||||
|
|
||||||
bindEvents(page, offset, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSelector(options) {
|
|
||||||
options = options || {};
|
|
||||||
options.appendTo = options.appendTo || $('body');
|
|
||||||
|
|
||||||
options.appendTo.append('<div class="emoji-modal-wrapper"></div>');
|
|
||||||
$('.emoji-modal-wrapper').click(() => closeSelector());
|
|
||||||
|
|
||||||
if (Discourse.Site.currentProp('mobileView')) { PER_ROW = 9; }
|
|
||||||
const page = options.page ? _.findIndex(groups, (g) => { return g.name === options.page; })
|
|
||||||
: keyValueStore.getInt("emojiPage", 0);
|
|
||||||
const offset = keyValueStore.getInt("emojiOffset", 0);
|
|
||||||
|
|
||||||
render(page, offset, options);
|
|
||||||
|
|
||||||
$('body, textarea').on('keydown.emoji', e => {
|
|
||||||
if (e.which === 27) {
|
|
||||||
closeSelector();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { showSelector };
|
|
|
@ -33,3 +33,5 @@
|
||||||
{{plugin-outlet name="editor-preview"}}
|
{{plugin-outlet name="editor-preview"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{emoji-picker active=emojiPickerIsActive emojiSelected=(action 'emojiSelected')}}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<div class='emoji-picker'></div>
|
||||||
|
<div class='emoji-picker-modal'></div>
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{#each recentEmojis as |emoji|}}
|
||||||
|
<a title='{{emoji.code}}'>
|
||||||
|
<img src='{{emoji.src}}' class='emoji'>
|
||||||
|
</a>
|
||||||
|
{{/each}}
|
|
@ -0,0 +1,88 @@
|
||||||
|
<div class='categories-column'>
|
||||||
|
<div class='category-icon'>
|
||||||
|
<a href='#' title='recent'>
|
||||||
|
<img src='<%= Emoji.url_for("star") %>' class='emoji'>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% JSON.parse(File.read("lib/emoji/groups.json")).each.with_index do |group, group_index| %>
|
||||||
|
<div class='category-icon'>
|
||||||
|
<a href='#' title='<%= group["name"] %>'>
|
||||||
|
<img src='<%= Emoji.url_for(group["tabicon"]) %>' class='emoji'>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if !Emoji.custom.blank? %>
|
||||||
|
<div class='category-icon'>
|
||||||
|
<a href='#' title='ungrouped'>
|
||||||
|
<img src='<%= Emoji.custom.first.url %>' class='emoji'>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='main-column'>
|
||||||
|
<div class='filter'>
|
||||||
|
<input type='text' name="filter" placeholder="{{i18n 'emoji_picker.filter_placeholder'}}" autocomplete="off" autofocus/>
|
||||||
|
<button class='clear-filter'>
|
||||||
|
{{fa-icon 'times'}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='results'></div>
|
||||||
|
|
||||||
|
<div class='list'>
|
||||||
|
<div class='section' data-section='recent'>
|
||||||
|
<div class='section-header'>
|
||||||
|
<span class="title">{{i18n 'emoji_picker.recent'}}</span>
|
||||||
|
<a href='#' class='clear-recent btn btn-default'>{{fa-icon 'trash'}}</a>
|
||||||
|
</div>
|
||||||
|
<div class='section-group'></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% JSON.parse(File.read("lib/emoji/groups.json")).each.with_index do |group, group_index| %>
|
||||||
|
<div class='section' data-section='<%= group["name"] %>'>
|
||||||
|
<div class='section-header'>
|
||||||
|
<span class="title">{{i18n 'emoji_picker.<%= group["name"] %>'}}</span>
|
||||||
|
</div>
|
||||||
|
<div class='section-group'>
|
||||||
|
<% group["icons"].each.with_index do |icon, icon_index| %>
|
||||||
|
<a title='<%= icon["name"] %>'>
|
||||||
|
<% if group_index == 0 && icon_index <= 77 %>
|
||||||
|
<img src='<%= Emoji.url_for(icon['name']) %>' class='emoji <%= "diversity" if icon["diversity"] %>'>
|
||||||
|
<% else %>
|
||||||
|
<img src='' class='emoji <%= "diversity" if icon["diversity"] %>'>
|
||||||
|
<% end %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
{{#if customEmojis.length}}
|
||||||
|
<div class='section' data-section='ungrouped'>
|
||||||
|
<div class='section-header'>
|
||||||
|
<span class="title">{{i18n 'emoji_picker.custom'}}</span>
|
||||||
|
</div>
|
||||||
|
<div class='section-group'>
|
||||||
|
{{#each customEmojis as |emoji|}}
|
||||||
|
<a title='{{emoji.code}}'>
|
||||||
|
<img src='{{emoji.src}}' class='emoji'>
|
||||||
|
</a>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class='footer'>
|
||||||
|
<div class='info'></div>
|
||||||
|
<div class='diversity-picker'>
|
||||||
|
<% ['default', 'light', 'medium-light', 'medium', 'medium-dark', 'dark'].each.with_index do |diversity, index| %>
|
||||||
|
<a href='#' class='diversity-scale <%= diversity %>' data-level="<%= index + 1 %>">
|
||||||
|
{{fa-icon "check"}}
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,45 +0,0 @@
|
||||||
<div class='emoji-modal {{modalClass}}'>
|
|
||||||
<ul class='toolbar'>
|
|
||||||
{{#each toolbarItems as |item|}}<li><a title='{{item.title}}' {{#if item.selected}}class='selected'{{/if}} data-group-id='{{item.groupId}}'><img src='{{item.src}}' class='emoji'></a></li>{{/each}}
|
|
||||||
</ul>
|
|
||||||
<div class='emoji-table-wrapper'>
|
|
||||||
<table class='emoji-page'>
|
|
||||||
{{#each rows as |row|}}
|
|
||||||
<tr>
|
|
||||||
{{#each row as |item|}}
|
|
||||||
<td><a title='{{item.title}}'><img src='{{item.src}}' class='emoji'></a></td>
|
|
||||||
{{/each}}
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='footer'>
|
|
||||||
<div class='info'></div>
|
|
||||||
<div class='tones'>
|
|
||||||
{{#each skinTones as |skinTone|}}
|
|
||||||
<a href='#' class='tones-button {{skinTone.className}}' data-skin-tone="{{skinTone.level}}">
|
|
||||||
{{#if skinTone.selected}}{{fa-icon "check"}}{{/if}}
|
|
||||||
</a>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
<div class='nav'>
|
|
||||||
<span class='prev'>
|
|
||||||
{{#if prevDisabled}}
|
|
||||||
{{fa-icon "fast-backward"}}
|
|
||||||
{{else}}
|
|
||||||
<a>{{fa-icon "fast-backward"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
<span class='next'>
|
|
||||||
{{#if nextDisabled}}
|
|
||||||
{{fa-icon "fast-forward"}}
|
|
||||||
{{else}}
|
|
||||||
<a>{{fa-icon "fast-forward"}}</a>
|
|
||||||
{{/if}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='clearfix'></div>
|
|
||||||
</div>
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { emojis, aliases, translations, tonableEmojis } from 'pretty-text/emoji/data';
|
import { emojis, aliases, translations, tonableEmojis } from 'pretty-text/emoji/data';
|
||||||
|
|
||||||
// bump up this number to expire all emojis
|
// bump up this number to expire all emojis
|
||||||
export const IMAGE_VERSION = "5";
|
export const IMAGE_VERSION = "<%= Emoji::EMOJI_VERSION %>";
|
||||||
|
|
||||||
const extendedEmoji = {};
|
const extendedEmoji = {};
|
||||||
|
|
||||||
|
@ -10,10 +10,8 @@ export function registerEmoji(code, url) {
|
||||||
extendedEmoji[code] = url;
|
extendedEmoji[code] = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emojiList() {
|
export function extendedEmojiList() {
|
||||||
const result = emojis.slice(0);
|
return extendedEmoji;
|
||||||
_.each(extendedEmoji, (v,k) => result.push(k));
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojiHash = {};
|
const emojiHash = {};
|
||||||
|
@ -54,8 +52,7 @@ export function isCustomEmoji(code, opts) {
|
||||||
|
|
||||||
export function buildEmojiUrl(code, opts) {
|
export function buildEmojiUrl(code, opts) {
|
||||||
let url;
|
let url;
|
||||||
code = code.toLowerCase();
|
code = String(code).toLowerCase();
|
||||||
|
|
||||||
if (extendedEmoji.hasOwnProperty(code)) {
|
if (extendedEmoji.hasOwnProperty(code)) {
|
||||||
url = extendedEmoji[code];
|
url = extendedEmoji[code];
|
||||||
}
|
}
|
||||||
|
@ -116,10 +113,9 @@ export function emojiSearch(term, options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isSkinTonableEmoji(term) {
|
export function isSkinTonableEmoji(term) {
|
||||||
let match = term.match(/^:?(.*?):?$/);
|
const match = _.compact(term.split(":"))[0];
|
||||||
if (match) {
|
if (match) {
|
||||||
return tonableEmojis.indexOf(match[1]) !== -1;
|
return tonableEmojis.indexOf(match) !== -1;
|
||||||
} else {
|
|
||||||
return tonableEmojis.indexOf(term) !== -1;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
|
@ -1,128 +1,115 @@
|
||||||
body img.emoji {
|
.emoji-picker {
|
||||||
width: 20px;
|
background-color: #fff;
|
||||||
height: 20px;
|
border: 1px solid #e9e9e9;
|
||||||
vertical-align: middle;
|
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||||
}
|
background-clip: padding-box;
|
||||||
|
|
||||||
.wmd-emoji-button:before {
|
|
||||||
content: "\f118";
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal {
|
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
width: 445px;
|
|
||||||
min-height: 264px;
|
|
||||||
margin-top: -132px;
|
|
||||||
margin-left: -222px;
|
|
||||||
background-color: dark-light-choose(#dadada, blend-primary-secondary(5%));
|
background-color: dark-light-choose(#dadada, blend-primary-secondary(5%));
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .categories-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: white;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-right: 1px solid #e9e9e9;
|
||||||
|
min-width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .category-icon {
|
||||||
|
display: block;
|
||||||
|
margin: 4px auto;
|
||||||
|
-webkit-filter: grayscale(100%);
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .category-icon.current, .emoji-picker .category-icon:hover {
|
||||||
|
-webkit-filter: grayscale(0%);
|
||||||
|
filter: grayscale(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .main-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: white;
|
||||||
|
flex: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .list {
|
||||||
|
overflow-y: scroll;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding: 0px;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.emoji-page td {
|
.emoji-picker .list .emoji {
|
||||||
border: 1px solid transparent;
|
margin: 5px;
|
||||||
background-color: dark-light-choose(white, $secondary);
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-page a {
|
.emoji-picker .section-header {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
display: block;
|
margin-bottom: 4px;
|
||||||
border-radius: 20px;
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #e9e9e9;
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-page a:hover {
|
.emoji-picker .section-header .title {
|
||||||
background-color: dark-light-choose(rgb(210, 236, 252), rgb(45, 19, 3));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-table-wrapper {
|
.emoji-picker .section-header .clear-recent .fa{
|
||||||
min-width: 442px;
|
margin: 0;
|
||||||
min-height: 185px;
|
|
||||||
background-color: $secondary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-modal-wrapper {
|
.section-group {
|
||||||
z-index: 9999;
|
flex-wrap: wrap;
|
||||||
position: fixed;
|
display: flex;
|
||||||
left: 0;
|
align-items: center;
|
||||||
top: 0;
|
justify-content: flex-start;
|
||||||
width: 100%;
|
padding: 4px;
|
||||||
height: 100%;
|
|
||||||
opacity: dark-light-choose(0.8, 0.5);
|
|
||||||
background-color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-modal .toolbar {
|
.section-group a:hover, .results a:hover {
|
||||||
margin: 8px 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .toolbar li {
|
|
||||||
display: inline;
|
|
||||||
padding-right: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .toolbar li a {
|
|
||||||
padding: 8px;
|
|
||||||
background-color: dark-light-choose(#dadada, blend-primary-secondary(5%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .toolbar li a.selected {
|
|
||||||
background-color: $secondary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .nav span {
|
|
||||||
color: dark-light-choose(#aaa, #555);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .nav span.next {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .nav a {
|
|
||||||
color: dark-light-choose(#333, #ccc);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-shortname {
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 200px;
|
vertical-align: top;
|
||||||
text-overflow: ellipsis;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
background-color: #d1f0ff;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-modal .footer {
|
.emoji-picker .footer {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-direction: row;
|
border-top: 1px solid #e9e9e9;
|
||||||
flex-grow: 2;
|
}
|
||||||
|
|
||||||
|
.emoji-picker .info {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 232px;
|
||||||
|
padding-left: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: 700;
|
||||||
|
max-width: 125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .diversity-picker {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-modal .info {
|
.emoji-picker .diversity-picker .diversity-scale {
|
||||||
flex: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .info span {
|
|
||||||
margin-left: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: $primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .nav {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .tones {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal .tones-button {
|
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
@ -133,14 +120,88 @@ table.emoji-page td {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.emoji-modal .tones-button.default { background: #ffcc4d; }
|
.emoji-picker .diversity-picker .diversity-scale.default { background: #ffcc4d; }
|
||||||
.emoji-modal .tones-button.light { background: #f7dece; }
|
.emoji-picker .diversity-picker .diversity-scale.light { background: #f7dece; }
|
||||||
.emoji-modal .tones-button.medium-light { background: #f3d2a2; }
|
.emoji-picker .diversity-picker .diversity-scale.medium-light { background: #f3d2a2; }
|
||||||
.emoji-modal .tones-button.medium { background: #d5ab88; }
|
.emoji-picker .diversity-picker .diversity-scale.medium { background: #d5ab88; }
|
||||||
.emoji-modal .tones-button.medium-dark { background: #af7e57; }
|
.emoji-picker .diversity-picker .diversity-scale.medium-dark { background: #af7e57; }
|
||||||
.emoji-modal .tones-button.dark { background: #7c533e; }
|
.emoji-picker .diversity-picker .diversity-scale.dark { background: #7c533e; }
|
||||||
|
|
||||||
.emoji-modal .tones-button i.fa {
|
.emoji-picker .diversity-picker .diversity-scale.selected i {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .diversity-picker i {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .diversity-picker i.fa {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
text-shadow: 0.5px 1.5px 0 rgba(0,0,0,0.3);
|
text-shadow: 0.5px 1.5px 0 rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body img.emoji {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wmd-emoji-button:before {
|
||||||
|
content: "\f118";
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker-modal.fadeIn {
|
||||||
|
z-index: 9999;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: .8;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .filter {
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
border-bottom: 1px solid #e9e9e9;
|
||||||
|
padding: 5px;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .filter input {
|
||||||
|
height: 24px;
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .results {
|
||||||
|
display: none;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 4px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .results .emoji {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker .filter .clear-filter {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 12px;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
color: $primary;
|
||||||
|
outline: none;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $tertiary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
.emoji-table-wrapper {
|
|
||||||
min-width: 320px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal {
|
|
||||||
width: 340px;
|
|
||||||
margin-top: -132px;
|
|
||||||
margin-left: -170px;
|
|
||||||
background-color: dark-light-choose(#dadada, blend-primary-secondary(5%));
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Emoji
|
class Emoji
|
||||||
# update this to clear the cache
|
# update this to clear the cache
|
||||||
EMOJI_VERSION = "v5"
|
EMOJI_VERSION = "5"
|
||||||
|
|
||||||
FITZPATRICK_SCALE ||= [ "1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff" ]
|
FITZPATRICK_SCALE ||= [ "1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff" ]
|
||||||
|
|
||||||
|
@ -46,15 +46,19 @@ class Emoji
|
||||||
|
|
||||||
def self.create_from_db_item(emoji)
|
def self.create_from_db_item(emoji)
|
||||||
name = emoji["name"]
|
name = emoji["name"]
|
||||||
filename = "#{emoji['filename'] || name}.png"
|
filename = emoji['filename'] || name
|
||||||
Emoji.new.tap do |e|
|
Emoji.new.tap do |e|
|
||||||
e.name = name
|
e.name = name
|
||||||
e.url = "#{Discourse.base_uri}/images/emoji/#{SiteSetting.emoji_set}/#{filename}"
|
e.url = Emoji.url_for(filename)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.url_for(name)
|
||||||
|
"#{Discourse.base_uri}/images/emoji/#{SiteSetting.emoji_set}/#{name}.png?v=5"
|
||||||
|
end
|
||||||
|
|
||||||
def self.cache_key(name)
|
def self.cache_key(name)
|
||||||
"#{name}:#{EMOJI_VERSION}:#{Plugin::CustomEmoji.cache_key}"
|
"#{name}:v#{EMOJI_VERSION}:#{Plugin::CustomEmoji.cache_key}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.clear_cache
|
def self.clear_cache
|
||||||
|
|
|
@ -1134,6 +1134,18 @@ en:
|
||||||
ctrl: 'Ctrl'
|
ctrl: 'Ctrl'
|
||||||
alt: 'Alt'
|
alt: 'Alt'
|
||||||
|
|
||||||
|
emoji_picker:
|
||||||
|
filter_placeholder: Search an emoji
|
||||||
|
people: People
|
||||||
|
nature: Nature
|
||||||
|
food: Food
|
||||||
|
activity: Activity
|
||||||
|
travel: Travel
|
||||||
|
objects: Objects
|
||||||
|
celebration: Celebration
|
||||||
|
custom: Custom emojis
|
||||||
|
recent: Recently used emojis
|
||||||
|
|
||||||
composer:
|
composer:
|
||||||
emoji: "Emoji :)"
|
emoji: "Emoji :)"
|
||||||
more_emoji: "more..."
|
more_emoji: "more..."
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@ require "json"
|
||||||
require "nokogiri"
|
require "nokogiri"
|
||||||
require "open-uri"
|
require "open-uri"
|
||||||
|
|
||||||
EMOJI_GROUPS_PATH ||= "app/assets/javascripts/discourse/lib/emoji/groups.js.es6"
|
EMOJI_GROUPS_PATH ||= "lib/emoji/groups.json"
|
||||||
|
|
||||||
EMOJI_DB_PATH ||= "lib/emoji/db.json"
|
EMOJI_DB_PATH ||= "lib/emoji/db.json"
|
||||||
|
|
||||||
|
@ -432,7 +432,7 @@ def fix_incomplete_sets(emojis)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_emoji_groups(emojis)
|
def generate_emoji_groups(keywords)
|
||||||
puts "Generating groups..."
|
puts "Generating groups..."
|
||||||
|
|
||||||
list = open(EMOJI_ORDERING_URL).read
|
list = open(EMOJI_ORDERING_URL).read
|
||||||
|
@ -453,8 +453,8 @@ def generate_emoji_groups(emojis)
|
||||||
|
|
||||||
emoji_char = code_to_emoji(emoji_code)
|
emoji_char = code_to_emoji(emoji_code)
|
||||||
|
|
||||||
if emoji = emojis[emoji_char]
|
if emoji = keywords[emoji_char]
|
||||||
group["icons"] << emoji["name"]
|
group["icons"] << { name: emoji["name"], diversity: emoji["fitzpatrick_scale"] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -518,15 +518,7 @@ def write_js_groups(emojis, groups)
|
||||||
|
|
||||||
confirm_overwrite(EMOJI_GROUPS_PATH)
|
confirm_overwrite(EMOJI_GROUPS_PATH)
|
||||||
|
|
||||||
template = <<TEMPLATE
|
template = JSON.pretty_generate(groups)
|
||||||
// This file is generated by emoji.rake do not modify directly
|
|
||||||
|
|
||||||
// note that these categories are copied from Slack
|
|
||||||
const groups = #{JSON.pretty_generate(groups)};
|
|
||||||
|
|
||||||
export default groups;
|
|
||||||
TEMPLATE
|
|
||||||
|
|
||||||
FileUtils.mkdir_p(File.expand_path("..", EMOJI_GROUPS_PATH))
|
FileUtils.mkdir_p(File.expand_path("..", EMOJI_GROUPS_PATH))
|
||||||
File.write(EMOJI_GROUPS_PATH, template)
|
File.write(EMOJI_GROUPS_PATH, template)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
import { acceptance } from "helpers/qunit-helpers";
|
||||||
|
import { IMAGE_VERSION as v } from 'pretty-text/emoji';
|
||||||
|
import {
|
||||||
|
keyValueStore,
|
||||||
|
EMOJI_USAGE,
|
||||||
|
EMOJI_SCROLL_Y,
|
||||||
|
EMOJI_SELECTED_DIVERSITY
|
||||||
|
} from 'discourse/components/emoji-picker';
|
||||||
|
|
||||||
|
acceptance("EmojiPicker", {
|
||||||
|
loggedIn: true,
|
||||||
|
beforeEach() {
|
||||||
|
keyValueStore.setObject({ key: EMOJI_USAGE, value: {} });
|
||||||
|
keyValueStore.setObject({ key: EMOJI_SCROLL_Y, value: 0 });
|
||||||
|
keyValueStore.setObject({ key: EMOJI_SELECTED_DIVERSITY, value: 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker can be opened/closed", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
|
||||||
|
click("button.emoji.btn");
|
||||||
|
andThen(() => {
|
||||||
|
assert.notEqual(
|
||||||
|
find('.emoji-picker').html().trim(),
|
||||||
|
"",
|
||||||
|
"it opens the picker"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
click("button.emoji.btn");
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.emoji-picker').html().trim(),
|
||||||
|
"",
|
||||||
|
"it closes the picker"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emojis can be hovered to display info", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
|
||||||
|
click("button.emoji.btn");
|
||||||
|
andThen(() => {
|
||||||
|
$(".emoji-picker a[title='grinning']").trigger('mouseover');
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.emoji-picker .info').html().trim(),
|
||||||
|
`<img src=\"/images/emoji/emoji_one/grinning.png?v=${v}\" class=\"emoji\"> <span>:grinning:<span></span></span>`,
|
||||||
|
"it displays emoji info when hovering emoji"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker has sections", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
click("button.emoji.btn");
|
||||||
|
|
||||||
|
click(".emoji-picker .categories-column a[title='travel']");
|
||||||
|
andThen(() => {
|
||||||
|
assert.notEqual(
|
||||||
|
find('.emoji-picker .list').scrollTop(),
|
||||||
|
0,
|
||||||
|
"it scrolls to section"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".emoji-picker .categories-column a[title='travel']").parent().hasClass('current'),
|
||||||
|
true,
|
||||||
|
"it highlights section icon"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker triggers event when picking emoji", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
click("button.emoji.btn");
|
||||||
|
|
||||||
|
click(".emoji-picker a[title='grinning']");
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.d-editor-input').val(),
|
||||||
|
":grinning:",
|
||||||
|
"it adds the emoji code in the editor when selected"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker has a list of recently used emojis", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
click("button.emoji.btn");
|
||||||
|
click(".emoji-picker .clear-recent");
|
||||||
|
|
||||||
|
click(".emoji-picker a[title='grinning']");
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.section[data-section="recent"]').css("display"),
|
||||||
|
"block",
|
||||||
|
"it shows recent section"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find('.section[data-section="recent"] .section-group img.emoji').length,
|
||||||
|
1,
|
||||||
|
"it adds the emoji code to the recently used emojis list"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker can clear recently used emojis", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
click("button.emoji.btn");
|
||||||
|
|
||||||
|
click(".emoji-picker a[title='grinning']");
|
||||||
|
click(".emoji-picker a[title='sunglasses']");
|
||||||
|
click(".emoji-picker a[title='sunglasses']");
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.section[data-section="recent"] .section-group img.emoji').length,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
click(".emoji-picker .clear-recent");
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.section[data-section="recent"] .section-group img.emoji').length,
|
||||||
|
0,
|
||||||
|
"it has cleared recent emojis"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find('.section[data-section="recent"]').css("display"),
|
||||||
|
"none",
|
||||||
|
"it hides recent section"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find('.category-icon a[title="recent"]').parent().css("display"),
|
||||||
|
"none",
|
||||||
|
"it hides recent category icon"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker correctly orders recently used emojis", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
click("button.emoji.btn");
|
||||||
|
click(".emoji-picker .clear-recent");
|
||||||
|
|
||||||
|
click(".emoji-picker a[title='grinning']");
|
||||||
|
click(".emoji-picker a[title='sunglasses']");
|
||||||
|
click(".emoji-picker a[title='sunglasses']");
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.section[data-section="recent"] .section-group img.emoji').length,
|
||||||
|
2,
|
||||||
|
"it has multiple recent emojis"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find('.section[data-section="recent"] .section-group img.emoji').first().attr('src'),
|
||||||
|
`/images/emoji/emoji_one/sunglasses.png?v=${v}`,
|
||||||
|
"it puts the most used emoji in first"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker lazy loads emojis", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
|
||||||
|
click("button.emoji.btn");
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
const $emoji = $('.emoji-picker a[title="massage_woman"] img');
|
||||||
|
assert.equal(
|
||||||
|
$emoji.attr('src'),
|
||||||
|
"",
|
||||||
|
"it doesn't load invisible emojis"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
const done = assert.async();
|
||||||
|
setTimeout(() => {
|
||||||
|
$('.emoji-picker .list').scrollTop(2600);
|
||||||
|
setTimeout(() => {
|
||||||
|
const $emoji = $('a[title="massage_woman"] img');
|
||||||
|
assert.equal(
|
||||||
|
$emoji.attr('src'),
|
||||||
|
`/images/emoji/emoji_one/massage_woman.png?v=${v}`,
|
||||||
|
"it loads visible emojis"
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
}, 50);
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker supports diversity scale", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
click("button.emoji.btn");
|
||||||
|
|
||||||
|
click('.emoji-picker a.diversity-scale.dark');
|
||||||
|
andThen(() => {
|
||||||
|
const done = assert.async();
|
||||||
|
setTimeout(() => {
|
||||||
|
$('.emoji-picker .list').scrollTop(2900);
|
||||||
|
setTimeout(() => {
|
||||||
|
const $emoji = $('a[title="massage_woman"] img');
|
||||||
|
assert.equal(
|
||||||
|
$emoji.attr('src'),
|
||||||
|
`/images/emoji/emoji_one/massage_woman/6.png?v=${v}`,
|
||||||
|
"it applies diversity scale on emoji"
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
}, 250);
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("emoji picker persists state", assert => {
|
||||||
|
visit("/t/internationalization-localization/280");
|
||||||
|
click("#topic-footer-buttons .btn.create");
|
||||||
|
|
||||||
|
click("button.emoji.btn");
|
||||||
|
andThen(() => {
|
||||||
|
$('.emoji-picker .list').scrollTop(2600);
|
||||||
|
click('.emoji-picker a.diversity-scale.medium-dark');
|
||||||
|
});
|
||||||
|
|
||||||
|
click("button.emoji.btn");
|
||||||
|
|
||||||
|
click("button.emoji.btn");
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find('.emoji-picker .list').scrollTop() > 2500,
|
||||||
|
true,
|
||||||
|
"it stores scroll position"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find('.emoji-picker .diversity-scale.medium-dark').hasClass('selected'),
|
||||||
|
true,
|
||||||
|
"it stores diversity scale"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,11 +3,3 @@
|
||||||
.modal-backdrop {
|
.modal-backdrop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-modal-wrapper {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-modal {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue