diff --git a/app/assets/javascripts/admin/components/embedding-setting.js.es6 b/app/assets/javascripts/admin/components/embedding-setting.js.es6 index 039e5d5dc38..f1d31fcf68b 100644 --- a/app/assets/javascripts/admin/components/embedding-setting.js.es6 +++ b/app/assets/javascripts/admin/components/embedding-setting.js.es6 @@ -6,11 +6,6 @@ export default Ember.Component.extend({ @computed('field') inputId(field) { return field.dasherize(); }, - @computed('placeholder') - placeholderValue(placeholder) { - return placeholder ? I18n.t(placeholder) : null; - }, - @computed('field') translationKey(field) { return `admin.embedding.${field}`; }, @@ -21,7 +16,7 @@ export default Ember.Component.extend({ checked: { get(value) { return !!value; }, set(value) { - this.set('value', value); + this.set('value', value); return value; } } diff --git a/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 b/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 index f259acba84f..8a3fd0b9609 100644 --- a/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-email-preview-digest.js.es6 @@ -5,7 +5,7 @@ export default Ember.Controller.extend({ const model = this.get('model'); this.set('loading', true); - Discourse.EmailPreview.findDigest(this.get('lastSeen')).then(email => { + Discourse.EmailPreview.findDigest(this.get('lastSeen'), this.get('username')).then(email => { model.setProperties(email.getProperties('html_content', 'text_content')); this.set('loading', false); }); diff --git a/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 new file mode 100644 index 00000000000..6628a6fa726 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin-groups-bulk.js.es6 @@ -0,0 +1,34 @@ +import computed from 'ember-addons/ember-computed-decorators'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; + +export default Ember.Controller.extend({ + users: null, + groupId: null, + saving: false, + + @computed('saving', 'users', 'groupId') + buttonDisabled(saving, users, groupId) { + return saving || !groupId || !users || !users.length; + }, + + actions: { + addToGroup() { + if (this.get('saving')) { return; } + + const users = this.get('users').split("\n") + .uniq() + .reject(x => x.length === 0); + + this.set('saving', true); + Discourse.ajax('/admin/groups/bulk', { + data: { users, group_id: this.get('groupId') }, + method: 'PUT' + }).then(() => { + this.transitionToRoute('adminGroups.bulkComplete'); + }).catch(popupAjaxError).finally(() => { + this.set('saving', false); + }); + + } + } +}); diff --git a/app/assets/javascripts/admin/models/admin-user.js.es6 b/app/assets/javascripts/admin/models/admin-user.js.es6 index 1e83de62065..20a9579bd62 100644 --- a/app/assets/javascripts/admin/models/admin-user.js.es6 +++ b/app/assets/javascripts/admin/models/admin-user.js.es6 @@ -288,10 +288,7 @@ const AdminUser = Discourse.User.extend({ data: { username: this.get('username') } }).then(function() { bootbox.alert( I18n.t('admin.user.activation_email_sent') ); - }).catch(function(e) { - var error = I18n.t('admin.user.send_activation_email_failed', { error: "http: " + e.status + " - " + e.body }); - bootbox.alert(error); - }); + }).catch(popupAjaxError); }, anonymizeForbidden: Em.computed.not("can_be_anonymized"), diff --git a/app/assets/javascripts/admin/models/email_preview.js b/app/assets/javascripts/admin/models/email_preview.js index b754095812c..8ae9bdfcd4f 100644 --- a/app/assets/javascripts/admin/models/email_preview.js +++ b/app/assets/javascripts/admin/models/email_preview.js @@ -9,18 +9,20 @@ Discourse.EmailPreview = Discourse.Model.extend({}); Discourse.EmailPreview.reopenClass({ - findDigest: function(lastSeenAt) { + findDigest: function(lastSeenAt, username) { if (Em.isEmpty(lastSeenAt)) { lastSeenAt = moment().subtract(7, 'days').format('YYYY-MM-DD'); } + if (Em.isEmpty(username)) { + username = Discourse.User.current().username; + } + return Discourse.ajax("/admin/email/preview-digest.json", { - data: {last_seen_at: lastSeenAt} + data: { last_seen_at: lastSeenAt, username: username } }).then(function (result) { return Discourse.EmailPreview.create(result); }); } }); - - diff --git a/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 b/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 new file mode 100644 index 00000000000..8d9554556f8 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin-groups-bulk.js.es6 @@ -0,0 +1,13 @@ +import Group from 'discourse/models/group'; + +export default Ember.Route.extend({ + model() { + return Group.findAll().then(groups => { + return groups.filter(g => !g.get('automatic')); + }); + }, + + setupController(controller, groups) { + controller.setProperties({ groups, groupId: null, users: null }); + } +}); diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index e01d0f8f0d6..dd758556c60 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -49,6 +49,8 @@ export default { }); this.resource('adminGroups', { path: '/groups' }, function() { + this.route('bulk'); + this.route('bulkComplete', { path: 'bulk-complete' }); this.resource('adminGroupsType', { path: '/:type' }, function() { this.resource('adminGroup', { path: '/:name' }); }); diff --git a/app/assets/javascripts/admin/templates/components/embedding-setting.hbs b/app/assets/javascripts/admin/templates/components/embedding-setting.hbs index e3a99c3202b..eb63c40af9e 100644 --- a/app/assets/javascripts/admin/templates/components/embedding-setting.hbs +++ b/app/assets/javascripts/admin/templates/components/embedding-setting.hbs @@ -5,7 +5,7 @@ {{else}} - {{input value=value id=inputId placeholder=placeholderValue}} + {{input value=value id=inputId placeholder=placeholder}} {{/if}}
diff --git a/app/assets/javascripts/admin/templates/email_preview_digest.hbs b/app/assets/javascripts/admin/templates/email_preview_digest.hbs index 83d99db35ff..630260f1317 100644 --- a/app/assets/javascripts/admin/templates/email_preview_digest.hbs +++ b/app/assets/javascripts/admin/templates/email_preview_digest.hbs @@ -4,6 +4,8 @@
{{input type="date" value=lastSeen id="last-seen"}} + + {{user-selector single="true" usernames=username}}
diff --git a/app/assets/javascripts/admin/templates/embedding.hbs b/app/assets/javascripts/admin/templates/embedding.hbs index d41970fb849..30f279105dd 100644 --- a/app/assets/javascripts/admin/templates/embedding.hbs +++ b/app/assets/javascripts/admin/templates/embedding.hbs @@ -48,11 +48,11 @@ {{embedding-setting field="embed_whitelist_selector" value=embedding.embed_whitelist_selector - placeholder="admin.embedding.whitelist_example"}} + placeholder="article, #story, .post"}} {{embedding-setting field="embed_blacklist_selector" value=embedding.embed_blacklist_selector - placeholder="admin.embedding.blacklist_example"}} + placeholder=".ad-unit, header"}}
diff --git a/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs b/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs new file mode 100644 index 00000000000..51eb3e4394b --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups-bulk-complete.hbs @@ -0,0 +1 @@ +

{{i18n "admin.groups.bulk_complete"}}

diff --git a/app/assets/javascripts/admin/templates/groups-bulk.hbs b/app/assets/javascripts/admin/templates/groups-bulk.hbs new file mode 100644 index 00000000000..baf3a63cda0 --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups-bulk.hbs @@ -0,0 +1,19 @@ +
+

{{i18n "admin.groups.bulk_paste"}}

+ +
+ {{textarea value=users class="paste-users"}} +
+ +
+ {{combo-box content=groups valueAttribute="id" value=groupId none="admin.groups.bulk_select"}} +
+ +
+ {{d-button disabled=buttonDisabled + class="btn-primary" + action="addToGroup" + icon="plus" + label="admin.groups.bulk"}} +
+
diff --git a/app/assets/javascripts/admin/templates/groups.hbs b/app/assets/javascripts/admin/templates/groups.hbs index 2d767c38442..aa7d9213ca8 100644 --- a/app/assets/javascripts/admin/templates/groups.hbs +++ b/app/assets/javascripts/admin/templates/groups.hbs @@ -1,6 +1,7 @@ {{#admin-nav}} {{nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}} {{nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}} + {{nav-item route='adminGroups.bulk' label='admin.groups.bulk'}} {{/admin-nav}}
diff --git a/app/assets/javascripts/admin/templates/site-text-edit.hbs b/app/assets/javascripts/admin/templates/site-text-edit.hbs index 438887f85fe..2a6693e8f3f 100644 --- a/app/assets/javascripts/admin/templates/site-text-edit.hbs +++ b/app/assets/javascripts/admin/templates/site-text-edit.hbs @@ -2,7 +2,7 @@

{{model.description}}

{{#if model.markdown}} - {{pagedown-editor value=model.value}} + {{d-editor value=model.value}} {{/if}} {{#if model.plainText}} {{textarea value=model.value class="plain"}} diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index 9c790e3fe42..96f4c90c6dd 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -14,7 +14,7 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, { if (!url) return url; // if it's a non relative URL, return it. - if (!/^\/[^\/]/.test(url)) return url; + if (url !== '/' && !/^\/[^\/]/.test(url)) return url; var u = Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri; diff --git a/app/assets/javascripts/discourse/components/d-editor-modal.js.es6 b/app/assets/javascripts/discourse/components/d-editor-modal.js.es6 new file mode 100644 index 00000000000..049914fe2c8 --- /dev/null +++ b/app/assets/javascripts/discourse/components/d-editor-modal.js.es6 @@ -0,0 +1,52 @@ +import { observes, on } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + classNameBindings: [':d-editor-modal', 'hidden'], + + @observes('hidden') + _hiddenChanged() { + if (!this.get('hidden')) { + Ember.run.scheduleOnce('afterRender', () => { + const $modal = this.$(); + const $parent = this.$().closest('.d-editor'); + const w = $parent.width(); + const h = $parent.height(); + $modal.css({ left: (w / 2) - ($modal.outerWidth() / 2) }); + parent.$('.d-editor-overlay').removeClass('hidden').css({ width: w, height: h}); + this.$('input').focus(); + }); + } else { + parent.$('.d-editor-overlay').addClass('hidden'); + } + }, + + @on('didInsertElement') + _listenKeys() { + this.$().on('keydown.d-modal', key => { + if (this.get('hidden')) { return; } + + if (key.keyCode === 27) { + this.send('cancel'); + } + if (key.keyCode === 13) { + this.send('ok'); + } + }); + }, + + @on('willDestroyElement') + _stopListening() { + this.$().off('keydown.d-modal'); + }, + + actions: { + ok() { + this.set('hidden', true); + this.sendAction('okAction'); + }, + + cancel() { + this.set('hidden', true); + } + } +}); diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 new file mode 100644 index 00000000000..879cb9c6434 --- /dev/null +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -0,0 +1,263 @@ +import loadScript from 'discourse/lib/load-script'; +import { default as property, on } from 'ember-addons/ember-computed-decorators'; +import { showSelector } from "discourse/lib/emoji/emoji-toolbar"; + +// Our head can be a static string or a function that returns a string +// based on input (like for numbered lists). +function getHead(head, prev) { + if (typeof head === "string") { + return [head, head.length]; + } else { + return getHead(head(prev)); + } +} + +export default Ember.Component.extend({ + classNames: ['d-editor'], + ready: false, + insertLinkHidden: true, + link: '', + lastSel: null, + + @on('didInsertElement') + _loadSanitizer() { + this._applyEmojiAutocomplete(); + loadScript('defer/html-sanitizer-bundle').then(() => this.set('ready', true)); + }, + + @property('ready', 'value') + preview(ready, value) { + if (!ready) { return; } + + const text = Discourse.Dialect.cook(value || "", {sanitize: true}); + return text ? text : ""; + }, + + _applyEmojiAutocomplete() { + if (!this.siteSettings.enable_emoji) { return; } + + const container = this.container; + const template = container.lookup('template:emoji-selector-autocomplete.raw'); + const self = this; + + this.$('.d-editor-input').autocomplete({ + template: template, + key: ":", + + transformComplete(v) { + if (v.code) { + return `${v.code}:`; + } else { + showSelector({ + appendTo: self.$(), + container, + onSelect: title => self._addText(`${title}:`) + }); + return ""; + } + }, + + dataSource(term) { + return new Ember.RSVP.Promise(resolve => { + const full = `:${term}`; + term = term.toLowerCase(); + + if (term === "") { + return resolve(["smile", "smiley", "wink", "sunny", "blush"]); + } + + if (Discourse.Emoji.translations[full]) { + return resolve([Discourse.Emoji.translations[full]]); + } + + const options = Discourse.Emoji.search(term, {maxResults: 5}); + + return resolve(options); + }).then(list => list.map(code => { + return {code, src: Discourse.Emoji.urlFor(code)}; + })).then(list => { + if (list.length) { + list.push({ label: I18n.t("composer.more_emoji") }); + } + return list; + }); + } + }); + }, + + _getSelected() { + if (!this.get('ready')) { return; } + + const textarea = this.$('textarea.d-editor-input')[0]; + let start = textarea.selectionStart; + let end = textarea.selectionEnd; + + if (start === end) { + start = end = textarea.value.length; + } + + const value = textarea.value.substring(start, end); + const pre = textarea.value.slice(0, start); + const post = textarea.value.slice(end); + + return { start, end, value, pre, post }; + }, + + _selectText(from, length) { + Ember.run.scheduleOnce('afterRender', () => { + const textarea = this.$('textarea.d-editor-input')[0]; + textarea.focus(); + textarea.selectionStart = from; + textarea.selectionEnd = textarea.selectionStart + length; + }); + }, + + _applySurround(head, tail, exampleKey) { + const sel = this._getSelected(); + const pre = sel.pre; + const post = sel.post; + + const tlen = tail.length; + if (sel.start === sel.end) { + if (tlen === 0) { return; } + + const [hval, hlen] = getHead(head); + const example = I18n.t(`composer.${exampleKey}`); + this.set('value', `${pre}${hval}${example}${tail}${post}`); + this._selectText(pre.length + hlen, example.length); + } else { + const lines = sel.value.split("\n"); + + let [hval, hlen] = getHead(head); + if (lines.length === 1 && pre.slice(-tlen) === tail && post.slice(0, hlen) === hval) { + this.set('value', `${pre.slice(0, -hlen)}${sel.value}${post.slice(tlen)}`); + this._selectText(sel.start - hlen, sel.value.length); + } else { + const contents = lines.map(l => { + if (l.length === 0) { return l; } + + if (l.slice(0, hlen) === hval && tlen === 0 || l.slice(-tlen) === tail) { + if (tlen === 0) { + const result = l.slice(hlen); + [hval, hlen] = getHead(head, hval); + return result; + } else if (l.slice(-tlen) === tail) { + const result = l.slice(hlen, -tlen); + [hval, hlen] = getHead(head, hval); + return result; + } + } + const result = `${hval}${l}${tail}`; + [hval, hlen] = getHead(head, hval); + return result; + }).join("\n"); + + this.set('value', `${pre}${contents}${post}`); + if (lines.length === 1 && tlen > 0) { + this._selectText(sel.start + hlen, contents.length - hlen - hlen); + } else { + this._selectText(sel.start, contents.length); + } + } + } + }, + + _applyList(head, exampleKey) { + const sel = this._getSelected(); + if (sel.value.indexOf("\n") !== -1) { + this._applySurround(head, '', exampleKey); + } else { + + const [hval, hlen] = getHead(head); + if (sel.start === sel.end) { + sel.value = I18n.t(`composer.${exampleKey}`); + } + + const trimmedPre = sel.pre.trim(); + const number = (sel.value.indexOf(hval) === 0) ? sel.value.slice(hlen) : `${hval}${sel.value}`; + const preLines = trimmedPre.length ? `${trimmedPre}\n\n` : ""; + + const trimmedPost = sel.post.trim(); + const post = trimmedPost.length ? `\n\n${trimmedPost}` : trimmedPost; + + this.set('value', `${preLines}${number}${post}`); + this._selectText(preLines.length, number.length); + } + }, + + _addText(text, sel) { + sel = sel || this._getSelected(); + const insert = `${sel.pre}${text}`; + this.set('value', `${insert}${sel.post}`); + this._selectText(insert.length, 0); + }, + + actions: { + bold() { + this._applySurround('**', '**', 'bold_text'); + }, + + italic() { + this._applySurround('*', '*', 'italic_text'); + }, + + showLinkModal() { + this._lastSel = this._getSelected(); + this.set('insertLinkHidden', false); + }, + + insertLink() { + const link = this.get('link'); + + if (Ember.isEmpty(link)) { return; } + const m = / "([^"]+)"/.exec(link); + if (m && m.length === 2) { + const description = m[1]; + const remaining = link.replace(m[0], ''); + this._addText(`[${description}](${remaining})`, this._lastSel); + } else { + this._addText(`[${link}](${link})`, this._lastSel); + } + + this.set('link', ''); + }, + + code() { + const sel = this._getSelected(); + if (sel.value.indexOf("\n") !== -1) { + this._applySurround(' ', '', 'code_text'); + } else { + this._applySurround('`', '`', 'code_text'); + } + }, + + quote() { + this._applySurround('> ', "", 'code_text'); + }, + + bullet() { + this._applyList('* ', 'list_item'); + }, + + list() { + this._applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item'); + }, + + heading() { + this._applyList('## ', 'heading_text'); + }, + + rule() { + this._addText("\n\n----------\n"); + }, + + emoji() { + showSelector({ + appendTo: this.$(), + container: this.container, + onSelect: title => this._addText(`:${title}:`) + }); + } + } + +}); diff --git a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 index 910b8d661af..e10c01c84d8 100644 --- a/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 +++ b/app/assets/javascripts/discourse/components/hamburger-menu.js.es6 @@ -56,6 +56,13 @@ export default Ember.Component.extend({ }); }, + @computed() + showUserDirectoryLink() { + if (!this.siteSettings.enable_user_directory) return false; + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) return false; + return true; + }, + actions: { keyboardShortcuts() { this.sendAction('showKeyboardAction'); diff --git a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 index 447184410c1..387f7ae8f17 100644 --- a/app/assets/javascripts/discourse/components/header-dropdown.js.es6 +++ b/app/assets/javascripts/discourse/components/header-dropdown.js.es6 @@ -4,9 +4,9 @@ export default Ember.Component.extend({ tagName: 'li', classNameBindings: [':header-dropdown-toggle', 'active'], - @computed('showUser') - href(showUser) { - return showUser ? this.currentUser.get('path') : ''; + @computed('showUser', 'path') + href(showUser, path) { + return showUser ? this.currentUser.get('path') : Discourse.getURL(path); }, active: Ember.computed.alias('toggleVisible'), diff --git a/app/assets/javascripts/discourse/components/pagedown-editor.js.es6 b/app/assets/javascripts/discourse/components/pagedown-editor.js.es6 deleted file mode 100644 index 6f04e77152b..00000000000 --- a/app/assets/javascripts/discourse/components/pagedown-editor.js.es6 +++ /dev/null @@ -1,25 +0,0 @@ -import { observes, on } from 'ember-addons/ember-computed-decorators'; -import loadScript from 'discourse/lib/load-script'; - -export default Ember.Component.extend({ - classNameBindings: [':pagedown-editor'], - - @on("didInsertElement") - _initializeWmd() { - loadScript('defer/html-sanitizer-bundle').then(() => { - this.$('.wmd-input').data('init', true); - this._editor = Discourse.Markdown.createEditor({ containerElement: this.element }); - this._editor.run(); - Ember.run.scheduleOnce('afterRender', this, this._refreshPreview); - }); - }, - - @observes("value") - observeValue() { - Ember.run.scheduleOnce('afterRender', this, this._refreshPreview); - }, - - _refreshPreview() { - this._editor.refreshPreview(); - } -}); diff --git a/app/assets/javascripts/discourse/components/private-message-map.js.es6 b/app/assets/javascripts/discourse/components/private-message-map.js.es6 index f8d402ba760..0d01910c722 100644 --- a/app/assets/javascripts/discourse/components/private-message-map.js.es6 +++ b/app/assets/javascripts/discourse/components/private-message-map.js.es6 @@ -9,7 +9,7 @@ export default Ember.Component.extend({ var self = this; bootbox.dialog(I18n.t("private_message_info.remove_allowed_user", {name: user.get('username')}), [ {label: I18n.t("no_value"), - 'class': 'btn-danger rightg'}, + 'class': 'btn-danger right'}, {label: I18n.t("yes_value"), 'class': 'btn-primary', callback: function() { diff --git a/app/assets/javascripts/discourse/components/signup-cta.js.es6 b/app/assets/javascripts/discourse/components/signup-cta.js.es6 index b1224306e89..b3567ac15b7 100644 --- a/app/assets/javascripts/discourse/components/signup-cta.js.es6 +++ b/app/assets/javascripts/discourse/components/signup-cta.js.es6 @@ -18,36 +18,6 @@ export default Ember.Component.extend({ } }, - signupMethodsTranslated: function() { - const methods = Ember.get('Discourse.LoginMethod.all'); - const loginWithEmail = this.siteSettings.enable_local_logins; - if (this.siteSettings.enable_sso) { - return I18n.t('signup_cta.methods.sso'); - } else if (methods.length === 0) { - if (loginWithEmail) { - return I18n.t('signup_cta.methods.only_email'); - } else { - return I18n.t('signup_cta.methods.unknown'); - } - } else if (methods.length === 1) { - let providerName = methods[0].name.capitalize(); - if (providerName === "Google_oauth2") { - providerName = "Google"; - } - if (loginWithEmail) { - return I18n.t('signup_cta.methods.one_and_email', {provider: providerName}); - } else { - return I18n.t('signup_cta.methods.only_other', {provider: providerName}); - } - } else { - if (loginWithEmail) { - return I18n.t('signup_cta.methods.multiple', {count: methods.length}); - } else { - return I18n.t('signup_cta.methods.multiple_no_email', {count: methods.length}); - } - } - }.property(), - _turnOffIfHidden: function() { if (this.session.get('hideSignupCta')) { this.session.set('showSignupCta', false); diff --git a/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 b/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 index 32aa7b514d5..6a34f559b03 100644 --- a/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 +++ b/app/assets/javascripts/discourse/controllers/avatar-selector.js.es6 @@ -21,8 +21,8 @@ export default Ember.Controller.extend(ModalFunctionality, { }, @computed() - allowImageUpload() { - return Discourse.Utilities.allowsImages(); + allowAvatarUpload() { + return this.siteSettings.allow_uploaded_avatars && Discourse.Utilities.allowsImages(); }, actions: { diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6 index b93363cb3be..b98a3285acf 100644 --- a/app/assets/javascripts/discourse/controllers/create-account.js.es6 +++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6 @@ -1,6 +1,7 @@ import debounce from 'discourse/lib/debounce'; import ModalFunctionality from 'discourse/mixins/modal-functionality'; import { setting } from 'discourse/lib/computed'; +import { on } from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend(ModalFunctionality, { needs: ['login'], @@ -78,10 +79,6 @@ export default Ember.Controller.extend(ModalFunctionality, { // Validate the name. nameValidation: function() { - if (this.get('accountPasswordConfirm') === 0) { - this.fetchConfirmationValue(); - } - if (Discourse.SiteSettings.full_name_required && Ember.isEmpty(this.get('accountName'))) { return Discourse.InputValidation.create({ failed: true }); } @@ -335,11 +332,11 @@ export default Ember.Controller.extend(ModalFunctionality, { }); }.property('accountPassword', 'rejectedPasswords.@each', 'accountUsername', 'accountEmail'), + @on('init') fetchConfirmationValue() { - const createAccountController = this; - return Discourse.ajax('/users/hp.json').then(function (json) { - createAccountController.set('accountPasswordConfirm', json.value); - createAccountController.set('accountChallenge', json.challenge.split("").reverse().join("")); + return Discourse.ajax('/users/hp.json').then(json => { + this.set('accountPasswordConfirm', json.value); + this.set('accountChallenge', json.challenge.split("").reverse().join("")); }); }, diff --git a/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 b/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 index c0d0b0178d9..8786828d7aa 100644 --- a/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery-sortable.js.es6 @@ -12,7 +12,7 @@ export var queryParams = { // Basic controller options var controllerOpts = { needs: ['discovery/topics'], - queryParams: Ember.keys(queryParams) + queryParams: Ember.keys(queryParams), }; // Aliases for the values diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 index 0a2b0462378..6426f75524f 100644 --- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 +++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 @@ -2,6 +2,7 @@ import DiscoveryController from 'discourse/controllers/discovery'; import { queryParams } from 'discourse/controllers/discovery-sortable'; import BulkTopicSelection from 'discourse/mixins/bulk-topic-selection'; import { endWith } from 'discourse/lib/computed'; +import showModal from 'discourse/lib/show-modal'; const controllerOpts = { needs: ['discovery'], @@ -66,10 +67,13 @@ const controllerOpts = { }); }, - resetNew() { this.topicTrackingState.resetNew(); Discourse.Topic.resetNew().then(() => this.send('refresh')); + }, + + dismissReadPosts() { + showModal('dismiss-read', { title: 'topics.bulk.dismiss_read' }); } }, diff --git a/app/assets/javascripts/discourse/controllers/group/members.js.es6 b/app/assets/javascripts/discourse/controllers/group/members.js.es6 index 794b5856a77..a22c82705ae 100644 --- a/app/assets/javascripts/discourse/controllers/group/members.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group/members.js.es6 @@ -11,7 +11,7 @@ export default Ember.Controller.extend({ this.set("loading", true); - Discourse.Group.loadMembers(this.get("name"), this.get("model.members.length"), this.get("limit")).then(result => { + Discourse.Group.loadMembers(this.get("model.name"), this.get("model.members.length"), this.get("limit")).then(result => { this.get("model.members").addObjects(result.members.map(member => Discourse.User.create(member))); this.setProperties({ loading: false, diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6 index 7befba51c2f..ffaaab94d1d 100644 --- a/app/assets/javascripts/discourse/controllers/invite.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invite.js.es6 @@ -68,12 +68,12 @@ export default Ember.Controller.extend(ModalFunctionality, { // Show Groups? (add invited user to private group) showGroups: function() { - return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && !this.get('isMessage'); + return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && Discourse.SiteSettings.enable_local_logins && !this.get('isMessage'); }.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic'), // Instructional text for the modal. inviteInstructions: function() { - if (Discourse.SiteSettings.enable_sso) { + if (Discourse.SiteSettings.enable_sso || !Discourse.SiteSettings.enable_local_logins) { // inviting existing user when SSO enabled return I18n.t('topic.invite_reply.sso_enabled'); } else if (this.get('isMessage')) { @@ -128,7 +128,7 @@ export default Ember.Controller.extend(ModalFunctionality, { }.property('isMessage'), placeholderKey: function() { - return Discourse.SiteSettings.enable_sso ? + return (Discourse.SiteSettings.enable_sso || !Discourse.SiteSettings.enable_local_logins) ? 'topic.invite_reply.username_placeholder' : 'topic.invite_private.email_or_username_placeholder'; }.property(), diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6 index 50abf282d73..157bbaf841d 100644 --- a/app/assets/javascripts/discourse/controllers/login.js.es6 +++ b/app/assets/javascripts/discourse/controllers/login.js.es6 @@ -78,9 +78,15 @@ export default Ember.Controller.extend(ModalFunctionality, { const $hidden_login_form = $('#hidden-login-form'); const destinationUrl = $.cookie('destination_url'); const shouldRedirectToUrl = self.session.get("shouldRedirectToUrl"); + const ssoDestinationUrl = $.cookie('sso_destination_url'); $hidden_login_form.find('input[name=username]').val(self.get('loginName')); $hidden_login_form.find('input[name=password]').val(self.get('loginPassword')); - if (destinationUrl) { + + if (ssoDestinationUrl) { + $.cookie('sso_destination_url', null); + window.location.assign(ssoDestinationUrl); + return; + } else if (destinationUrl) { // redirect client to the original URL $.cookie('destination_url', null); $hidden_login_form.find('input[name=redirect]').val(destinationUrl); diff --git a/app/assets/javascripts/discourse/controllers/preferences.js.es6 b/app/assets/javascripts/discourse/controllers/preferences.js.es6 index a7aa333fbca..f23f342e08f 100644 --- a/app/assets/javascripts/discourse/controllers/preferences.js.es6 +++ b/app/assets/javascripts/discourse/controllers/preferences.js.es6 @@ -5,12 +5,6 @@ import computed from "ember-addons/ember-computed-decorators"; export default Ember.Controller.extend(CanCheckEmails, { - allowAvatarUpload: setting('allow_uploaded_avatars'), - allowUserLocale: setting('allow_user_locale'), - ssoOverridesAvatar: setting('sso_overrides_avatar'), - allowBackgrounds: setting('allow_profile_backgrounds'), - editHistoryVisible: setting('edit_history_visible_to_public'), - @computed("model.watchedCategories", "model.trackedCategories", "model.mutedCategories") selectedCategories(watched, tracked, muted) { return [].concat(watched, tracked, muted); @@ -45,7 +39,7 @@ export default Ember.Controller.extend(CanCheckEmails, { @computed() nameInstructions() { - return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions'); + return I18n.t(this.siteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions'); }, @computed("model.has_title_badges") diff --git a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 index bd500049771..620decb81da 100644 --- a/app/assets/javascripts/discourse/controllers/quote-button.js.es6 +++ b/app/assets/javascripts/discourse/controllers/quote-button.js.es6 @@ -101,8 +101,10 @@ export default Ember.Controller.extend({ // defer load if needed, if in an expanded replies section if (!post) { const postStream = this.get('controllers.topic.model.postStream'); - postStream.loadPost(postId).then(() => this.quoteText()); - return; + return postStream.loadPost(postId).then(p => { + this.set('post', p); + return this.quoteText(); + }); } // If we can't create a post, delegate to reply as new topic 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 b5e8bcbb4bf..2dd79219e53 100644 --- a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 @@ -13,12 +13,13 @@ addBulkButton('closeTopics', 'close_topics'); addBulkButton('archiveTopics', 'archive_topics'); addBulkButton('showNotificationLevel', 'notification_level'); addBulkButton('resetRead', 'reset_read'); +addBulkButton('unlistTopics', 'unlist_topics'); // Modal for performing bulk actions on topics export default Ember.ArrayController.extend(ModalFunctionality, { buttonRows: null, - onShow: function() { + onShow() { this.set('controllers.modal.modalClass', 'topic-bulk-actions-modal small'); const buttonRows = []; @@ -36,87 +37,80 @@ export default Ember.ArrayController.extend(ModalFunctionality, { this.send('changeBulkTemplate', 'modal/bulk_actions_buttons'); }, - perform: function(operation) { + perform(operation) { this.set('loading', true); - var self = this, - topics = this.get('model'); - return Discourse.Topic.bulkOperation(this.get('model'), operation).then(function(result) { - self.set('loading', false); + const topics = this.get('model'); + return Discourse.Topic.bulkOperation(this.get('model'), operation).then(result => { + this.set('loading', false); if (result && result.topic_ids) { - return result.topic_ids.map(function (t) { - return topics.findBy('id', t); - }); + return result.topic_ids.map(t => topics.findBy('id', t)); } return result; - }).catch(function() { + }).catch(() => { bootbox.alert(I18n.t('generic_error')); - self.set('loading', false); + this.set('loading', false); }); }, - forEachPerformed: function(operation, cb) { - var self = this; - this.perform(operation).then(function (topics) { + forEachPerformed(operation, cb) { + this.perform(operation).then(topics => { if (topics) { topics.forEach(cb); - const refreshTarget = self.get('refreshTarget'); + const refreshTarget = this.get('refreshTarget'); if (refreshTarget) { refreshTarget.send('refresh'); } - self.send('closeModal'); + this.send('closeModal'); } }); }, - performAndRefresh: function(operation) { - const self = this; - return this.perform(operation).then(function() { - const refreshTarget = self.get('refreshTarget'); + performAndRefresh(operation) { + return this.perform(operation).then(() => { + const refreshTarget = this.get('refreshTarget'); if (refreshTarget) { refreshTarget.send('refresh'); } - self.send('closeModal'); + this.send('closeModal'); }); }, actions: { - showChangeCategory: function() { + showChangeCategory() { this.send('changeBulkTemplate', 'modal/bulk_change_category'); this.set('controllers.modal.modalClass', 'topic-bulk-actions-modal full'); }, - showNotificationLevel: function() { + showNotificationLevel() { this.send('changeBulkTemplate', 'modal/bulk_notification_level'); }, - deleteTopics: function() { + deleteTopics() { this.performAndRefresh({type: 'delete'}); }, - closeTopics: function() { - this.forEachPerformed({type: 'close'}, function(t) { - t.set('closed', true); - }); + closeTopics() { + this.forEachPerformed({type: 'close'}, t => t.set('closed', true)); }, - archiveTopics: function() { - this.forEachPerformed({type: 'archive'}, function(t) { - t.set('archived', true); - }); + archiveTopics() { + this.forEachPerformed({type: 'archive'}, t => t.set('archived', true)); }, - changeCategory: function() { - var categoryId = parseInt(this.get('newCategoryId'), 10) || 0, - category = Discourse.Category.findById(categoryId), - self = this; - this.perform({type: 'change_category', category_id: categoryId}).then(function(topics) { - topics.forEach(function(t) { - t.set('category', category); - }); - const refreshTarget = self.get('refreshTarget'); + unlistTopics() { + this.forEachPerformed({type: 'unlist'}, t => t.set('visible', false)); + }, + + changeCategory() { + const categoryId = parseInt(this.get('newCategoryId'), 10) || 0; + const category = Discourse.Category.findById(categoryId); + + this.perform({type: 'change_category', category_id: categoryId}).then(topics => { + topics.forEach(t => t.set('category', category)); + const refreshTarget = this.get('refreshTarget'); if (refreshTarget) { refreshTarget.send('refresh'); } - self.send('closeModal'); + this.send('closeModal'); }); }, - resetRead: function() { + resetRead() { this.performAndRefresh({ type: 'reset_read' }); } } diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index 968cd4e68d1..e0e480953f1 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -153,7 +153,7 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { if (user.get('staff') && replyCount > 0) { bootbox.dialog(I18n.t("post.controls.delete_replies.confirm", {count: replyCount}), [ {label: I18n.t("cancel"), - 'class': 'btn-danger rightg'}, + 'class': 'btn-danger right'}, {label: I18n.t("post.controls.delete_replies.no_value"), callback() { post.destroy(user); @@ -374,10 +374,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, { togglePinnedForUser() { if (this.get('model.pinned_at')) { - if (this.get('pinned')) { - this.get('content').clearPin(); + const topic = this.get('content'); + if (topic.get('pinned')) { + topic.clearPin(); } else { - this.get('content').rePin(); + topic.rePin(); } } }, diff --git a/app/assets/javascripts/discourse/controllers/user-card.js.es6 b/app/assets/javascripts/discourse/controllers/user-card.js.es6 index ce2e065a89d..c67125cdb2c 100644 --- a/app/assets/javascripts/discourse/controllers/user-card.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-card.js.es6 @@ -39,6 +39,11 @@ export default Ember.Controller.extend({ // XSS protection (should be encapsulated) username = username.toString().replace(/[^A-Za-z0-9_\.\-]/g, ""); + // No user card for anon + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { + return; + } + // Don't show on mobile if (Discourse.Mobile.mobileView) { const url = "/users/" + username; diff --git a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 index b7b269f78f0..3d2bd23b5fd 100644 --- a/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 +++ b/app/assets/javascripts/discourse/initializers/enable-emoji.js.es6 @@ -9,7 +9,15 @@ export default { window.PagedownCustom.appendButtons.push({ id: 'wmd-emoji-button', description: I18n.t("composer.emoji"), - execute: showSelector + execute() { + showSelector({ + container, + onSelect(title) { + const composerController = container.lookup('controller:composer'); + composerController.appendTextAtCursor(`:${title}:`, {space: true}); + }, + }); + } }); } } diff --git a/app/assets/javascripts/discourse/initializers/load-all-helpers.js.es6 b/app/assets/javascripts/discourse/initializers/load-all-helpers.js.es6 index f9ec0d71c75..93477787751 100644 --- a/app/assets/javascripts/discourse/initializers/load-all-helpers.js.es6 +++ b/app/assets/javascripts/discourse/initializers/load-all-helpers.js.es6 @@ -1,11 +1,12 @@ +export function loadAllHelpers() { + Ember.keys(requirejs.entries).forEach(entry => { + if ((/\/helpers\//).test(entry)) { + require(entry, null, null, true); + } + }); +} + export default { name: 'load-all-helpers', - - initialize: function() { - Ember.keys(requirejs.entries).forEach(function(entry) { - if ((/\/helpers\//).test(entry)) { - require(entry, null, null, true); - } - }); - } + initialize: loadAllHelpers }; diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji-groups.js.es6 b/app/assets/javascripts/discourse/lib/emoji/emoji-groups.js.es6 new file mode 100644 index 00000000000..71ef4eb903b --- /dev/null +++ b/app/assets/javascripts/discourse/lib/emoji/emoji-groups.js.es6 @@ -0,0 +1,57 @@ +// 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: ["grinning", "grin", "joy", "smiley", "smile", "sweat_smile", "laughing", "innocent", "smiling_imp", "imp", "wink", "blush", "relaxed", "yum", "relieved", "heart_eyes", "sunglasses", "smirk", "neutral_face", "expressionless", "unamused", "sweat", "pensive", "confused", "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", "frowning", "anguished", "fearful", "weary", "sleepy", "tired_face", "grimacing", "sob", "open_mouth", "hushed", "cold_sweat", "scream", "astonished", "flushed", "sleeping", "dizzy_face", "no_mouth", "mask", "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", "baby", "boy", "girl", "man", "woman", "family", "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"] + }, + { + name: "nature", + fullname: "Nature", + tabicon: "evergreen_tree", + icons: ["seedling", "evergreen_tree", "deciduous_tree", "palm_tree", "cactus", "tulip", "cherry_blossom", "rose", "hibiscus", "sunflower", "blossom", "bouquet", "ear_of_rice", "herb", "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", "full_moon", "waning_gibbous_moon", "last_quarter_moon", "waning_crescent_moon", "new_moon_with_face", "full_moon_with_face", "first_quarter_moon_with_face", "last_quarter_moon_with_face", "sun_with_face"] + }, + { + name: "food", + fullname: "Food & Drink", + tabicon: "hamburger", + icons: ["tomato", "eggplant", "corn", "sweet_potato", "grapes", "melon", "watermelon", "tangerine", "lemon", "banana", "pineapple", "apple", "green_apple", "pear", "peach", "cherries", "strawberry", "hamburger", "pizza", "meat_on_bone", "poultry_leg", "rice_cracker", "rice_ball", "rice", "curry", "ramen", "spaghetti", "bread", "fries", "dango", "oden", "sushi", "fried_shrimp", "fish_cake", "icecream", "shaved_ice", "ice_cream", "doughnut", "cookie", "chocolate_bar", "candy", "lollipop", "custard", "honey_pot", "cake", "bento", "stew", "egg", "fork_and_knife", "tea", "coffee", "sake", "wine_glass", "cocktail", "tropical_drink", "beer", "beers", "baby_bottle"] + }, + { + name: "celebration", + fullname: "Celebration", + tabicon: "gift", + icons: ["ribbon", "gift", "birthday", "jack_o_lantern", "christmas_tree", "tanabata_tree", "bamboo", "rice_scene", "fireworks", "sparkler", "tada", "confetti_ball", "balloon", "dizzy", "sparkles", "boom", "mortar_board", "crown", "dolls", "flags", "wind_chime", "crossed_flags", "izakaya_lantern", "ring", "heart", "broken_heart", "love_letter", "two_hearts", "revolving_hearts", "heartbeat", "heartpulse", "sparkling_heart", "cupid", "gift_heart", "heart_decoration", "purple_heart", "yellow_heart", "green_heart", "blue_heart"] + }, + { + name: "activity", + fullname: "Activities", + tabicon: "soccer", + icons: ["runner", "walking", "dancer", "rowboat", "swimmer", "surfer", "bath", "snowboarder", "ski", "snowman", "bicyclist", "mountain_bicyclist", "horse_racing", "tent", "fishing_pole_and_fish", "soccer", "basketball", "football", "baseball", "tennis", "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", "8ball", "bowling", "slot_machine", "game_die", "video_game", "flower_playing_cards", "black_joker", "mahjong", "carousel_horse", "ferris_wheel", "roller_coaster"] + }, + { + name: "travel", + fullname: "Travel & Places", + tabicon: "airplane", + icons: ["train", "mountain_railway", "railway_car", "steam_locomotive", "monorail", "bullettrain_side", "bullettrain_front", "train2", "metro", "light_rail", "station", "tram", "bus", "oncoming_bus", "trolleybus", "minibus", "ambulance", "fire_engine", "police_car", "oncoming_police_car", "rotating_light", "taxi", "oncoming_taxi", "car", "oncoming_automobile", "blue_car", "truck", "articulated_lorry", "tractor", "bike", "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"] + }, + { + name: "objects", + fullname: "Objects & Symbols", + tabicon: "eyeglasses", + icons: ["watch", "iphone", "calling", "computer", "alarm_clock", "hourglass_flowing_sand", "hourglass", "camera", "video_camera", "movie_camera", "tv", "radio", "pager", "telephone_receiver", "phone", "fax", "minidisc", "floppy_disk", "cd", "dvd", "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", "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", "notebook_with_decorative_cover", "ledger", "closed_book", "green_book", "blue_book", "orange_book", "books", "card_index", "link", "paperclip", "pushpin", "scissors", "triangular_ruler", "round_pushpin", "straight_ruler", "triangular_flag_on_post", "file_folder", "open_file_folder", "black_nib", "pencil2", "memo", "lock_with_ink_pen", "closed_lock_with_key", "lock", "unlock", "mega", "loudspeaker", "sound", "loud_sound", "speaker", "mute", "zzz", "bell", "no_bell", "thought_balloon", "speech_balloon", "children_crossing", "mag", "mag_right", "no_entry_sign", "no_entry", "name_badge", "no_pedestrians", "do_not_litter", "no_bicycles", "non-potable_water", "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", "aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpius", "sagittarius", "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", "twisted_rightwards_arrows", "repeat", "repeat_one", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "keycap_ten", "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", "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", "black_small_square", "white_small_square", "black_large_square", "white_large_square", "black_medium_square", "white_medium_square", "black_medium_small_square", "white_medium_small_square", "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"] + } +]; + +// scrub groups +groups.forEach(group => { + group.icons = group.icons.reject(obj => !Discourse.Emoji.exists(obj)); +}); + +// export so others can modify +Discourse.Emoji.groups = groups; + +export default groups; diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 index baf51822b02..5219e39faa1 100644 --- a/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 +++ b/app/assets/javascripts/discourse/lib/emoji/emoji-toolbar.js.es6 @@ -1,101 +1,45 @@ +import groups from 'discourse/lib/emoji/emoji-groups'; import KeyValueStore from "discourse/lib/key-value-store"; const keyValueStore = new KeyValueStore("discourse_emojis_"); const EMOJI_USAGE = "emojiUsage"; -// 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 -var groups = [ - { - name: "people", - fullname: "People", - tabicon: "grinning", - icons: ["grinning", "grin", "joy", "smiley", "smile", "sweat_smile", "laughing", "innocent", "smiling_imp", "imp", "wink", "blush", "relaxed", "yum", "relieved", "heart_eyes", "sunglasses", "smirk", "neutral_face", "expressionless", "unamused", "sweat", "pensive", "confused", "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", "frowning", "anguished", "fearful", "weary", "sleepy", "tired_face", "grimacing", "sob", "open_mouth", "hushed", "cold_sweat", "scream", "astonished", "flushed", "sleeping", "dizzy_face", "no_mouth", "mask", "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", "baby", "boy", "girl", "man", "woman", "family", "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"] - }, - { - name: "nature", - fullname: "Nature", - tabicon: "evergreen_tree", - icons: ["seedling", "evergreen_tree", "deciduous_tree", "palm_tree", "cactus", "tulip", "cherry_blossom", "rose", "hibiscus", "sunflower", "blossom", "bouquet", "ear_of_rice", "herb", "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", "full_moon", "waning_gibbous_moon", "last_quarter_moon", "waning_crescent_moon", "new_moon_with_face", "full_moon_with_face", "first_quarter_moon_with_face", "last_quarter_moon_with_face", "sun_with_face"] - }, - { - name: "food", - fullname: "Food & Drink", - tabicon: "hamburger", - icons: ["tomato", "eggplant", "corn", "sweet_potato", "grapes", "melon", "watermelon", "tangerine", "lemon", "banana", "pineapple", "apple", "green_apple", "pear", "peach", "cherries", "strawberry", "hamburger", "pizza", "meat_on_bone", "poultry_leg", "rice_cracker", "rice_ball", "rice", "curry", "ramen", "spaghetti", "bread", "fries", "dango", "oden", "sushi", "fried_shrimp", "fish_cake", "icecream", "shaved_ice", "ice_cream", "doughnut", "cookie", "chocolate_bar", "candy", "lollipop", "custard", "honey_pot", "cake", "bento", "stew", "egg", "fork_and_knife", "tea", "coffee", "sake", "wine_glass", "cocktail", "tropical_drink", "beer", "beers", "baby_bottle"] - }, - { - name: "celebration", - fullname: "Celebration", - tabicon: "gift", - icons: ["ribbon", "gift", "birthday", "jack_o_lantern", "christmas_tree", "tanabata_tree", "bamboo", "rice_scene", "fireworks", "sparkler", "tada", "confetti_ball", "balloon", "dizzy", "sparkles", "boom", "mortar_board", "crown", "dolls", "flags", "wind_chime", "crossed_flags", "izakaya_lantern", "ring", "heart", "broken_heart", "love_letter", "two_hearts", "revolving_hearts", "heartbeat", "heartpulse", "sparkling_heart", "cupid", "gift_heart", "heart_decoration", "purple_heart", "yellow_heart", "green_heart", "blue_heart"] - }, - { - name: "activity", - fullname: "Activities", - tabicon: "soccer", - icons: ["runner", "walking", "dancer", "rowboat", "swimmer", "surfer", "bath", "snowboarder", "ski", "snowman", "bicyclist", "mountain_bicyclist", "horse_racing", "tent", "fishing_pole_and_fish", "soccer", "basketball", "football", "baseball", "tennis", "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", "8ball", "bowling", "slot_machine", "game_die", "video_game", "flower_playing_cards", "black_joker", "mahjong", "carousel_horse", "ferris_wheel", "roller_coaster"] - }, - { - name: "travel", - fullname: "Travel & Places", - tabicon: "airplane", - icons: ["train", "mountain_railway", "railway_car", "steam_locomotive", "monorail", "bullettrain_side", "bullettrain_front", "train2", "metro", "light_rail", "station", "tram", "bus", "oncoming_bus", "trolleybus", "minibus", "ambulance", "fire_engine", "police_car", "oncoming_police_car", "rotating_light", "taxi", "oncoming_taxi", "car", "oncoming_automobile", "blue_car", "truck", "articulated_lorry", "tractor", "bike", "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"] - }, - { - name: "objects", - fullname: "Objects & Symbols", - tabicon: "eyeglasses", - icons: ["watch", "iphone", "calling", "computer", "alarm_clock", "hourglass_flowing_sand", "hourglass", "camera", "video_camera", "movie_camera", "tv", "radio", "pager", "telephone_receiver", "phone", "fax", "minidisc", "floppy_disk", "cd", "dvd", "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", "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", "notebook_with_decorative_cover", "ledger", "closed_book", "green_book", "blue_book", "orange_book", "books", "card_index", "link", "paperclip", "pushpin", "scissors", "triangular_ruler", "round_pushpin", "straight_ruler", "triangular_flag_on_post", "file_folder", "open_file_folder", "black_nib", "pencil2", "memo", "lock_with_ink_pen", "closed_lock_with_key", "lock", "unlock", "mega", "loudspeaker", "sound", "loud_sound", "speaker", "mute", "zzz", "bell", "no_bell", "thought_balloon", "speech_balloon", "children_crossing", "mag", "mag_right", "no_entry_sign", "no_entry", "name_badge", "no_pedestrians", "do_not_litter", "no_bicycles", "non-potable_water", "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", "aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpius", "sagittarius", "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", "twisted_rightwards_arrows", "repeat", "repeat_one", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "keycap_ten", "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", "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", "black_small_square", "white_small_square", "black_large_square", "white_large_square", "black_medium_square", "white_medium_square", "black_medium_small_square", "white_medium_small_square", "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"] - } -]; +let PER_ROW = 12; +const PER_PAGE = 60; -// scrub groups -groups.forEach(function(group){ - group.icons = _.reject(group.icons, function(obj){ - return !Discourse.Emoji.exists(obj); - }); -}); - -// export so others can modify -Discourse.Emoji.groups = groups; - -var closeSelector = function(){ - $('.emoji-modal, .emoji-modal-wrapper').remove(); - $('body, textarea').off('keydown.emoji'); -}; - -var ungroupedIcons, recentlyUsedIcons; - -var initializeUngroupedIcons = function(){ - ungroupedIcons = []; - - var groupedIcons = {}; - _.each(groups, function(group){ - _.each(group.icons, function(icon){ - groupedIcons[icon] = true; - }); - }); - - var emojis = Discourse.Emoji.list(); - _.each(emojis, function(emoji){ - if(groupedIcons[emoji] !== true){ - ungroupedIcons.push(emoji); - } - }); - - if(ungroupedIcons.length > 0){ - groups.push({name: 'ungrouped', icons: ungroupedIcons}); - } -}; +let ungroupedIcons, recentlyUsedIcons; if (!keyValueStore.getObject(EMOJI_USAGE)) { keyValueStore.setObject({key: EMOJI_USAGE, value: {}}); } -var trackEmojiUsage = function(title) { - var recent = keyValueStore.getObject(EMOJI_USAGE); +function closeSelector() { + $('.emoji-modal, .emoji-modal-wrapper').remove(); + $('body, textarea').off('keydown.emoji'); +} + +function initializeUngroupedIcons() { + const groupedIcons = {}; + + groups.forEach(group => { + group.icons.forEach(icon => groupedIcons[icon] = true); + }); + + ungroupedIcons = []; + const emojis = Discourse.Emoji.list(); + emojis.forEach(emoji => { + if (groupedIcons[emoji] !== true) { + ungroupedIcons.push(emoji); + } + }); + + if (ungroupedIcons.length) { + groups.push({name: 'ungrouped', icons: ungroupedIcons}); + } +} + +function trackEmojiUsage(title) { + const recent = keyValueStore.getObject(EMOJI_USAGE); if (!recent[title]) { recent[title] = { title: title, usage: 0 }; } recent[title]["usage"]++; @@ -104,46 +48,40 @@ var trackEmojiUsage = function(title) { // clear the cache recentlyUsedIcons = null; -}; +} -var initializeRecentlyUsedIcons = function(){ +function sortByUsage(a, b) { + if (a.usage > b.usage) { return -1; } + if (b.usage > a.usage) { return 1; } + return a.title.localeCompare(b.title); +} + +function initializeRecentlyUsedIcons() { recentlyUsedIcons = []; - var usage = _.map(keyValueStore.getObject(EMOJI_USAGE)); - usage.sort(function(a,b){ - if(a.usage > b.usage){ - return -1; + const usage = _.map(keyValueStore.getObject(EMOJI_USAGE)).sort(sortByUsage); + const recent = usage.slice(0, PER_ROW); + + if (recent.length > 0) { + + recent.forEach(emoji => recentlyUsedIcons.push(emoji.title)); + + const recentGroup = groups.findProperty('name', 'recent'); + if (recentGroup) { + recentGroup.icons = recentlyUsedIcons; + } else { + groups.push({ name: 'recent', icons: recentlyUsedIcons }); } - if(b.usage > a.usage){ - return 1; - } - return a.title.localeCompare(b.title); - }); - - var recent = _.take(usage, PER_ROW); - - if(recent.length > 0){ - _.each(recent, function(emoji){ - recentlyUsedIcons.push(emoji.title); - }); - - var recentGroup = _.find(groups, {name: 'recent'}); - if(!recentGroup){ - recentGroup = {name: 'recent', icons: []}; - groups.push(recentGroup); - } - - recentGroup.icons = recentlyUsedIcons; } -}; +} -var toolbar = function(selected){ +function toolbar(selected) { if (!ungroupedIcons) { initializeUngroupedIcons(); } if (!recentlyUsedIcons) { initializeRecentlyUsedIcons(); } - return _.map(groups, function(g, i){ - var icon = g.tabicon; - var title = g.fullname; + return groups.map((g, i) => { + let icon = g.tabicon; + let title = g.fullname; if (g.name === "recent") { icon = "star"; title = "Recent"; @@ -151,60 +89,48 @@ var toolbar = function(selected){ icon = g.icons[0]; title = "Custom"; } - var row = {src: Discourse.Emoji.urlFor(icon), title: title, groupId: i}; - if(i === selected){ - row.selected = true; - } - return row; + + return { src: Discourse.Emoji.urlFor(icon), + title, + groupId: i, + selected: i === selected }; }); -}; +} -var PER_ROW = 12, PER_PAGE = 60; - -var bindEvents = function(page, offset, options) { - var composerController = Discourse.__container__.lookup('controller:composer'); - - $('.emoji-page a').click(function(){ - var title = $(this).attr('title'); +function bindEvents(page, offset, options) { + $('.emoji-page a').click(e => { + const title = $(e.currentTarget).attr('title'); trackEmojiUsage(title); - - const prefix = options.skipPrefix ? "" : ":"; - composerController.appendTextAtCursor(`${prefix}${title}:`, {space: !options.skipPrefix}); + options.onSelect(title); closeSelector(); return false; - }).hover(function(){ - var title = $(this).attr('title'); - var html = " :" + title + ":"; + }).hover(e => { + const title = $(e.currentTarget).attr('title'); + const html = " :" + title + ":"; $('.emoji-modal .info').html(html); - },function(){ - $('.emoji-modal .info').html(""); - }); + }, () => $('.emoji-modal .info').html("")); - $('.emoji-modal .nav .next a').click(function(){ - render(page, offset+PER_PAGE, options); - }); - - $('.emoji-modal .nav .prev a').click(function(){ - render(page, offset-PER_PAGE, options); - }); + $('.emoji-modal .nav .next a').click(() => render(page, offset+PER_PAGE, options)); + $('.emoji-modal .nav .prev a').click(() => render(page, offset-PER_PAGE, options)); $('.emoji-modal .toolbar a').click(function(){ - var p = parseInt($(this).data('group-id')); + const p = parseInt($(this).data('group-id')); render(p, 0, options); return false; }); -}; +} -var render = function(page, offset, options) { +function render(page, offset, options) { keyValueStore.set({key: "emojiPage", value: page}); keyValueStore.set({key: "emojiOffset", value: offset}); - var toolbarItems = toolbar(page); - var rows = [], row = []; - var icons = groups[page].icons; - var max = offset + PER_PAGE; + const toolbarItems = toolbar(page); + const rows = []; + let row = []; + const icons = groups[page].icons; + const max = offset + PER_PAGE; - for(var i=offset; i icons.length }; - $('body .emoji-modal').remove(); - var rendered = Ember.TEMPLATES["emoji-toolbar.raw"](model); - $('body').append(rendered); + $('.emoji-modal', options.appendTo).remove(); + const template = options.container.lookup('template:emoji-toolbar.raw'); + options.appendTo.append(template(model)); bindEvents(page, offset, options); -}; +} -var showSelector = function(options) { +function showSelector(options) { options = options || {}; + options.appendTo = options.appendTo || $('body'); - $('body').append('
'); - - $('.emoji-modal-wrapper').click(function(){ - closeSelector(); - }); + options.appendTo.append('
'); + $('.emoji-modal-wrapper').click(() => closeSelector()); if (Discourse.Mobile.mobileView) PER_ROW = 9; + const page = keyValueStore.getInt("emojiPage", 0); + const offset = keyValueStore.getInt("emojiOffset", 0); - var page = keyValueStore.getInt("emojiPage", 0); - var offset = keyValueStore.getInt("emojiOffset", 0); render(page, offset, options); - $('body, textarea').on('keydown.emoji', function(e){ - if(e.which === 27){ + $('body, textarea').on('keydown.emoji', e => { + if (e.which === 27) { closeSelector(); return false; } }); -}; +} export { showSelector }; diff --git a/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb b/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb index 8a8fceb2528..eff6b85e8e4 100644 --- a/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb +++ b/app/assets/javascripts/discourse/lib/emoji/emoji.js.erb @@ -12,6 +12,11 @@ Discourse.Dialect.registerEmoji = function(code, url) { extendedEmoji[code] = url; }; +// This method is used by PrettyText to reset custom emojis in multisites +Discourse.Dialect.resetEmoji = function() { + extendedEmoji = {}; +}; + Discourse.Emoji.list = function(){ var list = emoji.slice(0); _.each(extendedEmoji, function(v,k){ list.push(k); }); diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 index 6d8f6ab285a..6a507050f0a 100644 --- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 +++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 @@ -99,7 +99,7 @@ export default { $('.topic-post.selected button.create').click(); // lazy but should work for now setTimeout(function() { - $('#wmd-quote-post').click(); + $('.wmd-quote-post').click(); }, 500); }, @@ -358,8 +358,8 @@ export default { }, _changeSection(direction) { - const $sections = $('#navigation-bar li'), - active = $('#navigation-bar li.active'), + const $sections = $('.nav.nav-pills li'), + active = $('.nav.nav-pills li.active'), index = $sections.index(active) + direction; if (index >= 0 && index < $sections.length) { diff --git a/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 b/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 index c0e7650d8b6..705f281a353 100644 --- a/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 +++ b/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 @@ -42,6 +42,7 @@ export default Ember.Mixin.create({ }); tracker.incrementMessageCount(); } + self.send('closeModal'); self.send('refresh'); }); } diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 366a55e73e9..f566ef943a6 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -201,7 +201,7 @@ const Composer = RestModel.extend({ return this.get('canCategorize') && !this.siteSettings.allow_uncategorized_topics && !this.get('categoryId') && - !this.user.get('staff'); + !this.user.get('admin'); } }.property('loading', 'canEditTitle', 'titleLength', 'targetUsernames', 'replyLength', 'categoryId', 'missingReplyCharacters'), diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6 index 010330451b0..4ea6646f035 100644 --- a/app/assets/javascripts/discourse/models/post-stream.js.es6 +++ b/app/assets/javascripts/discourse/models/post-stream.js.es6 @@ -445,8 +445,7 @@ const PostStream = RestModel.extend({ const url = "/posts/" + postId; const store = this.store; - return Discourse.ajax(url).then((p) => - this.storePost(store.createRecord('post', p))); + return Discourse.ajax(url).then(p => this.storePost(store.createRecord('post', p))); }, /** diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 3b63069ed76..6ed996b3368 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -8,6 +8,22 @@ const Topic = RestModel.extend({ message: null, errorLoading: false, + @computed('posters.firstObject') + creator(poster){ + return poster && poster.user; + }, + + @computed('posters.lastObject') + lastPoster(poster) { + if (poster){ + if (this.last_poster_username === poster.user.username){ + return poster.user; + } else { + return this.get('creator'); + } + } + }, + @computed('fancy_title') fancyTitle(title) { title = title || ""; diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 87278959ef7..3fc570c3a4d 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -68,6 +68,8 @@ const User = RestModel.extend({ adminPath: url('username_lower', "/admin/users/%@"), + mutedTopicsPath: url('/latest?state=muted'), + @computed("username") username_lower(username) { return username.toLowerCase(); diff --git a/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6 b/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6 index acaef8f0577..1bd40078369 100644 --- a/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6 +++ b/app/assets/javascripts/discourse/pre-initializers/dynamic-route-builders.js.es6 @@ -7,19 +7,32 @@ export default { name: 'dynamic-route-builders', initialize(container, app) { + app.DiscoveryCategoryController = DiscoverySortableController.extend(); + app.DiscoveryParentCategoryController = DiscoverySortableController.extend(); + app.DiscoveryCategoryNoneController = DiscoverySortableController.extend(); + app.DiscoveryCategoryRoute = buildCategoryRoute('latest'); app.DiscoveryParentCategoryRoute = buildCategoryRoute('latest'); app.DiscoveryCategoryNoneRoute = buildCategoryRoute('latest', {no_subcategories: true}); const site = Discourse.Site.current(); site.get('filters').forEach(filter => { - app["Discovery" + filter.capitalize() + "Controller"] = DiscoverySortableController.extend(); - app["Discovery" + filter.capitalize() + "Route"] = buildTopicRoute(filter); - app["Discovery" + filter.capitalize() + "CategoryRoute"] = buildCategoryRoute(filter); - app["Discovery" + filter.capitalize() + "CategoryNoneRoute"] = buildCategoryRoute(filter, {no_subcategories: true}); + const filterCapitalized = filter.capitalize(); + app[`Discovery${filterCapitalized}Controller`] = DiscoverySortableController.extend(); + app[`Discovery${filterCapitalized}CategoryController`] = DiscoverySortableController.extend(); + app[`Discovery${filterCapitalized}ParentCategoryController`] = DiscoverySortableController.extend(); + app[`Discovery${filterCapitalized}CategoryNoneController`] = DiscoverySortableController.extend(); + app[`Discovery${filterCapitalized}Route`] = buildTopicRoute(filter); + app[`Discovery${filterCapitalized}CategoryRoute`] = buildCategoryRoute(filter); + app[`Discovery${filterCapitalized}ParentCategoryRoute`] = buildCategoryRoute(filter); + app[`Discovery${filterCapitalized}CategoryNoneRoute`] = buildCategoryRoute(filter, {no_subcategories: true}); }); Discourse.DiscoveryTopController = DiscoverySortableController.extend(); + Discourse.DiscoveryTopCategoryController = DiscoverySortableController.extend(); + Discourse.DiscoveryTopParentCategoryController = DiscoverySortableController.extend(); + Discourse.DiscoveryTopCategoryNoneController = DiscoverySortableController.extend(); + Discourse.DiscoveryTopRoute = buildTopicRoute('top', { actions: { willTransition() { @@ -30,13 +43,19 @@ export default { } }); Discourse.DiscoveryTopCategoryRoute = buildCategoryRoute('top'); + Discourse.DiscoveryTopParentCategoryRoute = buildCategoryRoute('top'); Discourse.DiscoveryTopCategoryNoneRoute = buildCategoryRoute('top', {no_subcategories: true}); site.get('periods').forEach(period => { - app["DiscoveryTop" + period.capitalize() + "Controller"] = DiscoverySortableController.extend(); - app["DiscoveryTop" + period.capitalize() + "Route"] = buildTopicRoute('top/' + period); - app["DiscoveryTop" + period.capitalize() + "CategoryRoute"] = buildCategoryRoute('top/' + period); - app["DiscoveryTop" + period.capitalize() + "CategoryNoneRoute"] = buildCategoryRoute('top/' + period, {no_subcategories: true}); + const periodCapitalized = period.capitalize(); + app[`DiscoveryTop${periodCapitalized}Controller`] = DiscoverySortableController.extend(); + app[`DiscoveryTop${periodCapitalized}CategoryController`] = DiscoverySortableController.extend(); + app[`DiscoveryTop${periodCapitalized}ParentCategoryController`] = DiscoverySortableController.extend(); + app[`DiscoveryTop${periodCapitalized}CategoryNoneController`] = DiscoverySortableController.extend(); + app[`DiscoveryTop${periodCapitalized}Route`] = buildTopicRoute('top/' + period); + app[`DiscoveryTop${periodCapitalized}CategoryRoute`] = buildCategoryRoute('top/' + period); + app[`DiscoveryTop${periodCapitalized}ParentCategoryRoute`] = buildCategoryRoute('top/' + period); + app[`DiscoveryTop${periodCapitalized}CategoryNoneRoute`] = buildCategoryRoute('top/' + period, {no_subcategories: true}); }); } }; diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 index 172bd050e27..34f0a98b44a 100644 --- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 +++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 @@ -15,26 +15,25 @@ export default function() { this.resource('discovery', { path: '/' }, function() { // top this.route('top'); - this.route('topCategory', { path: '/c/:slug/l/top' }); + this.route('topParentCategory', { path: '/c/:slug/l/top' }); this.route('topCategoryNone', { path: '/c/:slug/none/l/top' }); this.route('topCategory', { path: '/c/:parentSlug/:slug/l/top' }); // top by periods - var self = this; - Discourse.Site.currentProp('periods').forEach(function(period) { - var top = 'top' + period.capitalize(); - self.route(top, { path: '/top/' + period }); - self.route(top + 'Category', { path: '/c/:slug/l/top/' + period }); - self.route(top + 'CategoryNone', { path: '/c/:slug/none/l/top/' + period }); - self.route(top + 'Category', { path: '/c/:parentSlug/:slug/l/top/' + period }); + Discourse.Site.currentProp('periods').forEach(period => { + const top = 'top' + period.capitalize(); + this.route(top, { path: '/top/' + period }); + this.route(top + 'ParentCategory', { path: '/c/:slug/l/top/' + period }); + this.route(top + 'CategoryNone', { path: '/c/:slug/none/l/top/' + period }); + this.route(top + 'Category', { path: '/c/:parentSlug/:slug/l/top/' + period }); }); // filters - Discourse.Site.currentProp('filters').forEach(function(filter) { - self.route(filter, { path: '/' + filter }); - self.route(filter + 'Category', { path: '/c/:slug/l/' + filter }); - self.route(filter + 'CategoryNone', { path: '/c/:slug/none/l/' + filter }); - self.route(filter + 'Category', { path: '/c/:parentSlug/:slug/l/' + filter }); + Discourse.Site.currentProp('filters').forEach(filter => { + this.route(filter, { path: '/' + filter }); + this.route(filter + 'ParentCategory', { path: '/c/:slug/l/' + filter }); + this.route(filter + 'CategoryNone', { path: '/c/:slug/none/l/' + filter }); + this.route(filter + 'Category', { path: '/c/:parentSlug/:slug/l/' + filter }); }); this.route('categories'); @@ -56,9 +55,8 @@ export default function() { this.resource('users'); this.resource('user', { path: '/users/:username' }, function() { this.resource('userActivity', { path: '/activity' }, function() { - var self = this; - _.map(Discourse.UserAction.TYPES, function (id, userAction) { - self.route(userAction, { path: userAction.replace('_', '-') }); + _.map(Discourse.UserAction.TYPES, (id, userAction) => { + this.route(userAction, { path: userAction.replace('_', '-') }); }); }); diff --git a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 index b0dfc941ca1..8456eaf4faa 100644 --- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 @@ -1,12 +1,13 @@ -import { queryParams, filterQueryParams, findTopicList } from 'discourse/routes/build-topic-route'; +import { filterQueryParams, findTopicList } from 'discourse/routes/build-topic-route'; +import { queryParams } from 'discourse/controllers/discovery-sortable'; // A helper function to create a category route with parameters export default (filter, params) => { return Discourse.Route.extend({ - queryParams: queryParams, + queryParams, model(modelParams) { - return Discourse.Category.findBySlug(modelParams.slug, modelParams.parentSlug); + return { category: Discourse.Category.findBySlug(modelParams.slug, modelParams.parentSlug) }; }, afterModel(model, transition) { @@ -15,27 +16,27 @@ export default (filter, params) => { return; } - this._setupNavigation(model); - return Em.RSVP.all([this._createSubcategoryList(model), - this._retrieveTopicList(model, transition)]); + this._setupNavigation(model.category); + return Em.RSVP.all([this._createSubcategoryList(model.category), + this._retrieveTopicList(model.category, transition)]); }, - _setupNavigation(model) { + _setupNavigation(category) { const noSubcategories = params && !!params.no_subcategories, - filterMode = `c/${Discourse.Category.slugFor(model)}${noSubcategories ? "/none" : ""}/l/${filter}`; + filterMode = `c/${Discourse.Category.slugFor(category)}${noSubcategories ? "/none" : ""}/l/${filter}`; this.controllerFor('navigation/category').setProperties({ - category: model, + category, filterMode: filterMode, noSubcategories: params && params.no_subcategories, - canEditCategory: model.get('can_edit') + canEditCategory: category.get('can_edit') }); }, - _createSubcategoryList(model) { + _createSubcategoryList(category) { this._categoryList = null; - if (Em.isNone(model.get('parentCategory')) && Discourse.SiteSettings.show_subcategory_list) { - return Discourse.CategoryList.listForParent(this.store, model) + if (Em.isNone(category.get('parentCategory')) && Discourse.SiteSettings.show_subcategory_list) { + return Discourse.CategoryList.listForParent(this.store, category) .then(list => this._categoryList = list); } @@ -43,49 +44,60 @@ export default (filter, params) => { return Em.RSVP.resolve(); }, - _retrieveTopicList(model, transition) { - const listFilter = `c/${Discourse.Category.slugFor(model)}/l/${filter}`, + _retrieveTopicList(category, transition) { + const listFilter = `c/${Discourse.Category.slugFor(category)}/l/${filter}`, findOpts = filterQueryParams(transition.queryParams, params), extras = { cached: this.isPoppedState(transition) }; return findTopicList(this.store, this.topicTrackingState, listFilter, findOpts, extras).then(list => { - Discourse.TopicList.hideUniformCategory(list, model); + Discourse.TopicList.hideUniformCategory(list, category); this.set('topics', list); + return list; }); }, titleToken() { const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', { count: 0 }), - model = this.currentModel; + category = this.currentModel.category; - return I18n.t('filters.with_category', { filter: filterText, category: model.get('name') }); + return I18n.t('filters.with_category', { filter: filterText, category: category.get('name') }); }, setupController(controller, model) { const topics = this.get('topics'), - periodId = topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), + category = model.category, canCreateTopic = topics.get('can_create_topic'), - canCreateTopicOnCategory = model.get('permission') === Discourse.PermissionType.FULL; + canCreateTopicOnCategory = category.get('permission') === Discourse.PermissionType.FULL; this.controllerFor('navigation/category').setProperties({ canCreateTopicOnCategory: canCreateTopicOnCategory, cannotCreateTopicOnCategory: !canCreateTopicOnCategory, canCreateTopic: canCreateTopic }); - this.controllerFor('discovery/topics').setProperties({ + + var topicOpts = { model: topics, - category: model, - period: periodId, + category, + period: topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), selected: [], noSubcategories: params && !!params.no_subcategories, - order: topics.get('params.order'), - ascending: topics.get('params.ascending'), expandAllPinned: true, canCreateTopic: canCreateTopic, canCreateTopicOnCategory: canCreateTopicOnCategory - }); + }; - this.searchService.set('searchContext', model.get('searchContext')); + const p = category.get('params'); + if (p && Object.keys(p).length) { + if (p.order !== undefined) { + topicOpts.order = p.order; + } + if (p.ascending !== undefined) { + topicOpts.ascending = p.ascending; + } + } + + this.controllerFor('discovery/topics').setProperties(topicOpts); + this.searchService.set('searchContext', category.get('searchContext')); this.set('topics', null); this.openTopicDraft(topics); @@ -100,6 +112,12 @@ export default (filter, params) => { this.render('discovery/topics', { controller: 'discovery/topics', outlet: 'list-container' }); }, + resetController(controller, isExiting) { + if (isExiting) { + controller.setProperties({ order: "default", ascending: false }); + } + }, + deactivate() { this._super(); this.searchService.set('searchContext', null); diff --git a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 index 342b2258eea..0da680a200f 100644 --- a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 @@ -84,18 +84,11 @@ export default function(filter, extras) { return I18n.t('filters.with_topics', {filter: filterText}); }, - setupController(controller, model, trans) { - if (trans) { - controller.setProperties(Em.getProperties(trans, _.keys(queryParams).map(function(v){ - return 'queryParams.' + v; - }))); - } - - const period = model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''); + setupController(controller, model) { const topicOpts = { model, category: null, - period, + period: model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : ''), selected: [], expandGloballyPinned: true }; @@ -115,6 +108,12 @@ export default function(filter, extras) { this.controllerFor('navigation/default').set('canCreateTopic', model.get('can_create_topic')); }, + resetController(controller, isExiting) { + if (isExiting) { + controller.setProperties({ order: "default", ascending: false }); + } + }, + renderTemplate() { this.render('navigation/default', { outlet: 'navigation-bar' }); this.render('discovery/topics', { controller: 'discovery/topics', outlet: 'list-container' }); diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index 83b5b4186b4..400d1df1eea 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -5,7 +5,7 @@ import OpenComposer from "discourse/mixins/open-composer"; import { scrollTop } from "discourse/mixins/scroll-top"; -const DiscoveryRoute = Discourse.Route.extend(OpenComposer, { +export default Discourse.Route.extend(OpenComposer, { redirect() { return this.redirectIfLoginRequired(); }, @@ -46,9 +46,16 @@ const DiscoveryRoute = Discourse.Route.extend(OpenComposer, { createTopic() { this.openComposer(this.controllerFor("discovery/topics")); + }, + + dismissReadTopics(dismissTopics) { + var operationType = dismissTopics ? "topics" : "posts"; + this.controllerFor("discovery/topics").send('dismissRead', operationType); + }, + + dismissRead(operationType) { + this.controllerFor("discovery/topics").send('dismissRead', operationType); } } }); - -export default DiscoveryRoute; diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index 8bfdf13d5b5..976de121666 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -43,7 +43,7 @@ const TopicRoute = Discourse.Route.extend({ showFlags(model) { showModal('flag', { model }); - this.controllerFor('flag').setProperties({ selected: null }); + this.controllerFor('flag').setProperties({ selected: null, flagTopic: false }); }, showFlagTopic(model) { diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6 index 7fb8fffe6f2..040f949b5a7 100644 --- a/app/assets/javascripts/discourse/routes/user.js.es6 +++ b/app/assets/javascripts/discourse/routes/user.js.es6 @@ -33,6 +33,12 @@ export default Discourse.Route.extend({ } }, + beforeModel() { + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { + this.replaceWith("discovery"); + } + }, + model(params) { // If we're viewing the currently logged in user, return that object instead const currentUser = this.currentUser; diff --git a/app/assets/javascripts/discourse/routes/users.js.es6 b/app/assets/javascripts/discourse/routes/users.js.es6 index beb25276fcc..ab3cbe32060 100644 --- a/app/assets/javascripts/discourse/routes/users.js.es6 +++ b/app/assets/javascripts/discourse/routes/users.js.es6 @@ -23,6 +23,12 @@ export default Discourse.Route.extend({ } }, + beforeModel() { + if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { + this.replaceWith("discovery"); + } + }, + model(params) { // If we refresh via `refreshModel` set the old model to loading this._params = params; diff --git a/app/assets/javascripts/discourse/templates/components/d-editor-modal.hbs b/app/assets/javascripts/discourse/templates/components/d-editor-modal.hbs new file mode 100644 index 00000000000..e84f4709554 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/d-editor-modal.hbs @@ -0,0 +1,7 @@ + +{{yield}} + +
+ {{d-button class="btn-primary" label="composer.modal_ok" action="ok"}} + {{d-button class="btn-danger" label="composer.modal_cancel" action="cancel"}} +
diff --git a/app/assets/javascripts/discourse/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/templates/components/d-editor.hbs new file mode 100644 index 00000000000..8921294cbe2 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/d-editor.hbs @@ -0,0 +1,32 @@ + +
+ {{#d-editor-modal class="insert-link" hidden=insertLinkHidden okAction="insertLink"}} +

{{i18n "composer.link_dialog_title"}}

+ {{text-field value=link placeholderKey="composer.link_placeholder"}} + {{/d-editor-modal}} +
+ +
+
+ {{d-button action="bold" icon="bold" class="bold"}} + {{d-button action="italic" icon="italic" class="italic"}} +
+ {{d-button action="showLinkModal" icon="link" class="link"}} + {{d-button action="quote" icon="quote-right" class="quote"}} + {{d-button action="code" icon="code" class="code"}} +
+ {{d-button action="bullet" icon="list-ul" class="bullet"}} + {{d-button action="list" icon="list-ol" class="list"}} + {{d-button action="heading" icon="font" class="heading"}} + {{d-button action="rule" icon="minus" class="rule"}} + {{#if siteSettings.enable_emoji}} + {{d-button action="emoji" icon="smile-o" class="emoji"}} + {{/if}} +
+ + {{textarea value=value class="d-editor-input"}} + +
+ {{{preview}}} +
+
diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-topic-template.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-topic-template.hbs index 7a410abbd84..c0d58fbbd9d 100644 --- a/app/assets/javascripts/discourse/templates/components/edit-category-topic-template.hbs +++ b/app/assets/javascripts/discourse/templates/components/edit-category-topic-template.hbs @@ -1,2 +1,2 @@ -{{pagedown-editor value=category.topic_template}} +{{d-editor value=category.topic_template}} diff --git a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs index 0deea8e1a71..3d875eb6062 100644 --- a/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs +++ b/app/assets/javascripts/discourse/templates/components/hamburger-menu.hbs @@ -57,7 +57,7 @@
  • {{d-link route="badges" class="badge-link" label="badges.title"}}
  • {{/if}} - {{#if siteSettings.enable_user_directory}} + {{#if showUserDirectoryLink}}
  • {{d-link route="users" class="user-directory-link" label="directory.title"}}
  • {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/home-logo.hbs b/app/assets/javascripts/discourse/templates/components/home-logo.hbs index 976aefd88fe..b2b8f9e6db7 100644 --- a/app/assets/javascripts/discourse/templates/components/home-logo.hbs +++ b/app/assets/javascripts/discourse/templates/components/home-logo.hbs @@ -1,18 +1,15 @@ {{#if showSmallLogo}} {{#if smallLogoUrl}} - {{else}} {{/if}} {{else}} {{#if showMobileLogo}} - {{else}} {{#if bigLogoUrl}} - {{else}} diff --git a/app/assets/javascripts/discourse/templates/components/pagedown-editor.hbs b/app/assets/javascripts/discourse/templates/components/pagedown-editor.hbs deleted file mode 100644 index 52e92ae1fed..00000000000 --- a/app/assets/javascripts/discourse/templates/components/pagedown-editor.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
    -{{textarea value=value class="wmd-input"}} -
    diff --git a/app/assets/javascripts/discourse/templates/components/signup-cta.hbs b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs index 721c38158ea..e4a4ff28d2b 100644 --- a/app/assets/javascripts/discourse/templates/components/signup-cta.hbs +++ b/app/assets/javascripts/discourse/templates/components/signup-cta.hbs @@ -7,7 +7,6 @@ {{else}}

    {{replace-emoji (i18n "signup_cta.intro")}}

    {{replace-emoji (i18n "signup_cta.value_prop")}}

    -

    {{signupMethodsTranslated}}

    {{d-button action="showCreateAccount" label="signup_cta.sign_up" icon="check" class="btn-primary"}} diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs index b57679afc79..9b55d626aea 100644 --- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs @@ -5,8 +5,7 @@ {{#if showDismissAtTop}}
    {{#if showDismissRead}} - - + {{/if}} {{#if showResetNew}} @@ -55,8 +54,7 @@ {{conditional-loading-spinner condition=model.loadingMore}} {{#if allLoaded}} {{#if showDismissRead}} - - + {{/if}} {{#if showResetNew}} diff --git a/app/assets/javascripts/discourse/templates/header.hbs b/app/assets/javascripts/discourse/templates/header.hbs index 9624352a34a..28da2d800c1 100644 --- a/app/assets/javascripts/discourse/templates/header.hbs +++ b/app/assets/javascripts/discourse/templates/header.hbs @@ -22,7 +22,7 @@ mobileAction="fullPageSearch" loginAction="showLogin" title="search.title" - href="search"}} + path="/search"}} {{/header-dropdown}} {{#header-dropdown iconId="toggle-hamburger-menu" diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs index ae964b46e39..3355aef4623 100644 --- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs @@ -25,8 +25,7 @@ {{conditional-loading-spinner condition=model.loadingMore}} {{#if allLoaded}} {{#if showDismissRead}} - - + {{/if}} {{#if showResetNew}} diff --git a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs index aa4370c966a..7a671458ffa 100644 --- a/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/list/topic_list_item.raw.hbs @@ -1,28 +1,35 @@ - -
    - {{raw "list/post-count-or-badges" topic=content postBadgesEnabled=controller.showTopicPostBadges}} -
    -
    +
    -
    - {{#unless controller.hideCategory}} -
    - {{category-link content.category}} +
    + - {{/unless}} - {{plugin-outlet "topic-list-tags"}} -
    -
    - {{raw "list/activity-column" topic=content tagName="span" class="age"}} - {{content.last_poster_username}} +
    + {{raw "list/post-count-or-badges" topic=content postBadgesEnabled=controller.showTopicPostBadges}} +
    + +
    + {{#unless controller.hideCategory}} +
    + {{category-link content.category}} +
    + {{/unless}} + + {{plugin-outlet "topic-list-tags"}} + + + +
    -
    -
    diff --git a/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs b/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs index e7d070d814a..760ab1a580f 100644 --- a/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs +++ b/app/assets/javascripts/discourse/templates/modal/avatar_selector.hbs @@ -9,7 +9,7 @@ {{d-button action="refreshGravatar" title="user.change_avatar.refresh_gravatar_title" disabled=gravatarRefreshDisabled icon="refresh"}}
    - {{#if allowImageUpload}} + {{#if allowAvatarUpload}}