- {{user-selector single="true" usernames=username}}
+ {{user-selector single="true" usernames=username canReceiveUpdates="true"}}
diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs
index 79dbcfc3b6..d1e8f3df18 100644
--- a/app/assets/javascripts/admin/templates/group.hbs
+++ b/app/assets/javascripts/admin/templates/group.hbs
@@ -43,10 +43,8 @@
{{/if}}
-
+
+ {{combo-box name="alias" valueAttribute="value" value=model.visibility_level content=visibilityLevelOptions}}
{{#unless model.automatic}}
diff --git a/app/assets/javascripts/discourse-common/lib/get-owner.js.es6 b/app/assets/javascripts/discourse-common/lib/get-owner.js.es6
index ada334f075..09f23c7b0d 100644
--- a/app/assets/javascripts/discourse-common/lib/get-owner.js.es6
+++ b/app/assets/javascripts/discourse-common/lib/get-owner.js.es6
@@ -15,7 +15,11 @@ export function getRegister(obj) {
const register = {
lookup: (...args) => owner.lookup(...args),
lookupFactory: (...args) => {
- return owner.lookupFactory ? owner.lookupFactory(...args) : owner._lookupFactory(...args);
+ if (owner.factoryFor) {
+ return owner.factoryFor(...args);
+ } else if (owner._lookupFactory) {
+ return owner._lookupFactory(...args);
+ }
},
deprecateContainer(target) {
diff --git a/app/assets/javascripts/discourse/components/composer-body.js.es6 b/app/assets/javascripts/discourse/components/composer-body.js.es6
index e9e4552a2b..1078c2d7ef 100644
--- a/app/assets/javascripts/discourse/components/composer-body.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-body.js.es6
@@ -3,8 +3,9 @@ import Composer from 'discourse/models/composer';
import afterTransition from 'discourse/lib/after-transition';
import positioningWorkaround from 'discourse/lib/safari-hacks';
import { headerHeight } from 'discourse/components/site-header';
+import KeyEnterEscape from 'discourse/mixins/key-enter-escape';
-export default Ember.Component.extend({
+export default Ember.Component.extend(KeyEnterEscape, {
elementId: 'reply-control',
classNameBindings: ['composer.creatingPrivateMessage:private-message',
@@ -65,17 +66,6 @@ export default Ember.Component.extend({
}, 1000);
},
- keyDown(e) {
- if (e.which === 27) {
- this.sendAction('cancelled');
- return false;
- } else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
- // CTRL+ENTER or CMD+ENTER
- this.sendAction('save');
- return false;
- }
- },
-
@observes('composeState')
disableFullscreen() {
if (this.get('composeState') !== Composer.OPEN && positioningWorkaround.blur) {
diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6
index b0213647fe..fe2ab5aae8 100644
--- a/app/assets/javascripts/discourse/components/composer-editor.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6
@@ -228,6 +228,8 @@ export default Ember.Component.extend({
_bindUploadTarget() {
this._unbindUploadTarget(); // in case it's still bound, let's clean it up first
+ this._pasted = false;
+
const $element = this.$();
const csrf = this.session.get('csrfToken');
const uploadPlaceholder = this.get('uploadPlaceholder');
@@ -238,10 +240,24 @@ export default Ember.Component.extend({
pasteZone: $element,
});
+ $element.on('fileuploadpaste', () => this._pasted = true);
+
$element.on('fileuploadsubmit', (e, data) => {
- const isUploading = validateUploadedFiles(data.files);
+ const isPrivateMessage = this.get("composer.privateMessage");
+
data.formData = { type: "composer" };
+ if (isPrivateMessage) data.formData.for_private_message = true;
+ if (this._pasted) data.formData.pasted = true;
+
+ const opts = {
+ isPrivateMessage,
+ allowStaffToUploadAnyFileInPm: this.siteSettings.allow_staff_to_upload_any_file_in_pm,
+ };
+
+ const isUploading = validateUploadedFiles(data.files, opts);
+
this.setProperties({ uploadProgress: 0, isUploading });
+
return isUploading;
});
@@ -250,6 +266,7 @@ export default Ember.Component.extend({
});
$element.on("fileuploadsend", (e, data) => {
+ this._pasted = false;
this._validUploads++;
this.appEvents.trigger('composer:insert-text', uploadPlaceholder);
diff --git a/app/assets/javascripts/discourse/components/cook-text.js.es6 b/app/assets/javascripts/discourse/components/cook-text.js.es6
new file mode 100644
index 0000000000..80ed693563
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/cook-text.js.es6
@@ -0,0 +1,15 @@
+import { cookAsync } from 'discourse/lib/text';
+
+const CookText = Ember.Component.extend({
+ tagName: '',
+ cooked: null,
+
+ didReceiveAttrs() {
+ this._super(...arguments);
+ cookAsync(this.get('rawText')).then(cooked => this.set('cooked', cooked));
+ }
+});
+
+CookText.reopenClass({ positionalParams: ['rawText'] });
+
+export default CookText;
diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6
index 9307e4cffb..289d5ee4f1 100644
--- a/app/assets/javascripts/discourse/components/d-editor.js.es6
+++ b/app/assets/javascripts/discourse/components/d-editor.js.es6
@@ -6,9 +6,9 @@ import { categoryHashtagTriggerRule } from 'discourse/lib/category-hashtags';
import { TAG_HASHTAG_POSTFIX } from 'discourse/lib/tag-hashtags';
import { search as searchCategoryTag } from 'discourse/lib/category-tag-search';
import { SEPARATOR } from 'discourse/lib/category-hashtags';
-import { cook } from 'discourse/lib/text';
+import { cookAsync } from 'discourse/lib/text';
import { translations } from 'pretty-text/emoji/data';
-import { emojiSearch } from 'pretty-text/emoji';
+import { emojiSearch, isSkinTonableEmoji } from 'pretty-text/emoji';
import { emojiUrlFor } from 'discourse/lib/text';
import { getRegister } from 'discourse-common/lib/get-owner';
import { findRawTemplate } from 'discourse/lib/raw-templates';
@@ -78,7 +78,7 @@ class Toolbar {
group: 'insertions',
icon: 'quote-right',
shortcut: 'Shift+9',
- perform: e => e.applySurround('> ', '', 'code_text')
+ perform: e => e.applyList('> ', 'blockquote_text')
});
this.addButton({id: 'code', group: 'insertions', shortcut: 'Shift+C', action: 'formatCode'});
@@ -138,7 +138,7 @@ class Toolbar {
label: button.label,
icon: button.label ? null : button.icon || button.id,
action: button.action || 'toolbarButton',
- perform: button.perform || Ember.K,
+ perform: button.perform || function() { },
trimLeading: button.trimLeading
};
@@ -247,6 +247,7 @@ export default Ember.Component.extend({
});
if (this.get('composerEvents')) {
+ this.appEvents.on('composer:insert-block', text => this._addBlock(this._getSelected(), text));
this.appEvents.on('composer:insert-text', text => this._addText(this._getSelected(), text));
this.appEvents.on('composer:replace-text', (oldVal, newVal) => this._replaceText(oldVal, newVal));
}
@@ -279,14 +280,14 @@ export default Ember.Component.extend({
const value = this.get('value');
const markdownOptions = this.get('markdownOptions') || {};
- markdownOptions.siteSettings = this.siteSettings;
-
- this.set('preview', cook(value));
- Ember.run.scheduleOnce('afterRender', () => {
- if (this._state !== "inDOM") { return; }
- const $preview = this.$('.d-editor-preview');
- if ($preview.length === 0) return;
- this.sendAction('previewUpdated', $preview);
+ cookAsync(value, markdownOptions).then(cooked => {
+ this.set('preview', cooked);
+ Ember.run.scheduleOnce('afterRender', () => {
+ if (this._state !== "inDOM") { return; }
+ const $preview = this.$('.d-editor-preview');
+ if ($preview.length === 0) return;
+ this.sendAction('previewUpdated', $preview);
+ });
});
},
@@ -337,6 +338,10 @@ export default Ember.Component.extend({
self.set('value', text);
},
+ onKeyUp(text, cp) {
+ return text.substring(0, cp).match(/(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/g);
+ },
+
transformComplete(v) {
if (v.code) {
return `${v.code}:`;
@@ -372,6 +377,20 @@ export default Ember.Component.extend({
return resolve([translations[full]]);
}
+ const match = term.match(/^:?(.*?):t(\d)?$/);
+ if (match) {
+ let name = match[1];
+ let scale = match[2];
+
+ if (isSkinTonableEmoji(name)) {
+ if (scale) {
+ return resolve([`${name}:t${scale}`]);
+ } else {
+ return resolve([2, 3, 4, 5, 6].map(x => `${name}:t${x}`));
+ }
+ }
+ }
+
const options = emojiSearch(term, {maxResults: 5});
return resolve(options);
@@ -553,6 +572,36 @@ export default Ember.Component.extend({
this._selectText(newSelection.start, newSelection.end - newSelection.start);
},
+ _addBlock(sel, text) {
+ text = (text || '').trim();
+ if (text.length === 0) {
+ return;
+ }
+
+ let pre = sel.pre;
+ let post = sel.value + sel.post;
+
+ if (pre.length > 0) {
+ pre = pre.replace(/\n*$/, "\n\n");
+ }
+
+ if (post.length > 0) {
+ post = post.replace(/^\n*/, "\n\n");
+ }
+
+
+ const value = pre + text + post;
+ const $textarea = this.$('textarea.d-editor-input');
+
+ this.set('value', value);
+
+ $textarea.val(value);
+ $textarea.prop("selectionStart", (pre+text).length + 2);
+ $textarea.prop("selectionEnd", (pre+text).length + 2);
+
+ Ember.run.scheduleOnce("afterRender", () => $textarea.focus());
+ },
+
_addText(sel, text) {
const $textarea = this.$('textarea.d-editor-input');
const insert = `${sel.pre}${text}`;
diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6
index d2667dd863..709e1e5149 100644
--- a/app/assets/javascripts/discourse/components/date-picker.js.es6
+++ b/app/assets/javascripts/discourse/components/date-picker.js.es6
@@ -2,7 +2,7 @@
import loadScript from "discourse/lib/load-script";
import { default as computed, on } from "ember-addons/ember-computed-decorators";
-export default Em.Component.extend({
+export default Ember.Component.extend({
classNames: ["date-picker-wrapper"],
_picker: null,
diff --git a/app/assets/javascripts/discourse/components/expand-post.js.es6 b/app/assets/javascripts/discourse/components/expand-post.js.es6
new file mode 100644
index 0000000000..957750e92f
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/expand-post.js.es6
@@ -0,0 +1,19 @@
+import { ajax } from 'discourse/lib/ajax';
+
+export default Ember.Component.extend({
+ tagName: '',
+
+ actions: {
+ expandItem() {
+ const item = this.get('item');
+ const topicId = item.get('topic_id');
+ const postNumber = item.get('post_number');
+
+ return ajax(`/posts/by_number/${topicId}/${postNumber}.json`).then(result => {
+ item.set('truncated', false);
+ item.set('excerpt', result.cooked);
+ });
+ }
+ }
+});
+
diff --git a/app/assets/javascripts/discourse/components/group-membership-button.js.es6 b/app/assets/javascripts/discourse/components/group-membership-button.js.es6
index 5c7449532d..b27ce66f79 100644
--- a/app/assets/javascripts/discourse/components/group-membership-button.js.es6
+++ b/app/assets/javascripts/discourse/components/group-membership-button.js.es6
@@ -1,8 +1,10 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';
-import Group from 'discourse/models/group';
+import DiscourseURL from 'discourse/lib/url';
export default Ember.Component.extend({
+ loading: false,
+
@computed("model.public")
canJoinGroup(publicGroup) {
return publicGroup;
@@ -17,22 +19,6 @@ export default Ember.Component.extend({
}
},
- @computed
- disableRequestMembership() {
- if (this.currentUser) {
- return this.currentUser.trust_level < this.siteSettings.min_trust_to_send_messages;
- } else {
- return false;
- }
- },
-
- @computed("disableRequestMembership")
- requestMembershipButtonTitle(disableRequestMembership) {
- if (disableRequestMembership) {
- return "groups.request_membership_pm.disabled";
- }
- },
-
_showLoginModal() {
this.sendAction('showLogin');
$.cookie('destination_url', window.location.href);
@@ -67,13 +53,12 @@ export default Ember.Component.extend({
requestMembership() {
if (this.currentUser) {
- const groupName = this.get('model.name');
+ this.set('loading', true);
- Group.loadOwners(groupName).then(result => {
- const names = result.map(owner => owner.username).join(",");
- const title = I18n.t('groups.request_membership_pm.title');
- const body = I18n.t('groups.request_membership_pm.body', { groupName });
- this.sendAction("createNewMessageViaParams", names, title, body);
+ this.get('model').requestMembership().then(result => {
+ DiscourseURL.routeTo(result.relative_url);
+ }).catch(popupAjaxError).finally(() => {
+ this.set('loading', false);
});
} else {
this._showLoginModal();
diff --git a/app/assets/javascripts/discourse/components/group-post-stream.js.es6 b/app/assets/javascripts/discourse/components/group-post-stream.js.es6
deleted file mode 100644
index fc83f2dbb6..0000000000
--- a/app/assets/javascripts/discourse/components/group-post-stream.js.es6
+++ /dev/null
@@ -1,8 +0,0 @@
-export default Ember.Component.extend({
- actions: {
- // TODO: When on Ember 1.13, use a closure action
- loadMore() {
- this.sendAction('loadMore');
- }
- }
-});
diff --git a/app/assets/javascripts/discourse/components/mount-widget.js.es6 b/app/assets/javascripts/discourse/components/mount-widget.js.es6
index 2153d3553a..ed327cf8c3 100644
--- a/app/assets/javascripts/discourse/components/mount-widget.js.es6
+++ b/app/assets/javascripts/discourse/components/mount-widget.js.es6
@@ -1,8 +1,8 @@
-import { keyDirty } from 'discourse/widgets/widget';
import { diff, patch } from 'virtual-dom';
import { WidgetClickHook } from 'discourse/widgets/hooks';
-import { renderedKey, queryRegistry } from 'discourse/widgets/widget';
+import { queryRegistry } from 'discourse/widgets/widget';
import { getRegister } from 'discourse-common/lib/get-owner';
+import DirtyKeys from 'discourse/lib/dirty-keys';
const _cleanCallbacks = {};
export function addWidgetCleanCallback(widgetName, fn) {
@@ -18,6 +18,7 @@ export default Ember.Component.extend({
_renderCallback: null,
_childEvents: null,
_dispatched: null,
+ dirtyKeys: null,
init() {
this._super();
@@ -34,6 +35,7 @@ export default Ember.Component.extend({
this._childEvents = [];
this._connected = [];
this._dispatched = [];
+ this.dirtyKeys = new DirtyKeys(name);
},
didInsertElement() {
@@ -73,7 +75,7 @@ export default Ember.Component.extend({
eventDispatched(eventName, key, refreshArg) {
const onRefresh = Ember.String.camelize(eventName.replace(/:/, '-'));
- keyDirty(key, { onRefresh, refreshArg });
+ this.dirtyKeys.keyDirty(key, { onRefresh, refreshArg });
this.queueRerender();
},
@@ -104,7 +106,10 @@ export default Ember.Component.extend({
const t0 = new Date().getTime();
const args = this.get('args') || this.buildArgs();
- const opts = { model: this.get('model') };
+ const opts = {
+ model: this.get('model'),
+ dirtyKeys: this.dirtyKeys,
+ };
const newTree = new this._widgetClass(args, this.register, opts);
newTree._rerenderable = this;
@@ -122,8 +127,8 @@ export default Ember.Component.extend({
this._renderCallback = null;
}
this.afterRender();
+ this.dirtyKeys.renderedKey('*');
- Ember.run.scheduleOnce('afterRender', () => renderedKey('*'));
if (this.profileWidget) {
console.log(new Date().getTime() - t0);
}
diff --git a/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6 b/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
index 994626ae8c..87e59f60ba 100644
--- a/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
+++ b/app/assets/javascripts/discourse/components/scrolling-post-stream.js.es6
@@ -1,5 +1,4 @@
import DiscourseURL from 'discourse/lib/url';
-import { keyDirty } from 'discourse/widgets/widget';
import MountWidget from 'discourse/components/mount-widget';
import { cloak, uncloak } from 'discourse/widgets/post-stream';
import { isWorkaroundActive } from 'discourse/lib/safari-hacks';
@@ -245,13 +244,13 @@ export default MountWidget.extend({
this.appEvents.on('post-stream:refresh', args => {
if (args) {
if (args.id) {
- keyDirty(`post-${args.id}`);
+ this.dirtyKeys.keyDirty(`post-${args.id}`);
if (args.refreshLikes) {
- keyDirty(`post-menu-${args.id}`, { onRefresh: 'refreshLikes' });
+ this.dirtyKeys.keyDirty(`post-menu-${args.id}`, { onRefresh: 'refreshLikes' });
}
} else if (args.force) {
- keyDirty(`*`);
+ this.dirtyKeys.forceAll();
}
}
this.queueRerender();
diff --git a/app/assets/javascripts/discourse/components/search-advanced-options.js.es6 b/app/assets/javascripts/discourse/components/search-advanced-options.js.es6
index 0bc886a817..356a2b688e 100644
--- a/app/assets/javascripts/discourse/components/search-advanced-options.js.es6
+++ b/app/assets/javascripts/discourse/components/search-advanced-options.js.es6
@@ -77,7 +77,8 @@ export default Em.Component.extend({
likes: false,
private: false,
seen: false
- }
+ },
+ all_tags: false
},
status: '',
min_post_count: '',
@@ -230,13 +231,15 @@ export default Em.Component.extend({
const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tags = this.get('searchedTerms.tags');
+ const contain_all_tags = this.get('searchedTerms.special.all_tags');
if (match.length !== 0) {
- const existingInput = _.isArray(tags) ? tags.join(',') : tags;
+ const join_char = contain_all_tags ? '+' : ',';
+ const existingInput = _.isArray(tags) ? tags.join(join_char) : tags;
const userInput = match[0].replace(REGEXP_TAGS_REPLACE, '');
if (existingInput !== userInput) {
- this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(',') : []);
+ this.set('searchedTerms.tags', (userInput.length !== 0) ? userInput.split(join_char) : []);
}
} else if (tags.length !== 0) {
this.set('searchedTerms.tags', []);
@@ -365,14 +368,16 @@ export default Em.Component.extend({
}
},
- @observes('searchedTerms.tags')
+ @observes('searchedTerms.tags', 'searchedTerms.special.all_tags')
updateSearchTermForTags() {
const match = this.filterBlocks(REGEXP_TAGS_PREFIX);
const tagFilter = this.get('searchedTerms.tags');
let searchTerm = this.get('searchTerm') || '';
+ const contain_all_tags = this.get('searchedTerms.special.all_tags');
if (tagFilter && tagFilter.length !== 0) {
- const tags = tagFilter.join(',');
+ const join_char = contain_all_tags ? '+' : ',';
+ const tags = tagFilter.join(join_char);
if (match.length !== 0) {
searchTerm = searchTerm.replace(match[0], `tags:${tags}`);
diff --git a/app/assets/javascripts/discourse/components/stream-item.js.es6 b/app/assets/javascripts/discourse/components/stream-item.js.es6
index d81366bec7..26d1e6f8e5 100644
--- a/app/assets/javascripts/discourse/components/stream-item.js.es6
+++ b/app/assets/javascripts/discourse/components/stream-item.js.es6
@@ -2,13 +2,7 @@ import { propertyEqual } from 'discourse/lib/computed';
import { actionDescription } from "discourse/components/small-action";
export default Ember.Component.extend({
- classNameBindings: [":item", "item.hidden", "item.deleted", "moderatorAction"],
+ classNameBindings: [":item", "item.hidden", "item.deleted:deleted", "moderatorAction"],
moderatorAction: propertyEqual("item.post_type", "site.post_types.moderator_action"),
actionDescription: actionDescription("item.action_code", "item.created_at", "item.username"),
-
- actions: {
- removeBookmark(userAction) {
- this.sendAction("removeBookmark", userAction);
- }
- }
});
diff --git a/app/assets/javascripts/discourse/components/topic-admin-menu-button.js.es6 b/app/assets/javascripts/discourse/components/topic-admin-menu-button.js.es6
index cb76f7997d..fb6d7bf59a 100644
--- a/app/assets/javascripts/discourse/components/topic-admin-menu-button.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-admin-menu-button.js.es6
@@ -1,10 +1,11 @@
import MountWidget from 'discourse/components/mount-widget';
export default MountWidget.extend({
+ classNames: 'topic-admin-menu-button-container',
tagName: 'span',
widget: "topic-admin-menu-button",
buildArgs() {
- return this.getProperties('topic', 'fixed', 'openUpwards');
+ return this.getProperties('topic', 'fixed', 'openUpwards', 'rightSide');
}
});
diff --git a/app/assets/javascripts/discourse/components/topic-entrance.js.es6 b/app/assets/javascripts/discourse/components/topic-entrance.js.es6
index 6e4ba25d14..d72be633d2 100644
--- a/app/assets/javascripts/discourse/components/topic-entrance.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-entrance.js.es6
@@ -92,17 +92,18 @@ export default Ember.Component.extend(CleansUp, {
this.appEvents.off('topic-entrance:show');
},
+ _jumpTo(destination) {
+ this.cleanUp();
+ DiscourseURL.routeTo(destination);
+ },
+
actions: {
enterTop() {
- const topic = this.get('topic');
- this.appEvents.trigger('header:update-topic', topic);
- DiscourseURL.routeTo(topic.get('url'));
+ this._jumpTo(this.get('topic.url'));
},
enterBottom() {
- const topic = this.get('topic');
- this.appEvents.trigger('header:update-topic', topic);
- DiscourseURL.routeTo(topic.get('lastPostUrl'));
+ this._jumpTo(this.get('topic.lastPostUrl'));
}
}
});
diff --git a/app/assets/javascripts/discourse/components/topic-progress.js.es6 b/app/assets/javascripts/discourse/components/topic-progress.js.es6
index 9e48d70c2f..a3f09e3182 100644
--- a/app/assets/javascripts/discourse/components/topic-progress.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-progress.js.es6
@@ -20,7 +20,7 @@ export default Ember.Component.extend({
@computed('postStream.loaded', 'topic.currentPost', 'postStream.filteredPostsCount')
hideProgress(loaded, currentPost, filteredPostsCount) {
- return (!loaded) || (!currentPost) || (filteredPostsCount < 2);
+ return (!loaded) || (!currentPost) || (!this.site.mobileView && filteredPostsCount < 2);
},
@computed('postStream.filteredPostsCount')
@@ -52,8 +52,14 @@ export default Ember.Component.extend({
},
_topicScrolled(event) {
- this.set('progressPosition', event.postIndex);
- this._streamPercentage = event.percent;
+ if (this.get('docked')) {
+ this.set('progressPosition', this.get('postStream.filteredPostsCount'));
+ this._streamPercentage = 1.0;
+ } else {
+ this.set('progressPosition', event.postIndex);
+ this._streamPercentage = event.percent;
+ }
+
this._updateBar();
},
@@ -110,11 +116,10 @@ export default Ember.Component.extend({
},
_dock() {
- const maximumOffset = $('#topic-footer-buttons').offset(),
+ const maximumOffset = $('#topic-bottom').offset(),
composerHeight = $('#reply-control').height() || 0,
$topicProgressWrapper = this.$(),
- offset = window.pageYOffset || $('html').scrollTop(),
- topicProgressHeight = $('#topic-progress').height();
+ offset = window.pageYOffset || $('html').scrollTop();
if (!$topicProgressWrapper || $topicProgressWrapper.length === 0) {
return;
@@ -124,7 +129,13 @@ export default Ember.Component.extend({
if (maximumOffset) {
const threshold = maximumOffset.top;
const windowHeight = $(window).height();
- isDocked = offset >= threshold - windowHeight + topicProgressHeight + composerHeight;
+ const headerHeight = $('header').outerHeight(true);
+
+ if (this.capabilities.isIOS) {
+ isDocked = offset >= (threshold - windowHeight - headerHeight + composerHeight);
+ } else {
+ isDocked = offset >= (threshold - windowHeight + composerHeight);
+ }
}
const dockPos = $(document).height() - $('#topic-bottom').offset().top;
diff --git a/app/assets/javascripts/discourse/components/topic-title.js.es6 b/app/assets/javascripts/discourse/components/topic-title.js.es6
new file mode 100644
index 0000000000..05c6c37b69
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/topic-title.js.es6
@@ -0,0 +1,5 @@
+import KeyEnterEscape from 'discourse/mixins/key-enter-escape';
+
+export default Ember.Component.extend(KeyEnterEscape, {
+ elementId: 'topic-title',
+});
diff --git a/app/assets/javascripts/discourse/components/user-stream.js.es6 b/app/assets/javascripts/discourse/components/user-stream.js.es6
index c9871ecadb..0b37142ff6 100644
--- a/app/assets/javascripts/discourse/components/user-stream.js.es6
+++ b/app/assets/javascripts/discourse/components/user-stream.js.es6
@@ -1,6 +1,7 @@
import LoadMore from "discourse/mixins/load-more";
import ClickTrack from 'discourse/lib/click-track';
import { selectedText } from 'discourse/lib/utilities';
+import Post from 'discourse/models/post';
export default Ember.Component.extend(LoadMore, {
loading: false,
@@ -44,6 +45,13 @@ export default Ember.Component.extend(LoadMore, {
}.on('willDestroyElement'),
actions: {
+ removeBookmark(userAction) {
+ const stream = this.get('stream');
+ Post.updateBookmark(userAction.get("post_id"), false).then(() => {
+ stream.remove(userAction);
+ });
+ },
+
loadMore() {
if (this.get('loading')) { return; }
diff --git a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6 b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6
index fb15918640..4311368186 100644
--- a/app/assets/javascripts/discourse/controllers/forgot-password.js.es6
+++ b/app/assets/javascripts/discourse/controllers/forgot-password.js.es6
@@ -5,6 +5,8 @@ import { extractError } from 'discourse/lib/ajax-error';
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend(ModalFunctionality, {
+ offerHelp: null,
+ helpSeen: false,
@computed('accountEmailOrUsername', 'disabled')
submitDisabled(accountEmailOrUsername, disabled) {
@@ -35,8 +37,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
if (data.user_found === true) {
key += '_found';
this.set('accountEmailOrUsername', '');
- bootbox.alert(I18n.t(key, {email: escaped, username: escaped}));
- this.send("closeModal");
+ this.set('offerHelp', I18n.t(key, {email: escaped, username: escaped}));
} else {
if (data.user_found === false) {
key += '_not_found';
@@ -52,6 +53,14 @@ export default Ember.Controller.extend(ModalFunctionality, {
});
return false;
+ },
+
+ ok() {
+ this.send('closeModal');
+ },
+
+ help() {
+ this.setProperties({ offerHelp: I18n.t('forgot_password.help'), helpSeen: true });
}
}
diff --git a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
index 8f9a0edce0..d85760f632 100644
--- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
+++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
@@ -46,6 +46,14 @@ export default Ember.Controller.extend({
return Em.isEmpty(q);
},
+
+ @computed('q')
+ highlightQuery(q) {
+ if (!q) { return; }
+ // remove l which can be used for sorting
+ return _.reject(q.split(/\s+/), t => t === 'l').join(' ');
+ },
+
@computed('skip_context', 'context')
searchContextEnabled: {
get(skip,context){
diff --git a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6
index ac84cf1968..ef7d23eba7 100644
--- a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6
+++ b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6
@@ -4,6 +4,9 @@ const _buttons = [];
const alwaysTrue = () => true;
+function identity() {
+}
+
function addBulkButton(action, key, opts) {
opts = opts || {};
@@ -72,7 +75,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
this.perform(operation).then(topics => {
if (topics) {
topics.forEach(cb);
- (this.get('refreshClosure') || Ember.k)();
+ (this.get('refreshClosure') || identity)();
this.send('closeModal');
}
});
@@ -80,7 +83,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
performAndRefresh(operation) {
return this.perform(operation).then(() => {
- (this.get('refreshClosure') || Ember.k)();
+ (this.get('refreshClosure') || identity)();
this.send('closeModal');
});
},
@@ -145,7 +148,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
this.perform({type: 'change_category', category_id: categoryId}).then(topics => {
topics.forEach(t => t.set('category', category));
- (this.get('refreshClosure') || Ember.k)();
+ (this.get('refreshClosure') || identity)();
this.send('closeModal');
});
},
diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6
index 8d36ea0e75..f015c94a02 100644
--- a/app/assets/javascripts/discourse/controllers/topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/topic.js.es6
@@ -196,7 +196,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
const quotedText = Quote.build(post, buffer);
composerOpts.quote = quotedText;
if (composer.get('model.viewOpen')) {
- this.appEvents.trigger('composer:insert-text', quotedText);
+ this.appEvents.trigger('composer:insert-block', quotedText);
} else if (composer.get('model.viewDraft')) {
const model = composer.get('model');
model.set('reply', model.get('reply') + quotedText);
@@ -320,7 +320,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
composerController.get('content.action') === Composer.REPLY) {
composerController.set('content.post', post);
composerController.set('content.composeState', Composer.OPEN);
- this.appEvents.trigger('composer:insert-text', quotedText.trim());
+ this.appEvents.trigger('composer:insert-block', quotedText.trim());
} else {
const opts = {
diff --git a/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6 b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
index 6e9f48296d..d78b671081 100644
--- a/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
@@ -12,6 +12,7 @@ export default Ember.Controller.extend({
canLoadMore: true,
invitesLoading: false,
reinvitedAll: false,
+ rescindedAll: false,
init: function() {
this._super();
@@ -32,7 +33,7 @@ export default Ember.Controller.extend({
inviteRedeemed: Em.computed.equal('filter', 'redeemed'),
- showReinviteAllButton: function() {
+ showBulkActionButtons: function() {
return (this.get('filter') === "pending" && this.get('model').invites.length > 4 && this.currentUser.get('staff'));
}.property('filter'),
@@ -86,17 +87,27 @@ export default Ember.Controller.extend({
return false;
},
+ rescindAll() {
+ bootbox.confirm(I18n.t("user.invited.rescind_all_confirm"), confirm => {
+ if (confirm) {
+ Invite.rescindAll().then(() => {
+ this.set('rescindedAll', true);
+ this.get('model.invites').clear();
+ }).catch(popupAjaxError);
+ }
+ });
+ },
+
reinvite(invite) {
invite.reinvite();
return false;
},
reinviteAll() {
- const self = this;
bootbox.confirm(I18n.t("user.invited.reinvite_all_confirm"), confirm => {
if (confirm) {
- Invite.reinviteAll().then(function() {
- self.set('reinvitedAll', true);
+ Invite.reinviteAll().then(() => {
+ this.set('reinvitedAll', true);
}).catch(popupAjaxError);
}
});
diff --git a/app/assets/javascripts/discourse/helpers/cook-text.js.es6 b/app/assets/javascripts/discourse/helpers/cook-text.js.es6
deleted file mode 100644
index 7864f01af8..0000000000
--- a/app/assets/javascripts/discourse/helpers/cook-text.js.es6
+++ /dev/null
@@ -1,4 +0,0 @@
-import { cook } from 'discourse/lib/text';
-import { registerUnbound } from 'discourse-common/lib/helpers';
-
-registerUnbound('cook-text', cook);
diff --git a/app/assets/javascripts/discourse/initializers/inject-objects.js.es6 b/app/assets/javascripts/discourse/initializers/inject-objects.js.es6
index f15f57fa8a..1ad38a8fb1 100644
--- a/app/assets/javascripts/discourse/initializers/inject-objects.js.es6
+++ b/app/assets/javascripts/discourse/initializers/inject-objects.js.es6
@@ -2,5 +2,5 @@
export default {
name: "inject-objects",
- initialize: Ember.K
+ initialize() { }
};
diff --git a/app/assets/javascripts/discourse/initializers/register-discourse-location.js.es6 b/app/assets/javascripts/discourse/initializers/register-discourse-location.js.es6
index 1f073e95cf..88c49402a8 100644
--- a/app/assets/javascripts/discourse/initializers/register-discourse-location.js.es6
+++ b/app/assets/javascripts/discourse/initializers/register-discourse-location.js.es6
@@ -2,5 +2,5 @@
export default {
name: "register-discourse-location",
- initialize: Ember.K
+ initialize() { }
};
diff --git a/app/assets/javascripts/discourse/lib/autocomplete.js.es6 b/app/assets/javascripts/discourse/lib/autocomplete.js.es6
index 4b49fdc014..4ce4adb6ee 100644
--- a/app/assets/javascripts/discourse/lib/autocomplete.js.es6
+++ b/app/assets/javascripts/discourse/lib/autocomplete.js.es6
@@ -358,10 +358,22 @@ export default function(options) {
$(this).on('keyup.autocomplete', function(e) {
if ([keys.esc, keys.enter].indexOf(e.which) !== -1) return true;
- var cp = caretPosition(me[0]);
+ let cp = caretPosition(me[0]);
+ const key = me[0].value[cp-1];
- if (options.key && completeStart === null && cp > 0) {
- var key = me[0].value[cp-1];
+ if (options.key) {
+ if (options.onKeyUp && key !== options.key) {
+ let match = options.onKeyUp(me.val(), cp);
+ if (match) {
+ completeStart = cp - match[0].length;
+ completeEnd = completeStart + match[0].length - 1;
+ let term = match[0].substring(1, match[0].length);
+ updateAutoComplete(dataSource(term, options));
+ }
+ }
+ }
+
+ if (completeStart === null && cp > 0) {
if (key === options.key) {
var prevChar = me.val().charAt(cp-2);
if (checkTriggerRule() && (!prevChar || allowedLettersRegex.test(prevChar))) {
@@ -370,7 +382,7 @@ export default function(options) {
}
}
} else if (completeStart !== null) {
- var term = me.val().substring(completeStart + (options.key ? 1 : 0), cp);
+ let term = me.val().substring(completeStart + (options.key ? 1 : 0), cp);
updateAutoComplete(dataSource(term, options));
}
});
diff --git a/app/assets/javascripts/discourse/lib/dirty-keys.js.es6 b/app/assets/javascripts/discourse/lib/dirty-keys.js.es6
new file mode 100644
index 0000000000..924e8fd2fb
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/dirty-keys.js.es6
@@ -0,0 +1,32 @@
+export default class DirtyKeys {
+ constructor(name) {
+ this.name = name;
+ this._keys = {};
+ }
+
+ keyDirty(key, options) {
+ options = options || {};
+ options.dirty = true;
+ this._keys[key] = options;
+ }
+
+ forceAll() {
+ this.keyDirty('*');
+ }
+
+ allDirty() {
+ return !!this._keys['*'];
+ }
+
+ optionsFor(key) {
+ return this._keys[key] || { dirty: false };
+ }
+
+ renderedKey(key) {
+ if (key === '*') {
+ this._keys = {};
+ } else {
+ delete this._keys[key];
+ }
+ }
+}
diff --git a/app/assets/javascripts/discourse/lib/emoji/groups.js.es6 b/app/assets/javascripts/discourse/lib/emoji/groups.js.es6
index 7b8d9bbedf..0d33852b2e 100644
--- a/app/assets/javascripts/discourse/lib/emoji/groups.js.es6
+++ b/app/assets/javascripts/discourse/lib/emoji/groups.js.es6
@@ -1,358 +1,457 @@
-import { emojiExists } from 'pretty-text/emoji';
+// This file is generated by emoji.rake do not modify directly
// note that these categories are copied from Slack
-// be careful, there are ~20 differences in synonyms, e.g. :boom: vs. :collision:
-// a few Emoji are actually missing from the Slack categories as well (?), and were added
const groups = [
{
- name:"people",
- fullname:"People",
- tabicon:"grinning",
- icons:[
- "slight_smile",
+ "name": "people",
+ "fullname": "People",
+ "tabicon": "grinning",
+ "icons": [
"grinning",
"grin",
"joy",
+ "rofl",
"smiley",
"smile",
"sweat_smile",
"laughing",
- "innocent",
- "smiling_imp",
- "imp",
"wink",
"blush",
- "relaxed",
"yum",
- "relieved",
- "heart_eyes",
"sunglasses",
- "smirk",
+ "heart_eyes",
+ "kissing_heart",
+ "kissing",
+ "kissing_smiling_eyes",
+ "kissing_closed_eyes",
+ "relaxed",
+ "slightly_smiling_face",
+ "hugs",
+ "star_struck",
+ "thinking",
+ "face_with_raised_eyebrow",
"neutral_face",
"expressionless",
+ "no_mouth",
+ "roll_eyes",
+ "smirk",
+ "persevere",
+ "disappointed_relieved",
+ "open_mouth",
+ "zipper_mouth_face",
+ "hushed",
+ "sleepy",
+ "tired_face",
+ "sleeping",
+ "relieved",
+ "stuck_out_tongue",
+ "stuck_out_tongue_winking_eye",
+ "stuck_out_tongue_closed_eyes",
+ "drooling_face",
"unamused",
"sweat",
"pensive",
"confused",
+ "upside_down_face",
+ "money_mouth_face",
+ "astonished",
+ "frowning_face",
+ "slightly_frowning_face",
"confounded",
- "kissing",
- "kissing_heart",
- "kissing_smiling_eyes",
- "kissing_closed_eyes",
- "stuck_out_tongue",
- "stuck_out_tongue_winking_eye",
- "stuck_out_tongue_closed_eyes",
"disappointed",
"worried",
- "angry",
- "rage",
- "cry",
- "persevere",
"triumph",
- "disappointed_relieved",
+ "cry",
+ "sob",
"frowning",
"anguished",
"fearful",
"weary",
- "sleepy",
- "tired_face",
+ "exploding_head",
"grimacing",
- "sob",
- "open_mouth",
- "hushed",
"cold_sweat",
"scream",
- "astonished",
"flushed",
- "sleeping",
+ "crazy_face",
"dizzy_face",
- "no_mouth",
+ "rage",
+ "angry",
+ "face_with_symbols_over_mouth",
"mask",
+ "face_with_thermometer",
+ "face_with_head_bandage",
+ "nauseated_face",
+ "face_vomiting",
+ "sneezing_face",
+ "innocent",
+ "cowboy_hat_face",
+ "clown_face",
+ "lying_face",
+ "sushing_face",
+ "face_with_hand_over_mouth",
+ "face_with_monocle",
+ "nerd_face",
+ "smiling_imp",
+ "imp",
+ "japanese_ogre",
+ "japanese_goblin",
+ "skull",
+ "skull_and_crossbones",
+ "ghost",
+ "alien",
+ "space_invader",
+ "robot",
+ "poop",
+ "smiley_cat",
"smile_cat",
"joy_cat",
- "smiley_cat",
"heart_eyes_cat",
"smirk_cat",
"kissing_cat",
- "pouting_cat",
- "crying_cat_face",
"scream_cat",
- "footprints",
- "bust_in_silhouette",
- "busts_in_silhouette",
+ "crying_cat_face",
+ "pouting_cat",
+ "see_no_evil",
+ "hear_no_evil",
+ "speak_no_evil",
"baby",
+ "child",
"boy",
"girl",
+ "adult",
"man",
"woman",
- "family",
+ "older_adult",
+ "older_man",
+ "older_woman",
+ "man_health_worker",
+ "woman_health_worker",
+ "man_student",
+ "woman_student",
+ "man_teacher",
+ "woman_teacher",
+ "man_judge",
+ "woman_judge",
+ "man_farmer",
+ "woman_farmer",
+ "man_cook",
+ "woman_cook",
+ "man_mechanic",
+ "woman_mechanic",
+ "man_factory_worker",
+ "woman_factory_worker",
+ "man_office_worker",
+ "woman_office_worker",
+ "man_scientist",
+ "woman_scientist",
+ "man_technologist",
+ "woman_technologist",
+ "man_singer",
+ "woman_singer",
+ "man_artist",
+ "woman_artist",
+ "man_pilot",
+ "woman_pilot",
+ "man_astronaut",
+ "woman_astronaut",
+ "man_firefighter",
+ "woman_firefighter",
+ "policeman",
+ "policewoman",
+ "male_detective",
+ "female_detective",
+ "guardsman",
+ "guardswoman",
+ "construction_worker_man",
+ "construction_worker_woman",
+ "prince",
+ "princess",
+ "man_with_turban",
+ "woman_with_turban",
+ "man_with_gua_pi_mao",
+ "woman_with_headscarf",
+ "bearded_person",
+ "blonde_man",
+ "blonde_woman",
+ "man_in_tuxedo",
+ "bride_with_veil",
+ "pregnant_woman",
+ "breast_feeding",
+ "angel",
+ "santa",
+ "mrs_claus",
+ "mage",
+ "fairy",
+ "vampire",
+ "merperson",
+ "elf",
+ "genie",
+ "zombie",
+ "frowning_woman",
+ "frowning_man",
+ "pouting_woman",
+ "pouting_man",
+ "no_good_woman",
+ "no_good_man",
+ "ok_woman",
+ "ok_man",
+ "tipping_hand_woman",
+ "tipping_hand_man",
+ "raising_hand_woman",
+ "raising_hand_man",
+ "bowing_man",
+ "bowing_woman",
+ "man_facepalming",
+ "woman_facepalming",
+ "man_shrugging",
+ "woman_shrugging",
"couple",
"two_men_holding_hands",
"two_women_holding_hands",
- "dancers",
- "bride_with_veil",
- "person_with_blond_hair",
- "man_with_gua_pi_mao",
- "man_with_turban",
- "older_man",
- "older_woman",
- "cop",
- "construction_worker",
- "princess",
- "guardsman",
- "angel",
- "santa",
- "ghost",
- "japanese_ogre",
- "japanese_goblin",
- "hankey",
- "skull",
- "alien",
- "space_invader",
- "bow",
- "information_desk_person",
- "no_good",
- "ok_woman",
- "raising_hand",
- "person_with_pouting_face",
- "person_frowning",
- "massage",
- "haircut",
- "couple_with_heart",
- "couplekiss",
- "raised_hands",
- "clap",
- "hand",
- "ear",
- "eyes",
- "nose",
- "lips",
- "kiss",
- "tongue",
- "nail_care",
- "wave",
- "+1",
- "-1",
- "point_up",
- "point_up_2",
- "point_down",
- "point_left",
- "point_right",
- "ok_hand",
- "v",
- "facepunch",
- "fist",
- "raised_hand",
- "muscle",
- "open_hands",
- "pray",
- "anger_right",
- "eye",
- "frowning2",
- "hand_splayed",
- "head_bandage",
- "hugging",
- "middle_finger",
- "money_mouth",
- "nerd",
- "poop",
- "punch",
- "robot",
- "rolling_eyes",
- "skull_crossbones",
- "slight_frown",
- "speaking_head",
- "spy",
- "thinking",
- "thumbsup",
- "thumbsdown",
- "upside_down",
- "urn",
- "vulcan",
- "wind_blowing_face",
- "writing_hand",
- "zipper_mouth",
- "female_couple_with_heart",
- "male_couple_with_heart",
- "female_couplekiss",
- "male_couplekiss",
+ "couplekiss_man_woman",
+ "couplekiss_man_man",
+ "couplekiss_woman_woman",
+ "couple_with_heart_woman_man",
+ "couple_with_heart_man_man",
+ "couple_with_heart_woman_woman",
+ "family_man_woman_boy",
"family_man_woman_girl",
"family_man_woman_girl_boy",
- "family_man_woman_boys",
- "family_man_woman_girls",
- "family_women_boy",
- "family_women_girl",
- "family_women_girl_boy",
- "family_women_boys",
- "family_women_girls",
- "family_men_boy",
- "family_men_girl",
- "family_men_girl_boy",
- "family_men_boys",
- "family_men_girls"
+ "family_man_woman_boy_boy",
+ "family_man_woman_girl_girl",
+ "family_man_man_boy",
+ "family_man_man_girl",
+ "family_man_man_girl_boy",
+ "family_man_man_boy_boy",
+ "family_man_man_girl_girl",
+ "family_woman_woman_boy",
+ "family_woman_woman_girl",
+ "family_woman_woman_girl_boy",
+ "family_woman_woman_boy_boy",
+ "family_woman_woman_girl_girl",
+ "family_man_boy",
+ "family_man_boy_boy",
+ "family_man_girl",
+ "family_man_girl_boy",
+ "family_man_girl_girl",
+ "family_woman_boy",
+ "family_woman_boy_boy",
+ "family_woman_girl",
+ "family_woman_girl_boy",
+ "family_woman_girl_girl",
+ "selfie",
+ "muscle",
+ "point_left",
+ "point_right",
+ "point_up",
+ "point_up_2",
+ "fu",
+ "point_down",
+ "v",
+ "crossed_fingers",
+ "vulcan_salute",
+ "metal",
+ "call_me_hand",
+ "raised_hand_with_fingers_splayed",
+ "raised_hand",
+ "ok_hand",
+ "+1",
+ "-1",
+ "fist",
+ "facepunch",
+ "fist_left",
+ "fist_right",
+ "raised_back_of_hand",
+ "wave",
+ "love_you_gesture",
+ "writing_hand",
+ "clap",
+ "open_hands",
+ "raised_hands",
+ "palms_up_together",
+ "pray",
+ "handshake",
+ "nail_care",
+ "ear",
+ "nose",
+ "footprints",
+ "eyes",
+ "eye",
+ "brain",
+ "tongue",
+ "lips"
]
},
{
- name:"nature",
- fullname:"Nature",
- tabicon:"evergreen_tree",
- icons:[
+ "name": "nature",
+ "fullname": "Nature",
+ "tabicon": "evergreen_tree",
+ "icons": [
+ "monkey_face",
+ "monkey",
+ "gorilla",
+ "dog",
+ "dog2",
+ "poodle",
+ "wolf",
+ "fox_face",
+ "cat",
+ "cat2",
+ "lion",
+ "tiger",
+ "tiger2",
+ "leopard",
+ "horse",
+ "racehorse",
+ "unicorn",
+ "zebra",
+ "deer",
+ "cow",
+ "ox",
+ "water_buffalo",
+ "cow2",
+ "pig",
+ "pig2",
+ "boar",
+ "pig_nose",
+ "ram",
+ "sheep",
+ "goat",
+ "dromedary_camel",
+ "camel",
+ "giraffe",
+ "elephant",
+ "rhinoceros",
+ "mouse",
+ "mouse2",
+ "rat",
+ "hamster",
+ "rabbit",
+ "rabbit2",
+ "chipmunk",
+ "hedgehog",
+ "bat",
+ "bear",
+ "koala",
+ "panda_face",
+ "paw_prints",
+ "turkey",
+ "chicken",
+ "rooster",
+ "hatching_chick",
+ "baby_chick",
+ "hatched_chick",
+ "bird",
+ "penguin",
+ "dove",
+ "eagle",
+ "duck",
+ "owl",
+ "frog",
+ "crocodile",
+ "turtle",
+ "lizard",
+ "snake",
+ "dragon_face",
+ "dragon",
+ "sauropod",
+ "t_rex",
+ "whale",
+ "whale2",
+ "dolphin",
+ "fish",
+ "tropical_fish",
+ "blowfish",
+ "shark",
+ "octopus",
+ "shell",
+ "crab",
+ "shrimp",
+ "squid",
+ "snail",
+ "butterfly",
+ "bug",
+ "ant",
+ "honeybee",
+ "beetle",
+ "cricket",
+ "spider",
+ "spider_web",
+ "scorpion",
+ "bouquet",
+ "cherry_blossom",
+ "white_flower",
+ "rosette",
+ "rose",
+ "wilted_flower",
+ "hibiscus",
+ "sunflower",
+ "blossom",
+ "tulip",
"seedling",
"evergreen_tree",
"deciduous_tree",
"palm_tree",
"cactus",
- "tulip",
- "cherry_blossom",
- "rose",
- "hibiscus",
- "sunflower",
- "blossom",
- "bouquet",
"ear_of_rice",
"herb",
+ "shamrock",
"four_leaf_clover",
"maple_leaf",
"fallen_leaf",
"leaves",
- "mushroom",
- "chestnut",
- "rat",
- "mouse2",
- "mouse",
- "hamster",
- "ox",
- "water_buffalo",
- "cow2",
- "cow",
- "tiger2",
- "leopard",
- "tiger",
- "rabbit2",
- "rabbit",
- "cat2",
- "cat",
- "racehorse",
- "horse",
- "ram",
- "sheep",
- "goat",
- "rooster",
- "chicken",
- "baby_chick",
- "hatching_chick",
- "hatched_chick",
- "bird",
- "penguin",
- "elephant",
- "dromedary_camel",
- "camel",
- "boar",
- "pig2",
- "pig",
- "pig_nose",
- "dog2",
- "poodle",
- "dog",
- "wolf",
- "bear",
- "koala",
- "panda_face",
- "monkey_face",
- "see_no_evil",
- "hear_no_evil",
- "speak_no_evil",
- "monkey",
- "dragon",
- "dragon_face",
- "crocodile",
- "snake",
- "turtle",
- "frog",
- "whale2",
- "whale",
- "dolphin",
- "octopus",
- "fish",
- "tropical_fish",
- "blowfish",
- "shell",
- "snail",
- "bug",
- "ant",
- "bee",
- "beetle",
- "feet",
- "zap",
- "fire",
- "crescent_moon",
- "sunny",
- "partly_sunny",
- "cloud",
- "droplet",
- "sweat_drops",
- "umbrella",
- "dash",
- "snowflake",
- "star2",
- "star",
- "stars",
- "sunrise_over_mountains",
- "sunrise",
- "rainbow",
- "ocean",
- "volcano",
- "milky_way",
- "mount_fuji",
- "japan",
- "globe_with_meridians",
- "earth_africa",
- "earth_americas",
- "earth_asia",
"new_moon",
"waxing_crescent_moon",
"first_quarter_moon",
- "moon",
+ "waxing_gibbous_moon",
"full_moon",
"waning_gibbous_moon",
"last_quarter_moon",
"waning_crescent_moon",
+ "crescent_moon",
"new_moon_with_face",
- "full_moon_with_face",
"first_quarter_moon_with_face",
"last_quarter_moon_with_face",
+ "thermometer",
+ "sunny",
+ "full_moon_with_face",
"sun_with_face",
- "chipmunk",
- "cloud_lightning",
- "cloud_rain",
- "cloud_snow",
- "cloud_tornado",
- "comet",
- "crab",
- "dove",
+ "star",
+ "star2",
+ "stars",
+ "cloud",
+ "partly_sunny",
+ "cloud_with_lightning_and_rain",
+ "sun_behind_small_cloud",
+ "sun_behind_large_cloud",
+ "sun_behind_rain_cloud",
+ "cloud_with_rain",
+ "cloud_with_snow",
+ "cloud_with_lightning",
+ "tornado",
"fog",
- "lion_face",
- "scorpion",
- "spider",
- "spider_web",
- "thunder_cloud_rain",
- "turkey",
- "unicorn",
- "waxing_gibbous_moon",
- "white_sun_cloud",
- "white_sun_rain_cloud",
- "white_sun_small_cloud"
+ "wind_face",
+ "cyclone",
+ "rainbow",
+ "closed_umbrella",
+ "open_umbrella",
+ "umbrella",
+ "parasol_on_ground",
+ "zap",
+ "snowflake",
+ "snowman_with_snow",
+ "snowman",
+ "comet",
+ "fire",
+ "droplet",
+ "ocean"
]
},
{
- name:"food",
- fullname:"Food & Drink",
- tabicon:"hamburger",
- icons:[
- "tomato",
- "eggplant",
- "corn",
- "sweet_potato",
+ "name": "food",
+ "fullname": "Food & Drink",
+ "tabicon": "hamburger",
+ "icons": [
"grapes",
"melon",
"watermelon",
@@ -366,187 +465,335 @@ const groups = [
"peach",
"cherries",
"strawberry",
- "hamburger",
- "pizza",
+ "kiwi_fruit",
+ "tomato",
+ "coconut",
+ "avocado",
+ "eggplant",
+ "potato",
+ "carrot",
+ "corn",
+ "hot_pepper",
+ "cucumber",
+ "broccoli",
+ "mushroom",
+ "peanuts",
+ "chestnut",
+ "bread",
+ "croissant",
+ "baguette_bread",
+ "pretzel",
+ "pancakes",
+ "cheese",
"meat_on_bone",
"poultry_leg",
+ "cut_of_meat",
+ "bacon",
+ "hamburger",
+ "fries",
+ "pizza",
+ "hotdog",
+ "sandwich",
+ "taco",
+ "burrito",
+ "stuffed_flatbread",
+ "egg",
+ "fried_egg",
+ "shallow_pan_of_food",
+ "stew",
+ "bowl_with_spoon",
+ "green_salad",
+ "popcorn",
+ "canned_food",
+ "bento",
"rice_cracker",
"rice_ball",
"rice",
"curry",
"ramen",
"spaghetti",
- "bread",
- "fries",
- "dango",
+ "sweet_potato",
"oden",
"sushi",
"fried_shrimp",
"fish_cake",
+ "dango",
+ "dumpling",
+ "fortune_cookie",
+ "takeout_box",
"icecream",
"shaved_ice",
"ice_cream",
"doughnut",
"cookie",
+ "birthday",
+ "cake",
+ "pie",
"chocolate_bar",
"candy",
"lollipop",
"custard",
"honey_pot",
- "cake",
- "bento",
- "stew",
- "egg",
- "fork_and_knife",
- "tea",
+ "baby_bottle",
+ "milk_glass",
"coffee",
+ "tea",
"sake",
+ "champagne",
"wine_glass",
"cocktail",
"tropical_drink",
"beer",
"beers",
- "baby_bottle",
- "burrito",
- "champagne",
- "cheese",
- "hot_pepper",
- "hotdog",
- "taco"
+ "clinking_glasses",
+ "tumbler_glass",
+ "cup_with_straw",
+ "chopsticks",
+ "plate_with_cutlery",
+ "fork_and_knife",
+ "spoon",
+ "hocho",
+ "amphora"
]
},
{
- name:"celebration",
- fullname:"Celebration",
- tabicon:"gift",
- icons:[
- "ribbon",
- "gift",
- "birthday",
+ "name": "celebration",
+ "fullname": "Celebration",
+ "tabicon": "gift",
+ "icons": [
"jack_o_lantern",
"christmas_tree",
- "tanabata_tree",
- "bamboo",
- "rice_scene",
"fireworks",
"sparkler",
+ "sparkles",
+ "balloon",
"tada",
"confetti_ball",
- "balloon",
- "dizzy",
- "sparkles",
- "boom",
- "mortar_board",
- "crown",
+ "tanabata_tree",
+ "bamboo",
"dolls",
"flags",
"wind_chime",
- "crossed_flags",
- "izakaya_lantern",
- "ring",
- "heart",
- "broken_heart",
- "love_letter",
- "two_hearts",
- "revolving_hearts",
- "heartbeat",
- "heartpulse",
- "sparkling_heart",
+ "rice_scene",
+ "ribbon",
+ "gift",
+ "reminder_ribbon",
+ "tickets",
+ "ticket",
+ "kiss",
"cupid",
- "gift_heart",
- "heart_decoration",
- "purple_heart",
- "yellow_heart",
- "green_heart",
+ "heart",
+ "heartbeat",
+ "broken_heart",
+ "two_hearts",
+ "sparkling_heart",
+ "heartpulse",
"blue_heart",
- "heart_exclamation"
+ "green_heart",
+ "yellow_heart",
+ "orange_heart",
+ "purple_heart",
+ "black_heart",
+ "gift_heart",
+ "revolving_hearts",
+ "heart_decoration",
+ "heavy_heart_exclamation",
+ "love_letter",
+ "zzz",
+ "anger",
+ "bomb",
+ "boom",
+ "sweat_drops",
+ "dash",
+ "dizzy",
+ "speech_balloon",
+ "left_speech_bubble",
+ "right_anger_bubble",
+ "thought_balloon",
+ "hole"
]
},
{
- name:"activity",
- fullname:"Activities",
- tabicon:"soccer",
- icons:[
- "runner",
- "walking",
+ "name": "activity",
+ "fullname": "Activities",
+ "tabicon": "soccer",
+ "icons": [
+ "massage_woman",
+ "massage_man",
+ "haircut_woman",
+ "haircut_man",
+ "walking_man",
+ "walking_woman",
+ "running_man",
+ "running_woman",
"dancer",
- "rowboat",
- "swimmer",
- "surfer",
+ "man_dancing",
+ "dancing_women",
+ "dancing_men",
+ "person_in_steamy_room",
+ "person_climbing",
+ "person_in_lotus_position",
"bath",
- "snowboarder",
- "ski",
- "snowman",
- "bicyclist",
- "mountain_bicyclist",
+ "sleeping_bed",
+ "business_suit_levitating",
+ "speaking_head",
+ "bust_in_silhouette",
+ "busts_in_silhouette",
+ "person_fencing",
"horse_racing",
- "tent",
- "fishing_pole_and_fish",
+ "skier",
+ "snowboarder",
+ "golfing_man",
+ "golfing_woman",
+ "surfing_man",
+ "surfing_woman",
+ "rowing_man",
+ "rowing_woman",
+ "swimming_man",
+ "swimming_woman",
+ "basketball_man",
+ "basketball_woman",
+ "weight_lifting_man",
+ "weight_lifting_woman",
+ "biking_man",
+ "biking_woman",
+ "mountain_biking_man",
+ "mountain_biking_woman",
+ "racing_car",
+ "motorcycle",
+ "man_cartwheeling",
+ "woman_cartwheeling",
+ "men_wrestling",
+ "women_wrestling",
+ "man_playing_water_polo",
+ "woman_playing_water_polo",
+ "man_playing_handball",
+ "woman_playing_handball",
+ "man_juggling",
+ "woman_juggling",
"soccer",
- "basketball",
- "football",
"baseball",
- "tennis",
+ "basketball",
+ "volleyball",
+ "football",
"rugby_football",
- "golf",
- "trophy",
- "running_shirt_with_sash",
- "checkered_flag",
- "musical_keyboard",
- "guitar",
- "violin",
- "saxophone",
- "trumpet",
- "musical_note",
- "notes",
- "musical_score",
- "headphones",
- "microphone",
- "performing_arts",
- "ticket",
- "tophat",
- "circus_tent",
- "clapper",
- "art",
- "dart",
+ "tennis",
"8ball",
"bowling",
- "slot_machine",
- "game_die",
+ "cricket",
+ "field_hockey",
+ "ice_hockey",
+ "ping_pong",
+ "badminton",
+ "boxing_glove",
+ "martial_arts_uniform",
+ "goal_net",
+ "dart",
+ "golf",
+ "ice_skate",
+ "fishing_pole_and_fish",
+ "running_shirt_with_sash",
+ "ski",
+ "sled",
+ "curling_stone",
"video_game",
- "flower_playing_cards",
+ "joystick",
+ "game_die",
+ "spades",
+ "hearts",
+ "diamonds",
+ "clubs",
"black_joker",
"mahjong",
+ "flower_playing_cards",
+ "musical_score",
+ "musical_note",
+ "notes",
+ "studio_microphone",
+ "level_slider",
+ "control_knobs",
+ "microphone",
+ "headphones",
+ "radio",
+ "saxophone",
+ "guitar",
+ "musical_keyboard",
+ "trumpet",
+ "violin",
+ "drum"
+ ]
+ },
+ {
+ "name": "travel",
+ "fullname": "Travel & Places",
+ "tabicon": "airplane",
+ "icons": [
+ "earth_africa",
+ "earth_americas",
+ "earth_asia",
+ "globe_with_meridians",
+ "world_map",
+ "japan",
+ "mountain_snow",
+ "mountain",
+ "volcano",
+ "mount_fuji",
+ "camping",
+ "beach_umbrella",
+ "desert",
+ "desert_island",
+ "national_park",
+ "stadium",
+ "classical_building",
+ "building_construction",
+ "houses",
+ "cityscape",
+ "derelict_house",
+ "house",
+ "house_with_garden",
+ "office",
+ "post_office",
+ "european_post_office",
+ "hospital",
+ "bank",
+ "hotel",
+ "love_hotel",
+ "convenience_store",
+ "school",
+ "department_store",
+ "factory",
+ "japanese_castle",
+ "european_castle",
+ "wedding",
+ "tokyo_tower",
+ "statue_of_liberty",
+ "church",
+ "mosque",
+ "synagogue",
+ "shinto_shrine",
+ "kaaba",
+ "fountain",
+ "tent",
+ "foggy",
+ "night_with_stars",
+ "sunrise_over_mountains",
+ "sunrise",
+ "city_sunset",
+ "city_sunrise",
+ "bridge_at_night",
+ "hotsprings",
+ "milky_way",
"carousel_horse",
"ferris_wheel",
"roller_coaster",
- "badminton",
- "ballot_box",
- "basketball_player",
- "bow_and_arrow",
- "cricket",
- "crossed_swords",
- "field_hockey",
- "golfer",
- "hockey",
- "ice_skate",
- "paintbrush",
- "skier",
- "snowman2",
- "stadium",
- "volleyball"
- ]
- },
- {
- name:"travel",
- fullname:"Travel & Places",
- tabicon:"airplane",
- icons:[
- "train",
- "mountain_railway",
- "railway_car",
+ "barber",
+ "circus_tent",
+ "performing_arts",
+ "framed_picture",
+ "art",
+ "slot_machine",
"steam_locomotive",
- "monorail",
+ "railway_car",
"bullettrain_side",
"bullettrain_front",
"train2",
@@ -554,6 +801,9 @@ const groups = [
"light_rail",
"station",
"tram",
+ "monorail",
+ "mountain_railway",
+ "train",
"bus",
"oncoming_bus",
"trolleybus",
@@ -562,318 +812,569 @@ const groups = [
"fire_engine",
"police_car",
"oncoming_police_car",
- "rotating_light",
"taxi",
"oncoming_taxi",
- "car",
+ "red_car",
"oncoming_automobile",
"blue_car",
"truck",
"articulated_lorry",
"tractor",
"bike",
+ "kick_scooter",
+ "motor_scooter",
"busstop",
- "fuelpump",
- "construction",
- "vertical_traffic_light",
- "traffic_light",
- "rocket",
- "helicopter",
- "airplane",
- "seat",
- "anchor",
- "ship",
- "speedboat",
- "boat",
- "aerial_tramway",
- "mountain_cableway",
- "suspension_railway",
- "passport_control",
- "customs",
- "baggage_claim",
- "left_luggage",
- "yen",
- "euro",
- "pound",
- "dollar",
- "statue_of_liberty",
- "moyai",
- "foggy",
- "tokyo_tower",
- "fountain",
- "european_castle",
- "japanese_castle",
- "city_sunrise",
- "city_sunset",
- "night_with_stars",
- "bridge_at_night",
- "house",
- "house_with_garden",
- "office",
- "department_store",
- "factory",
- "post_office",
- "european_post_office",
- "hospital",
- "bank",
- "hotel",
- "love_hotel",
- "wedding",
- "church",
- "convenience_store",
- "school",
- "cn",
- "de",
- "es",
- "fr",
- "gb",
- "it",
- "jp",
- "kr",
- "ru",
- "us",
- "airplane_arriving",
- "airplane_departure",
- "airplane_small",
- "beach",
- "beach_umbrella",
- "camping",
- "city_dusk",
- "cityscape",
- "classical_building",
- "construction_site",
- "cruise_ship",
- "desert",
- "ferry",
- "flag_black",
- "flag_cn",
- "flag_de",
- "flag_es",
- "flag_fr",
- "flag_gb",
- "flag_it",
- "flag_jp",
- "flag_kr",
- "flag_ru",
- "flag_us",
- "flag_white",
- "hole",
- "homes",
- "house_abandoned",
- "island",
- "kaaba",
- "map",
- "mosque",
- "motorboat",
- "motorcycle",
"motorway",
- "mountain",
- "mountain_snow",
- "park",
- "place_of_worship",
- "race_car",
"railway_track",
- "red_car",
+ "fuelpump",
+ "rotating_light",
+ "traffic_light",
+ "vertical_traffic_light",
+ "construction",
+ "stop_sign",
+ "anchor",
"sailboat",
- "shinto_shrine",
- "sleeping_accommodation",
- "synagogue"
+ "canoe",
+ "speedboat",
+ "passenger_ship",
+ "ferry",
+ "motor_boat",
+ "ship",
+ "airplane",
+ "small_airplane",
+ "flight_departure",
+ "flight_arrival",
+ "seat",
+ "helicopter",
+ "suspension_railway",
+ "mountain_cableway",
+ "aerial_tramway",
+ "artificial_satellite",
+ "rocket",
+ "flying_saucer",
+ "bellhop_bell",
+ "door",
+ "bed",
+ "couch_and_lamp",
+ "toilet",
+ "shower",
+ "bathtub",
+ "checkered_flag",
+ "triangular_flag_on_post",
+ "crossed_flags",
+ "black_flag",
+ "white_flag",
+ "rainbow_flag",
+ "ascension_island",
+ "andorra",
+ "united_arab_emirates",
+ "afghanistan",
+ "antigua_barbuda",
+ "anguilla",
+ "albania",
+ "armenia",
+ "angola",
+ "antarctica",
+ "argentina",
+ "american_samoa",
+ "austria",
+ "australia",
+ "aruba",
+ "aland_islands",
+ "azerbaijan",
+ "bosnia_herzegovina",
+ "barbados",
+ "bangladesh",
+ "belgium",
+ "burkina_faso",
+ "bulgaria",
+ "bahrain",
+ "burundi",
+ "benin",
+ "st_barthelemy",
+ "bermuda",
+ "brunei",
+ "bolivia",
+ "caribbean_netherlands",
+ "brazil",
+ "bahamas",
+ "bhutan",
+ "bouvet_island",
+ "botswana",
+ "belarus",
+ "belize",
+ "canada",
+ "cocos_islands",
+ "congo_kinshasa",
+ "central_african_republic",
+ "congo_brazzaville",
+ "switzerland",
+ "cote_divoire",
+ "cook_islands",
+ "chile",
+ "cameroon",
+ "cn",
+ "colombia",
+ "clipperton_island",
+ "costa_rica",
+ "cuba",
+ "cape_verde",
+ "curacao",
+ "christmas_island",
+ "cyprus",
+ "czech_republic",
+ "de",
+ "diego_garcia",
+ "djibouti",
+ "denmark",
+ "dominica",
+ "dominican_republic",
+ "algeria",
+ "ceuta_and_melilla",
+ "ecuador",
+ "estonia",
+ "egypt",
+ "western_sahara",
+ "eritrea",
+ "es",
+ "ethiopia",
+ "eu",
+ "finland",
+ "fiji",
+ "falkland_islands",
+ "micronesia",
+ "faroe_islands",
+ "fr",
+ "gabon",
+ "uk",
+ "grenada",
+ "georgia",
+ "french_guiana",
+ "guernsey",
+ "ghana",
+ "gibraltar",
+ "greenland",
+ "gambia",
+ "guinea",
+ "guadeloupe",
+ "equatorial_guinea",
+ "greece",
+ "south_georgia_south_sandwich_islands",
+ "guatemala",
+ "guam",
+ "guinea_bissau",
+ "guyana",
+ "hong_kong",
+ "heard_and_mc_donald_islands",
+ "honduras",
+ "croatia",
+ "haiti",
+ "hungary",
+ "canary_islands",
+ "indonesia",
+ "ireland",
+ "israel",
+ "isle_of_man",
+ "india",
+ "british_indian_ocean_territory",
+ "iraq",
+ "iran",
+ "iceland",
+ "it",
+ "jersey",
+ "jamaica",
+ "jordan",
+ "jp",
+ "kenya",
+ "kyrgyzstan",
+ "cambodia",
+ "kiribati",
+ "comoros",
+ "st_kitts_nevis",
+ "north_korea",
+ "kr",
+ "kuwait",
+ "cayman_islands",
+ "kazakhstan",
+ "laos",
+ "lebanon",
+ "st_lucia",
+ "liechtenstein",
+ "sri_lanka",
+ "liberia",
+ "lesotho",
+ "lithuania",
+ "luxembourg",
+ "latvia",
+ "libya",
+ "morocco",
+ "monaco",
+ "moldova",
+ "montenegro",
+ "st_martin",
+ "madagascar",
+ "marshall_islands",
+ "macedonia",
+ "mali",
+ "myanmar",
+ "mongolia",
+ "macau",
+ "northern_mariana_islands",
+ "martinique",
+ "mauritania",
+ "montserrat",
+ "malta",
+ "mauritius",
+ "maldives",
+ "malawi",
+ "mexico",
+ "malaysia",
+ "mozambique",
+ "namibia",
+ "new_caledonia",
+ "niger",
+ "norfolk_island",
+ "nigeria",
+ "nicaragua",
+ "netherlands",
+ "norway",
+ "nepal",
+ "nauru",
+ "niue",
+ "new_zealand",
+ "oman",
+ "panama",
+ "peru",
+ "french_polynesia",
+ "papua_new_guinea",
+ "philippines",
+ "pakistan",
+ "poland",
+ "st_pierre_miquelon",
+ "pitcairn_islands",
+ "puerto_rico",
+ "palestinian_territories",
+ "portugal",
+ "palau",
+ "paraguay",
+ "qatar",
+ "reunion",
+ "romania",
+ "serbia",
+ "ru",
+ "rwanda",
+ "saudi_arabia",
+ "solomon_islands",
+ "seychelles",
+ "sudan",
+ "sweden",
+ "singapore",
+ "st_helena",
+ "slovenia",
+ "svalbard_and_jan_mayen",
+ "slovakia",
+ "sierra_leone",
+ "san_marino",
+ "senegal",
+ "somalia",
+ "suriname",
+ "south_sudan",
+ "sao_tome_principe",
+ "el_salvador",
+ "sint_maarten",
+ "syria",
+ "swaziland",
+ "tristan_da_cunha",
+ "turks_caicos_islands",
+ "chad",
+ "french_southern_territories",
+ "togo",
+ "thailand",
+ "tajikistan",
+ "tokelau",
+ "timor_leste",
+ "turkmenistan",
+ "tunisia",
+ "tonga",
+ "tr",
+ "trinidad_tobago",
+ "tuvalu",
+ "taiwan",
+ "tanzania",
+ "ukraine",
+ "uganda",
+ "us_outlying_islands",
+ "united_nations",
+ "us",
+ "uruguay",
+ "uzbekistan",
+ "vatican_city",
+ "st_vincent_grenadines",
+ "venezuela",
+ "british_virgin_islands",
+ "us_virgin_islands",
+ "vietnam",
+ "vanuatu",
+ "wallis_futuna",
+ "samoa",
+ "kosovo",
+ "yemen",
+ "mayotte",
+ "south_africa",
+ "zambia",
+ "zimbabwe"
]
},
{
- name:"objects",
- fullname:"Objects & Symbols",
- tabicon:"eyeglasses",
- icons:[
- "watch",
+ "name": "objects",
+ "fullname": "Objects & Symbols",
+ "tabicon": "eyeglasses",
+ "icons": [
+ "eyeglasses",
+ "dark_sunglasses",
+ "necktie",
+ "tshirt",
+ "jeans",
+ "scarf",
+ "gloves",
+ "coat",
+ "socks",
+ "dress",
+ "kimono",
+ "bikini",
+ "womans_clothes",
+ "purse",
+ "handbag",
+ "pouch",
+ "shopping",
+ "school_satchel",
+ "mans_shoe",
+ "athletic_shoe",
+ "high_heel",
+ "sandal",
+ "boot",
+ "crown",
+ "womans_hat",
+ "tophat",
+ "mortar_board",
+ "billed_cap",
+ "rescue_worker_helmet",
+ "prayer_beads",
+ "lipstick",
+ "ring",
+ "gem",
+ "medal_military",
+ "trophy",
+ "medal_sports",
+ "1st_place_medal",
+ "2nd_place_medal",
+ "3rd_place_medal",
+ "mute",
+ "speaker",
+ "sound",
+ "loud_sound",
+ "loudspeaker",
+ "mega",
+ "postal_horn",
+ "bell",
+ "no_bell",
"iphone",
"calling",
- "computer",
- "alarm_clock",
- "hourglass_flowing_sand",
- "hourglass",
- "camera",
- "video_camera",
- "movie_camera",
- "tv",
- "radio",
- "pager",
- "telephone_receiver",
"phone",
+ "telephone_receiver",
+ "pager",
"fax",
+ "battery",
+ "electric_plug",
+ "computer",
+ "desktop_computer",
+ "printer",
+ "keyboard",
+ "computer_mouse",
+ "trackball",
"minidisc",
"floppy_disk",
"cd",
"dvd",
+ "movie_camera",
+ "film_strip",
+ "film_projector",
+ "clapper",
+ "tv",
+ "camera",
+ "camera_flash",
+ "video_camera",
"vhs",
- "battery",
- "electric_plug",
- "bulb",
- "flashlight",
- "satellite",
- "credit_card",
- "money_with_wings",
- "moneybag",
- "gem",
- "closed_umbrella",
- "pouch",
- "purse",
- "handbag",
- "briefcase",
- "school_satchel",
- "lipstick",
- "eyeglasses",
- "womans_hat",
- "sandal",
- "high_heel",
- "boot",
- "mans_shoe",
- "athletic_shoe",
- "bikini",
- "dress",
- "kimono",
- "womans_clothes",
- "shirt",
- "necktie",
- "jeans",
- "door",
- "shower",
- "bathtub",
- "toilet",
- "barber",
- "syringe",
- "pill",
+ "mag",
+ "mag_right",
"microscope",
"telescope",
- "crystal_ball",
- "wrench",
- "hocho",
- "nut_and_bolt",
- "hammer",
- "bomb",
- "smoking",
- "gun",
- "bookmark",
- "newspaper",
- "key",
- "email",
- "envelope_with_arrow",
- "incoming_envelope",
- "e-mail",
- "inbox_tray",
- "outbox_tray",
- "package",
- "postal_horn",
- "postbox",
- "mailbox_closed",
- "mailbox",
- "mailbox_with_mail",
- "mailbox_with_no_mail",
- "page_facing_up",
- "page_with_curl",
- "bookmark_tabs",
- "chart_with_upwards_trend",
- "chart_with_downwards_trend",
- "bar_chart",
- "date",
- "calendar",
- "low_brightness",
- "high_brightness",
- "scroll",
- "clipboard",
- "book",
- "notebook",
+ "satellite",
+ "candle",
+ "bulb",
+ "flashlight",
+ "izakaya_lantern",
"notebook_with_decorative_cover",
- "ledger",
"closed_book",
+ "open_book",
"green_book",
"blue_book",
"orange_book",
"books",
- "card_index",
- "link",
- "paperclip",
- "pushpin",
- "scissors",
- "triangular_ruler",
- "round_pushpin",
- "straight_ruler",
- "triangular_flag_on_post",
+ "notebook",
+ "ledger",
+ "page_with_curl",
+ "scroll",
+ "page_facing_up",
+ "newspaper",
+ "newspaper_roll",
+ "bookmark_tabs",
+ "bookmark",
+ "label",
+ "moneybag",
+ "yen",
+ "dollar",
+ "euro",
+ "pound",
+ "money_with_wings",
+ "credit_card",
+ "chart",
+ "currency_exchange",
+ "heavy_dollar_sign",
+ "email",
+ "e-mail",
+ "incoming_envelope",
+ "envelope_with_arrow",
+ "outbox_tray",
+ "inbox_tray",
+ "package",
+ "mailbox",
+ "mailbox_closed",
+ "mailbox_with_mail",
+ "mailbox_with_no_mail",
+ "postbox",
+ "ballot_box",
+ "pencil2",
+ "black_nib",
+ "fountain_pen",
+ "pen",
+ "paintbrush",
+ "crayon",
+ "memo",
+ "briefcase",
"file_folder",
"open_file_folder",
- "black_nib",
- "pencil2",
- "memo",
- "lock_with_ink_pen",
- "closed_lock_with_key",
+ "card_index_dividers",
+ "date",
+ "calendar",
+ "spiral_notepad",
+ "spiral_calendar",
+ "card_index",
+ "chart_with_upwards_trend",
+ "chart_with_downwards_trend",
+ "bar_chart",
+ "clipboard",
+ "pushpin",
+ "round_pushpin",
+ "paperclip",
+ "paperclips",
+ "straight_ruler",
+ "triangular_ruler",
+ "scissors",
+ "card_file_box",
+ "file_cabinet",
+ "wastebasket",
"lock",
"unlock",
- "mega",
- "loudspeaker",
- "sound",
- "loud_sound",
- "speaker",
- "mute",
- "zzz",
- "bell",
- "no_bell",
- "thought_balloon",
- "speech_balloon",
+ "lock_with_ink_pen",
+ "closed_lock_with_key",
+ "key",
+ "old_key",
+ "hammer",
+ "pick",
+ "hammer_and_pick",
+ "hammer_and_wrench",
+ "dagger",
+ "crossed_swords",
+ "gun",
+ "bow_and_arrow",
+ "shield",
+ "wrench",
+ "nut_and_bolt",
+ "gear",
+ "clamp",
+ "alembic",
+ "balance_scale",
+ "link",
+ "chains",
+ "syringe",
+ "pill",
+ "smoking",
+ "coffin",
+ "funeral_urn",
+ "moyai",
+ "oil_drum",
+ "crystal_ball",
+ "shopping_cart",
+ "atm",
+ "put_litter_in_its_place",
+ "potable_water",
+ "wheelchair",
+ "mens",
+ "womens",
+ "restroom",
+ "baby_symbol",
+ "wc",
+ "passport_control",
+ "customs",
+ "baggage_claim",
+ "left_luggage",
+ "warning",
"children_crossing",
- "mag",
- "mag_right",
- "no_entry_sign",
"no_entry",
- "name_badge",
- "no_pedestrians",
- "do_not_litter",
+ "no_entry_sign",
"no_bicycles",
+ "no_smoking",
+ "do_not_litter",
"non-potable_water",
+ "no_pedestrians",
"no_mobile_phones",
"underage",
- "accept",
- "ideograph_advantage",
- "white_flower",
- "secret",
- "congratulations",
- "u5408",
- "u6e80",
- "u7981",
- "u6709",
- "u7121",
- "u7533",
- "u55b6",
- "u6708",
- "u5272",
- "u7a7a",
- "sa",
- "koko",
- "u6307",
- "chart",
- "sparkle",
- "eight_spoked_asterisk",
- "negative_squared_cross_mark",
- "white_check_mark",
- "eight_pointed_black_star",
- "vibration_mode",
- "mobile_phone_off",
- "vs",
- "a",
- "b",
- "ab",
- "cl",
- "o2",
- "sos",
- "id",
- "parking",
- "wc",
- "cool",
- "free",
- "new",
- "ng",
- "ok",
- "up",
- "atm",
+ "radioactive",
+ "biohazard",
+ "arrow_up",
+ "arrow_upper_right",
+ "arrow_right",
+ "arrow_lower_right",
+ "arrow_down",
+ "arrow_lower_left",
+ "arrow_left",
+ "arrow_upper_left",
+ "arrow_up_down",
+ "left_right_arrow",
+ "leftwards_arrow_with_hook",
+ "arrow_right_hook",
+ "arrow_heading_up",
+ "arrow_heading_down",
+ "arrows_clockwise",
+ "arrows_counterclockwise",
+ "back",
+ "end",
+ "on",
+ "soon",
+ "top",
+ "place_of_worship",
+ "atom_symbol",
+ "om",
+ "star_of_david",
+ "wheel_of_dharma",
+ "yin_yang",
+ "latin_cross",
+ "orthodox_cross",
+ "star_and_crescent",
+ "peace_symbol",
+ "menorah",
+ "six_pointed_star",
"aries",
"taurus",
"gemini",
@@ -886,40 +1387,63 @@ const groups = [
"capricorn",
"aquarius",
"pisces",
- "restroom",
- "mens",
- "womens",
- "baby_symbol",
- "wheelchair",
- "potable_water",
- "no_smoking",
- "put_litter_in_its_place",
- "arrow_forward",
- "arrow_backward",
- "arrow_up_small",
- "arrow_down_small",
- "fast_forward",
- "rewind",
- "arrow_double_up",
- "arrow_double_down",
- "arrow_right",
- "arrow_left",
- "arrow_up",
- "arrow_down",
- "arrow_upper_right",
- "arrow_lower_right",
- "arrow_lower_left",
- "arrow_upper_left",
- "arrow_up_down",
- "left_right_arrow",
- "arrows_counterclockwise",
- "arrow_right_hook",
- "leftwards_arrow_with_hook",
- "arrow_heading_up",
- "arrow_heading_down",
+ "ophiuchus",
"twisted_rightwards_arrows",
"repeat",
"repeat_one",
+ "arrow_forward",
+ "fast_forward",
+ "next_track_button",
+ "play_or_pause_button",
+ "arrow_backward",
+ "rewind",
+ "previous_track_button",
+ "arrow_up_small",
+ "arrow_double_up",
+ "arrow_down_small",
+ "arrow_double_down",
+ "pause_button",
+ "stop_button",
+ "record_button",
+ "cinema",
+ "low_brightness",
+ "high_brightness",
+ "signal_strength",
+ "vibration_mode",
+ "mobile_phone_off",
+ "recycle",
+ "fleur_de_lis",
+ "trident",
+ "name_badge",
+ "beginner",
+ "o",
+ "white_check_mark",
+ "ballot_box_with_check",
+ "heavy_check_mark",
+ "heavy_multiplication_x",
+ "x",
+ "negative_squared_cross_mark",
+ "heavy_plus_sign",
+ "heavy_minus_sign",
+ "heavy_division_sign",
+ "curly_loop",
+ "loop",
+ "part_alternation_mark",
+ "eight_spoked_asterisk",
+ "eight_pointed_black_star",
+ "sparkle",
+ "bangbang",
+ "interrobang",
+ "question",
+ "grey_question",
+ "grey_exclamation",
+ "exclamation",
+ "wavy_dash",
+ "copyright",
+ "registered",
+ "tm",
+ "hash",
+ "asterisk",
"zero",
"one",
"two",
@@ -931,204 +1455,101 @@ const groups = [
"eight",
"nine",
"keycap_ten",
- "keycap_star",
- "1234",
- "hash",
- "abc",
- "abcd",
- "capital_abcd",
- "information_source",
- "signal_strength",
- "cinema",
- "symbols",
- "heavy_plus_sign",
- "heavy_minus_sign",
- "wavy_dash",
- "heavy_division_sign",
- "heavy_multiplication_x",
- "heavy_check_mark",
- "arrows_clockwise",
- "tm",
- "copyright",
- "registered",
- "currency_exchange",
- "heavy_dollar_sign",
- "curly_loop",
- "loop",
- "part_alternation_mark",
- "exclamation",
- "bangbang",
- "question",
- "grey_exclamation",
- "grey_question",
- "interrobang",
- "x",
- "o",
"100",
- "end",
- "back",
- "on",
- "top",
- "soon",
- "cyclone",
+ "capital_abcd",
+ "abcd",
+ "1234",
+ "symbols",
+ "abc",
+ "a",
+ "ab",
+ "b",
+ "cl",
+ "cool",
+ "free",
+ "information_source",
+ "id",
"m",
- "ophiuchus",
- "six_pointed_star",
- "beginner",
- "trident",
- "warning",
- "hotsprings",
- "recycle",
- "anger",
- "diamond_shape_with_a_dot_inside",
- "spades",
- "clubs",
- "hearts",
- "diamonds",
- "ballot_box_with_check",
- "white_circle",
- "black_circle",
- "radio_button",
- "red_circle",
- "large_blue_circle",
- "small_red_triangle",
- "small_red_triangle_down",
- "small_orange_diamond",
- "small_blue_diamond",
- "large_orange_diamond",
- "large_blue_diamond",
+ "new",
+ "ng",
+ "o2",
+ "ok",
+ "parking",
+ "sos",
+ "up",
+ "vs",
+ "koko",
+ "sa",
+ "u6708",
+ "u6709",
+ "u6307",
+ "ideograph_advantage",
+ "u5272",
+ "u7121",
+ "u7981",
+ "accept",
+ "u7533",
+ "u5408",
+ "u7a7a",
+ "congratulations",
+ "secret",
+ "u55b6",
+ "u6e80",
"black_small_square",
"white_small_square",
+ "white_medium_square",
+ "black_medium_square",
+ "white_medium_small_square",
+ "black_medium_small_square",
"black_large_square",
"white_large_square",
- "black_medium_square",
- "white_medium_square",
- "black_medium_small_square",
- "white_medium_small_square",
+ "large_orange_diamond",
+ "large_blue_diamond",
+ "small_orange_diamond",
+ "small_blue_diamond",
+ "small_red_triangle",
+ "small_red_triangle_down",
+ "diamond_shape_with_a_dot_inside",
+ "radio_button",
"black_square_button",
"white_square_button",
- "clock1",
- "clock2",
- "clock3",
- "clock4",
- "clock5",
- "clock6",
- "clock7",
- "clock8",
- "clock9",
- "clock10",
- "clock11",
- "clock12",
- "clock130",
- "clock230",
- "clock330",
- "clock430",
- "clock530",
- "clock630",
- "clock730",
- "clock830",
- "clock930",
- "clock1030",
- "clock1130",
- "clock1230",
- "alembic",
- "amphora",
- "atom",
- "biohazard",
- "bed",
- "bellhop",
- "calendar_spiral",
- "camera_with_flash",
- "candle",
- "card_box",
- "chains",
- "clock",
- "coffin",
- "compression",
- "control_knobs",
- "couch",
- "crayon",
- "cross",
- "dagger",
- "dark_sunglasses",
- "desktop",
- "dividers",
- "envelope",
- "file_cabinet",
- "film_frames",
- "fleur-de-lis",
- "fork_knife_plate",
- "frame_photo",
- "gear",
- "hammer_pick",
- "helmet_with_cross",
- "joystick",
- "key2",
- "keyboard",
- "knife",
- "label",
- "level_slider",
- "levitate",
- "lifter",
- "medal",
- "menorah",
- "metal",
- "microphone2",
- "military_medal",
- "mouse_three_button",
- "newspaper2",
- "notepad_spiral",
- "oil",
- "om_symbol",
- "orthodox_cross",
- "paperclips",
- "pause_button",
- "peace",
- "pen_ballpoint",
- "pen_fountain",
- "pencil",
- "pick",
- "ping_pong",
- "play_pause",
- "popcorn",
- "prayer_beads",
- "printer",
- "projector",
- "radioactive",
- "record_button",
- "reminder_ribbon",
- "rosette",
- "satellite_orbital",
- "scales",
- "shamrock",
- "shield",
- "shopping_bags",
- "star_and_crescent",
- "star_of_david",
- "stop_button",
+ "white_circle",
+ "black_circle",
+ "red_circle",
+ "large_blue_circle",
+ "hourglass",
+ "hourglass_flowing_sand",
+ "watch",
+ "alarm_clock",
"stopwatch",
- "telephone",
- "ten",
- "thermometer",
- "thermometer_face",
- "tickets",
- "timer",
- "tools",
- "track_next",
- "track_previous",
- "trackball",
- "umbrella2",
- "wastebasket",
- "wheel_of_dharma",
- "yin_yang",
- "left_speech_bubble"
+ "timer_clock",
+ "mantelpiece_clock",
+ "clock12",
+ "clock1230",
+ "clock1",
+ "clock130",
+ "clock2",
+ "clock230",
+ "clock3",
+ "clock330",
+ "clock4",
+ "clock430",
+ "clock5",
+ "clock530",
+ "clock6",
+ "clock630",
+ "clock7",
+ "clock730",
+ "clock8",
+ "clock830",
+ "clock9",
+ "clock930",
+ "clock10",
+ "clock1030",
+ "clock11",
+ "clock1130"
]
}
];
-// scrub groups
-groups.forEach(group => {
- group.icons = group.icons.reject(obj => !emojiExists(obj));
-});
-
export default groups;
diff --git a/app/assets/javascripts/discourse/lib/emoji/toolbar.js.es6 b/app/assets/javascripts/discourse/lib/emoji/toolbar.js.es6
index 3666df1b58..9ef409b3d4 100644
--- a/app/assets/javascripts/discourse/lib/emoji/toolbar.js.es6
+++ b/app/assets/javascripts/discourse/lib/emoji/toolbar.js.es6
@@ -1,6 +1,6 @@
import groups from 'discourse/lib/emoji/groups';
import KeyValueStore from "discourse/lib/key-value-store";
-import { emojiList } from 'pretty-text/emoji';
+import { emojiList, isSkinTonableEmoji } from 'pretty-text/emoji';
import { emojiUrlFor } from 'discourse/lib/text';
import { findRawTemplate } from 'discourse/lib/raw-templates';
@@ -11,6 +11,7 @@ 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: {}});
@@ -121,6 +122,13 @@ function bindEvents(page, offset, options) {
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) {
@@ -139,13 +147,30 @@ function render(page, offset, options) {
rows.push(row);
row = [];
}
- row.push({src: emojiUrlFor(icons[i]), title: icons[i]});
+
+ 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
icons.length,
modalClass: options.modalClass
diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
index 01b1c9216d..5d382e4ac0 100644
--- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6
+++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
@@ -22,7 +22,7 @@ import { attachAdditionalPanel } from 'discourse/widgets/header';
// If you add any methods to the API ensure you bump up this number
-const PLUGIN_API_VERSION = '0.8.6';
+const PLUGIN_API_VERSION = '0.8.7';
class PluginApi {
constructor(version, container) {
@@ -39,6 +39,25 @@ class PluginApi {
return this.container.lookup('current-user:main');
}
+ /**
+ * Allows you to overwrite or extend methods in a class.
+ *
+ * For example:
+ *
+ * ```
+ * api.modifyClass('controller:composer', {
+ * actions: {
+ * newActionHere() { }
+ * }
+ * });
+ * ```
+ **/
+ modifyClass(resolverName, changes) {
+ const klass = this.container.factoryFor(resolverName);
+ klass.class.reopen(changes);
+ return klass;
+ }
+
/**
* Used for decorating the `cooked` content of a post after it is rendered using
* jQuery.
@@ -61,7 +80,7 @@ class PluginApi {
if (!opts.onlyStream) {
decorate(ComposerEditor, 'previewRefreshed', callback);
- decorate(this.container.lookupFactory('component:user-stream'), 'didInsertElement', callback);
+ decorate(this.container.factoryFor('component:user-stream').class, 'didInsertElement', callback);
}
}
@@ -170,7 +189,7 @@ class PluginApi {
* ```
**/
attachWidgetAction(widget, actionName, fn) {
- const widgetClass = this.container.lookupFactory(`widget:${widget}`);
+ const widgetClass = this.container.factoryFor(`widget:${widget}`).class;
widgetClass.prototype[actionName] = fn;
}
diff --git a/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6 b/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6
index a57f46c9eb..fcad355343 100644
--- a/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6
+++ b/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6
@@ -56,7 +56,7 @@ export default Ember.Object.extend(Ember.Array, {
},
finishedPrepending(postIds) {
- this._changeArray(Ember.K, 0, 0, postIds.length);
+ this._changeArray(function() { }, 0, 0, postIds.length);
},
objectAt(index) {
diff --git a/app/assets/javascripts/discourse/lib/text.js.es6 b/app/assets/javascripts/discourse/lib/text.js.es6
index dc3c1cdf97..476ca3ea9e 100644
--- a/app/assets/javascripts/discourse/lib/text.js.es6
+++ b/app/assets/javascripts/discourse/lib/text.js.es6
@@ -2,24 +2,39 @@ import { default as PrettyText, buildOptions } from 'pretty-text/pretty-text';
import { performEmojiUnescape, buildEmojiUrl } from 'pretty-text/emoji';
import WhiteLister from 'pretty-text/white-lister';
import { sanitize as textSanitize } from 'pretty-text/sanitizer';
+import loadScript from 'discourse/lib/load-script';
-function getOpts() {
+function getOpts(opts) {
const siteSettings = Discourse.__container__.lookup('site-settings:main');
- return buildOptions({
+ opts = _.merge({
getURL: Discourse.getURLWithCDN,
currentUser: Discourse.__container__.lookup('current-user:main'),
siteSettings
- });
+ }, opts);
+
+ return buildOptions(opts);
}
// Use this to easily create a pretty text instance with proper options
-export function cook(text) {
- return new Handlebars.SafeString(new PrettyText(getOpts()).cook(text));
+export function cook(text, options) {
+ return new Handlebars.SafeString(new PrettyText(getOpts(options)).cook(text));
}
-export function sanitize(text) {
- return textSanitize(text, new WhiteLister(getOpts()));
+// everything should eventually move to async API and this should be renamed
+// cook
+export function cookAsync(text, options) {
+ if (Discourse.MarkdownItURL) {
+ return loadScript(Discourse.MarkdownItURL)
+ .then(()=>cook(text, options));
+ } else {
+ return Ember.RSVP.Promise.resolve(cook(text));
+ }
+}
+
+
+export function sanitize(text, options) {
+ return textSanitize(text, new WhiteLister(options));
}
function emojiOptions() {
diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6
index 5ce1cfa9b3..1275f06f25 100644
--- a/app/assets/javascripts/discourse/lib/url.js.es6
+++ b/app/assets/javascripts/discourse/lib/url.js.es6
@@ -221,6 +221,11 @@ const DiscourseURL = Ember.Object.extend({
// TODO: Extract into rules we can inject into the URL handler
if (this.navigatedToHome(oldPath, path, opts)) { return; }
+ // Navigating to empty string is the same as root
+ if (path === '') {
+ path = '/';
+ }
+
return this.handleURL(path, opts);
},
@@ -367,7 +372,7 @@ const DiscourseURL = Ember.Object.extend({
discoveryTopics.resetParams();
}
- router.router.updateURL(path);
+ router._routerMicrolib.updateURL(path);
}
const split = path.split('#');
diff --git a/app/assets/javascripts/discourse/lib/utilities.js.es6 b/app/assets/javascripts/discourse/lib/utilities.js.es6
index d80b84cabc..a1765cf9c6 100644
--- a/app/assets/javascripts/discourse/lib/utilities.js.es6
+++ b/app/assets/javascripts/discourse/lib/utilities.js.es6
@@ -172,7 +172,7 @@ export function validateUploadedFiles(files, opts) {
}
opts = opts || {};
- opts["type"] = uploadTypeFromFileName(upload.name);
+ opts.type = uploadTypeFromFileName(upload.name);
return validateUploadedFile(upload, opts);
}
@@ -185,12 +185,18 @@ export function validateUploadedFile(file, opts) {
if (!name) { return false; }
// check that the uploaded file is authorized
- if (opts["imagesOnly"]) {
+ if (opts.allowStaffToUploadAnyFileInPm && opts.isPrivateMessage) {
+ if (Discourse.User.current("staff")) {
+ return true;
+ }
+ }
+
+ if (opts.imagesOnly) {
if (!isAnImage(name) && !isAuthorizedImage(name)) {
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedImagesExtensions() }));
return false;
}
- } else if (opts["csvOnly"]) {
+ } else if (opts.csvOnly) {
if (!(/\.csv$/i).test(name)) {
bootbox.alert(I18n.t('user.invited.bulk_invite.error'));
return false;
@@ -202,10 +208,10 @@ export function validateUploadedFile(file, opts) {
}
}
- if (!opts["bypassNewUserRestriction"]) {
+ if (!opts.bypassNewUserRestriction) {
// ensures that new users can upload a file
- if (!Discourse.User.current().isAllowedToUploadAFile(opts["type"])) {
- bootbox.alert(I18n.t(`post.errors.${opts["type"]}_upload_not_allowed_for_new_user`));
+ if (!Discourse.User.current().isAllowedToUploadAFile(opts.type)) {
+ bootbox.alert(I18n.t(`post.errors.${opts.type}_upload_not_allowed_for_new_user`));
return false;
}
}
diff --git a/app/assets/javascripts/discourse/mixins/key-enter-escape.js.es6 b/app/assets/javascripts/discourse/mixins/key-enter-escape.js.es6
new file mode 100644
index 0000000000..f9037da98d
--- /dev/null
+++ b/app/assets/javascripts/discourse/mixins/key-enter-escape.js.es6
@@ -0,0 +1,14 @@
+// A mixin where hitting ESC calls `cancelled` and ctrl+enter calls `save.
+export default {
+ keyDown(e) {
+ if (e.which === 27) {
+ this.sendAction('cancelled');
+ return false;
+ } else if (e.which === 13 && (e.ctrlKey || e.metaKey)) {
+ // CTRL+ENTER or CMD+ENTER
+ this.sendAction('save');
+ return false;
+ }
+ },
+};
+
diff --git a/app/assets/javascripts/discourse/mixins/scrolling.js.es6 b/app/assets/javascripts/discourse/mixins/scrolling.js.es6
index c2997fa395..bbd21aca02 100644
--- a/app/assets/javascripts/discourse/mixins/scrolling.js.es6
+++ b/app/assets/javascripts/discourse/mixins/scrolling.js.es6
@@ -31,7 +31,7 @@ const Scrolling = Ember.Mixin.create({
opts = opts || { debounce: 100 };
// So we can not call the scrolled event while transitioning
- const router = Discourse.__container__.lookup('router:main').router;
+ const router = Discourse.__container__.lookup('router:main')._routerMicrolib;
let onScrollMethod = () => {
if (router.activeTransition) { return; }
diff --git a/app/assets/javascripts/discourse/models/admin-post.js.es6 b/app/assets/javascripts/discourse/models/admin-post.js.es6
deleted file mode 100644
index 8de331ac95..0000000000
--- a/app/assets/javascripts/discourse/models/admin-post.js.es6
+++ /dev/null
@@ -1,26 +0,0 @@
-import Post from 'discourse/models/post';
-
-export default Post.extend({
-
- _attachCategory: function () {
- const categoryId = this.get("category_id");
- if (categoryId) {
- this.set("category", Discourse.Category.findById(categoryId));
- }
- }.on("init"),
-
- presentName: Ember.computed.or('name', 'username'),
-
- sameUser: function() {
- return this.get("username") === Discourse.User.currentProp("username");
- }.property("username"),
-
- descriptionKey: function () {
- if (this.get("reply_to_post_number")) {
- return this.get("sameUser") ? "you_replied_to_post" : "user_replied_to_post";
- } else {
- return this.get("sameUser") ? "you_replied_to_topic" : "user_replied_to_topic";
- }
- }.property("reply_to_post_number", "sameUser")
-
-});
diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6
index a85025e410..67c70e510b 100644
--- a/app/assets/javascripts/discourse/models/group.js.es6
+++ b/app/assets/javascripts/discourse/models/group.js.es6
@@ -2,7 +2,6 @@ import { ajax } from 'discourse/lib/ajax';
import { default as computed, observes } from "ember-addons/ember-computed-decorators";
import GroupHistory from 'discourse/models/group-history';
import RestModel from 'discourse/models/rest';
-import { popupAjaxError } from 'discourse/lib/ajax-error';
const Group = RestModel.extend({
limit: 50,
@@ -114,23 +113,27 @@ const Group = RestModel.extend({
return aliasLevel === '99';
},
- @observes("visible", "canEveryoneMention")
+ @observes("visibility_level", "canEveryoneMention")
_updateAllowMembershipRequests() {
- if (!this.get('visible') || !this.get('canEveryoneMention')) {
+ if (this.get('visibility_level') !== 0 || !this.get('canEveryoneMention')) {
this.set ('allow_membership_requests', false);
}
},
- @observes("visible")
+ @observes("visibility_level")
_updatePublic() {
- if (!this.get('visible')) this.set('public', false);
+ let visibility_level = parseInt(this.get('visibility_level'));
+ if (visibility_level !== 0) {
+ this.set('public', false);
+ this.set('allow_membership_requests', false);
+ }
},
asJSON() {
return {
name: this.get('name'),
alias_level: this.get('alias_level'),
- visible: !!this.get('visible'),
+ visibility_level: this.get('visibility_level'),
automatic_membership_email_domains: this.get('emailDomains'),
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive'),
title: this.get('title'),
@@ -202,7 +205,13 @@ const Group = RestModel.extend({
data: { notification_level, user_id: userId },
type: "POST"
});
- }
+ },
+
+ requestMembership() {
+ return ajax(`/groups/${this.get('name')}/request_membership`, {
+ type: "POST"
+ });
+ },
});
Group.reopenClass({
@@ -216,10 +225,6 @@ Group.reopenClass({
return ajax("/groups/" + name + ".json").then(result => Group.create(result.basic_group));
},
- loadOwners(name) {
- return ajax('/groups/' + name + '/owners.json').catch(popupAjaxError);
- },
-
loadMembers(name, offset, limit, params) {
return ajax('/groups/' + name + '/members.json', {
data: _.extend({
diff --git a/app/assets/javascripts/discourse/models/invite.js.es6 b/app/assets/javascripts/discourse/models/invite.js.es6
index 1425e63b25..b3373f1080 100644
--- a/app/assets/javascripts/discourse/models/invite.js.es6
+++ b/app/assets/javascripts/discourse/models/invite.js.es6
@@ -58,6 +58,10 @@ Invite.reopenClass({
reinviteAll() {
return ajax('/invites/reinvite-all', { type: 'POST' });
+ },
+
+ rescindAll() {
+ return ajax('/invites/rescind-all', { type: 'POST' });
}
});
diff --git a/app/assets/javascripts/discourse/models/rest.js.es6 b/app/assets/javascripts/discourse/models/rest.js.es6
index 0c595bb735..ed78efd752 100644
--- a/app/assets/javascripts/discourse/models/rest.js.es6
+++ b/app/assets/javascripts/discourse/models/rest.js.es6
@@ -3,7 +3,7 @@ const RestModel = Ember.Object.extend({
isCreated: Ember.computed.equal('__state', 'created'),
isSaving: false,
- afterUpdate: Ember.K,
+ afterUpdate() { },
update(props) {
if (this.get('isSaving')) { return Ember.RSVP.reject(); }
diff --git a/app/assets/javascripts/discourse/models/store.js.es6 b/app/assets/javascripts/discourse/models/store.js.es6
index 0d1ef7cdac..96086f068f 100644
--- a/app/assets/javascripts/discourse/models/store.js.es6
+++ b/app/assets/javascripts/discourse/models/store.js.es6
@@ -297,7 +297,16 @@ export default Ember.Object.extend({
if (existing) {
delete obj.id;
- const klass = this.register.lookupFactory('model:' + type) || RestModel;
+ let klass = this.register.lookupFactory('model:' + type);
+
+ if (klass && klass.class) {
+ klass = klass.class;
+ }
+
+ if (!klass) {
+ klass = RestModel;
+ }
+
existing.setProperties(klass.munge(obj));
obj.id = id;
return existing;
diff --git a/app/assets/javascripts/discourse/models/user-posts-stream.js.es6 b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6
index cd552d35db..6a96803bdd 100644
--- a/app/assets/javascripts/discourse/models/user-posts-stream.js.es6
+++ b/app/assets/javascripts/discourse/models/user-posts-stream.js.es6
@@ -1,6 +1,6 @@
import { ajax } from 'discourse/lib/ajax';
import { url } from 'discourse/lib/computed';
-import AdminPost from 'discourse/models/admin-post';
+import UserAction from 'discourse/models/user-action';
export default Discourse.Model.extend({
loaded: false,
@@ -36,7 +36,7 @@ export default Discourse.Model.extend({
return ajax(this.get("url"), { cache: false }).then(function (result) {
if (result) {
- const posts = result.map(function (post) { return AdminPost.create(post); });
+ const posts = result.map(function (post) { return UserAction.create(post); });
self.get("content").pushObjects(posts);
self.setProperties({
loaded: true,
diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6
index 2c800ff199..60e895a81b 100644
--- a/app/assets/javascripts/discourse/routes/application.js.es6
+++ b/app/assets/javascripts/discourse/routes/application.js.es6
@@ -94,6 +94,7 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
showCreateAccount: unlessReadOnly('handleShowCreateAccount', I18n.t("read_only_mode.login_disabled")),
showForgotPassword() {
+ this.controllerFor('forgot-password').setProperties({ offerHelp: null, helpSeen: false });
showModal('forgotPassword', { title: 'forgot_password.title' });
},
diff --git a/app/assets/javascripts/discourse/routes/invites-show.js.es6 b/app/assets/javascripts/discourse/routes/invites-show.js.es6
index 9acc266ced..10d8515258 100644
--- a/app/assets/javascripts/discourse/routes/invites-show.js.es6
+++ b/app/assets/javascripts/discourse/routes/invites-show.js.es6
@@ -8,6 +8,8 @@ export default Discourse.Route.extend({
model(params) {
if (PreloadStore.get("invite_info")) {
return PreloadStore.getAndRemove("invite_info").then(json => _.merge(params, json));
+ } else {
+ return {};
}
}
});
diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6
index 7f05b9f3eb..5a2590d279 100644
--- a/app/assets/javascripts/discourse/routes/topic.js.es6
+++ b/app/assets/javascripts/discourse/routes/topic.js.es6
@@ -218,6 +218,10 @@ const TopicRoute = Discourse.Route.extend({
// We reset screen tracking every time a topic is entered
this.screenTrack.start(model.get('id'), controller);
+
+ Ember.run.scheduleOnce('afterRender', () => {
+ this.appEvents.trigger('header:update-topic', model);
+ });
}
});
diff --git a/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6
index 4fbf8e4300..13fe328d30 100644
--- a/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6
+++ b/app/assets/javascripts/discourse/routes/user-activity-stream.js.es6
@@ -19,26 +19,9 @@ export default Discourse.Route.extend(ViewingActionType, {
},
actions: {
-
didTransition() {
this.controllerFor("user-activity")._showFooter();
return true;
- },
-
- removeBookmark(userAction) {
- var user = this.modelFor("user");
- Discourse.Post.updateBookmark(userAction.get("post_id"), false)
- .then(function() {
- // remove the user action from the stream
- user.get("stream").remove(userAction);
- // update the counts
- user.get("stats").forEach(function (stat) {
- if (stat.get("action_type") === userAction.action_type) {
- stat.decrementProperty("count");
- }
- });
- });
- },
-
+ }
}
});
diff --git a/app/assets/javascripts/discourse/templates/components/cook-text.hbs b/app/assets/javascripts/discourse/templates/components/cook-text.hbs
new file mode 100644
index 0000000000..1732a78191
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/components/cook-text.hbs
@@ -0,0 +1 @@
+{{cooked}}
diff --git a/app/assets/javascripts/discourse/templates/components/d-button.hbs b/app/assets/javascripts/discourse/templates/components/d-button.hbs
index 47b714b8dc..e01a9bb5c8 100644
--- a/app/assets/javascripts/discourse/templates/components/d-button.hbs
+++ b/app/assets/javascripts/discourse/templates/components/d-button.hbs
@@ -2,5 +2,8 @@
{{fa-icon icon}}
{{/if}}
-{{{translatedLabel}}}
+{{#if translatedLabel}}
+ {{{translatedLabel}}}
+{{/if}}
+
{{yield}}
diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs
index dacde9aa74..78d00be874 100644
--- a/app/assets/javascripts/discourse/templates/components/d-editor.hbs
+++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs
@@ -30,5 +30,6 @@
{{{preview}}}
+ {{plugin-outlet name="editor-preview"}}