diff --git a/.eslintrc b/.eslintrc index 527935d0f4..86bdc29e2a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,28 +6,27 @@ "browser": true, "builtin": true }, - "ecmaVersion": 7, + "parserOptions": { + "ecmaVersion": 7, + "sourceType": "module" + }, "globals": {"Ember":true, "jQuery":true, "$":true, + "QUnit":true, "RSVP":true, "Discourse":true, "Em":true, "Handlebars":true, "I18n":true, "bootbox":true, - "module":true, "moduleFor":true, "moduleForComponent":true, "Pretender":true, "sandbox":true, "controllerFor":true, "test":true, - "ok":true, - "not":true, - "expect":true, - "equal":true, "visit":true, "andThen":true, "click":true, @@ -48,12 +47,8 @@ "find":true, "sinon":true, "moment":true, - "start":true, "_":true, "alert":true, - "containsInstance":true, - "deepEqual":true, - "notEqual":true, "define":true, "require":true, "requirejs":true, diff --git a/.travis.yml b/.travis.yml index 8959606bbe..5eaaec0902 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ before_install: - git clone --depth=1 https://github.com/discourse/discourse-cakeday.git plugins/discourse-cakeday - git clone --depth=1 https://github.com/discourse/discourse-canned-replies.git plugins/discourse-canned-replies - git clone --depth=1 https://github.com/discourse/discourse-slack-official.git plugins/discourse-slack-official - - yarn global add eslint@3 babel-eslint + - yarn global add eslint babel-eslint - eslint app/assets/javascripts - eslint --ext .es6 app/assets/javascripts - eslint --ext .es6 test/javascripts diff --git a/Gemfile b/Gemfile index 5bf31ec332..ff17ddd052 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ end gem 'mail' gem 'mime-types', require: 'mime/types/columnar' +gem 'mini_mime' gem 'hiredis' gem 'redis', require: ["redis", "redis/connection/hiredis"] @@ -74,6 +75,10 @@ gem 'discourse_image_optim', require: 'image_optim' gem 'multi_json' gem 'mustache' gem 'nokogiri' + +# this may end up deprecating nokogiri +gem 'oga', require: false + gem 'omniauth' gem 'omniauth-openid' gem 'openid-redis-store' @@ -94,13 +99,13 @@ gem 'r2', '~> 0.2.5', require: false gem 'rake' gem 'thor', require: false -gem 'rest-client' gem 'rinku' gem 'sanitize' gem 'sidekiq' # for sidekiq web -gem 'sinatra', require: false +gem 'tilt', require: false + gem 'execjs', require: false gem 'mini_racer' gem 'highline', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 1214cbfb3a..58bddb6b38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -42,7 +42,9 @@ GEM annotate (2.7.2) activerecord (>= 3.2, < 6.0) rake (>= 10.4, < 13.0) + ansi (1.5.0) arel (6.0.4) + ast (2.3.0) aws-sdk (2.5.3) aws-sdk-resources (= 2.5.3) aws-sdk-core (2.5.3) @@ -86,8 +88,6 @@ GEM image_size (~> 1.5) in_threads (~> 1.3) progress (~> 3.0, >= 3.0.1) - domain_name (0.5.20170404) - unf (>= 0.0.5, < 1.0.0) email_reply_trimmer (0.1.6) ember-data-source (2.2.1) ember-source (>= 1.8, < 3.0) @@ -101,7 +101,7 @@ GEM ember-source (>= 1.1.0) jquery-rails (>= 1.0.17) railties (>= 3.1) - ember-source (2.10.2) + ember-source (2.13.3) erubis (2.7.0) excon (0.56.0) execjs (2.7.0) @@ -130,8 +130,6 @@ GEM highline (1.7.8) hiredis (0.6.1) htmlentities (4.3.4) - http-cookie (1.0.3) - domain_name (~> 0.5) http_accept_language (2.0.5) i18n (0.8.4) image_size (1.5.0) @@ -160,6 +158,7 @@ GEM metaclass (0.0.4) method_source (0.8.2) mime-types (2.99.3) + mini_mime (0.1.3) mini_portile2 (2.2.0) mini_racer (0.1.9) libv8 (~> 5.3) @@ -173,7 +172,6 @@ GEM multi_xml (0.6.0) multipart-post (2.0.0) mustache (1.0.5) - netrc (0.11.0) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) nokogumbo (1.4.13) @@ -185,6 +183,9 @@ GEM multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) + oga (2.10) + ast + ruby-ll (~> 2.1) oj (3.1.0) omniauth (1.6.1) hashie (>= 3.4.6, < 3.6.0) @@ -214,7 +215,7 @@ GEM omniauth-twitter (1.3.0) omniauth-oauth (~> 1.1) rack - onebox (1.8.12) + onebox (1.8.13) fast_blank (>= 1.0.0) htmlentities (~> 4.3) moneta (~> 1.0) @@ -288,10 +289,6 @@ GEM redis (3.3.3) redis-namespace (1.5.3) redis (~> 3.0, >= 3.0.4) - rest-client (1.8.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 3.0) - netrc (~> 0.7) rinku (2.0.2) rmmseg-cpp (0.2.9) rspec (3.6.0) @@ -319,6 +316,9 @@ GEM rspec-support (~> 3.6.0) rspec-support (3.6.0) rtlit (0.0.5) + ruby-ll (2.1.2) + ansi + ast ruby-openid (2.7.0) ruby-readability (0.7.0) guess_html_encoding (>= 0.0.4) @@ -343,16 +343,12 @@ GEM shoulda-context (1.2.2) shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (5.0.2) + sidekiq (5.0.3) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) redis (~> 3.3, >= 3.3.3) simple-rss (1.3.1) - sinatra (1.4.8) - rack (~> 1.5) - rack-protection (~> 1.4) - tilt (>= 1.3, < 3) slop (3.6.0) spork (1.0.0rc4) spork-rails (4.0.0) @@ -432,6 +428,7 @@ DEPENDENCIES memory_profiler message_bus mime-types + mini_mime mini_racer minitest mocha @@ -439,6 +436,7 @@ DEPENDENCIES multi_json mustache nokogiri + oga oj omniauth omniauth-facebook @@ -465,7 +463,6 @@ DEPENDENCIES rbtrace redis redis-namespace - rest-client rinku rmmseg-cpp rspec @@ -479,11 +476,11 @@ DEPENDENCIES shoulda sidekiq simple-rss - sinatra spork-rails stackprof test_after_commit thor + tilt timecop uglifier unf @@ -491,4 +488,4 @@ DEPENDENCIES webmock BUNDLED WITH - 1.14.6 + 1.15.1 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 8065d732fc..eb03301c92 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 @@ -2,11 +2,13 @@ import EmailPreview from 'admin/models/email-preview'; import { popupAjaxError } from 'discourse/lib/ajax-error'; export default Ember.Controller.extend({ + username: null, + lastSeen: null, - emailEmpty: Em.computed.empty('email'), - sendEmailDisabled: Em.computed.or('emailEmpty', 'sendingEmail'), - showSendEmailForm: Em.computed.notEmpty('model.html_content'), - htmlEmpty: Em.computed.empty('model.html_content'), + emailEmpty: Ember.computed.empty('email'), + sendEmailDisabled: Ember.computed.or('emailEmpty', 'sendingEmail'), + showSendEmailForm: Ember.computed.notEmpty('model.html_content'), + htmlEmpty: Ember.computed.empty('model.html_content'), actions: { refresh() { @@ -14,7 +16,14 @@ export default Ember.Controller.extend({ this.set('loading', true); this.set('sentEmail', false); - EmailPreview.findDigest(this.get('lastSeen'), this.get('username')).then(email => { + + let username = this.get('username'); + if (!username) { + username = this.currentUser.get('username'); + this.set('username', username); + } + + EmailPreview.findDigest(username, this.get('lastSeen')).then(email => { model.setProperties(email.getProperties('html_content', 'text_content')); this.set('loading', false); }); @@ -28,16 +37,14 @@ export default Ember.Controller.extend({ this.set('sendingEmail', true); this.set('sentEmail', false); - const self = this; - - EmailPreview.sendDigest(this.get('lastSeen'), this.get('username'), this.get('email')).then(result => { + EmailPreview.sendDigest(this.get('username'), this.get('lastSeen'), this.get('email')).then(result => { if (result.errors) { bootbox.alert(result.errors); } else { - self.set('sentEmail', true); + this.set('sentEmail', true); } - }).catch(popupAjaxError).finally(function() { - self.set('sendingEmail', false); + }).catch(popupAjaxError).finally(() => { + this.set('sendingEmail', false); }); } } diff --git a/app/assets/javascripts/admin/controllers/admin-group.js.es6 b/app/assets/javascripts/admin/controllers/admin-group.js.es6 index a0f4961b62..52dbc07791 100644 --- a/app/assets/javascripts/admin/controllers/admin-group.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-group.js.es6 @@ -15,6 +15,15 @@ export default Ember.Controller.extend({ ]; }.property(), + visibilityLevelOptions: function() { + return [ + { name: I18n.t("groups.visibility_levels.public"), value: 0 }, + { name: I18n.t("groups.visibility_levels.members"), value: 1 }, + { name: I18n.t("groups.visibility_levels.staff"), value: 2 }, + { name: I18n.t("groups.visibility_levels.owners"), value: 3 } + ]; + }.property(), + trustLevelOptions: function() { return [ { name: I18n.t("groups.trust_levels.none"), value: 0 }, @@ -22,14 +31,16 @@ export default Ember.Controller.extend({ ]; }.property(), - @computed('model.visible', 'model.public') - disableMembershipRequestSetting(visible, publicGroup) { - return !visible || publicGroup; + @computed('model.visibility_level', 'model.public') + disableMembershipRequestSetting(visibility_level, publicGroup) { + visibility_level = parseInt(visibility_level); + return (visibility_level !== 0) || publicGroup; }, - @computed('model.visible', 'model.allow_membership_requests') - disablePublicSetting(visible, allowMembershipRequests) { - return !visible || allowMembershipRequests; + @computed('model.visibility_level', 'model.allow_membership_requests') + disablePublicSetting(visibility_level, allowMembershipRequests) { + visibility_level = parseInt(visibility_level); + return (visibility_level !== 0) || allowMembershipRequests; }, actions: { diff --git a/app/assets/javascripts/admin/models/email-preview.js.es6 b/app/assets/javascripts/admin/models/email-preview.js.es6 index acc7462b92..2aaca02d1e 100644 --- a/app/assets/javascripts/admin/models/email-preview.js.es6 +++ b/app/assets/javascripts/admin/models/email-preview.js.es6 @@ -1,42 +1,24 @@ import { ajax } from 'discourse/lib/ajax'; const EmailPreview = Discourse.Model.extend({}); +export function oneWeekAgo() { + return moment().locale('en').subtract(7, 'days').format('YYYY-MM-DD'); +} + EmailPreview.reopenClass({ - findDigest: function(lastSeenAt, username) { - - if (Em.isEmpty(lastSeenAt)) { - lastSeenAt = this.oneWeekAgo(); - } - - if (Em.isEmpty(username)) { - username = Discourse.User.current().username; - } + findDigest(username, lastSeenAt) { return ajax("/admin/email/preview-digest.json", { - data: { last_seen_at: lastSeenAt, username: username } - }).then(function (result) { - return EmailPreview.create(result); - }); + data: { last_seen_at: lastSeenAt || oneWeekAgo(), username } + }).then(result => EmailPreview.create(result)); }, - sendDigest: function(lastSeenAt, username, email) { - if (Em.isEmpty(lastSeenAt)) { - lastSeenAt = this.oneWeekAgo(); - } - - if (Em.isEmpty(username)) { - username = Discourse.User.current().username; - } - + sendDigest(username, lastSeenAt, email) { return ajax("/admin/email/send-digest.json", { - data: { last_seen_at: lastSeenAt, username: username, email: email } + data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email } }); }, - oneWeekAgo() { - const en = moment().locale('en'); - return en.subtract(7, 'days').format('YYYY-MM-DD'); - } }); export default EmailPreview; diff --git a/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 b/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 index 7ca2f72772..0ee7b69755 100644 --- a/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-preview-digest.js.es6 @@ -1,16 +1,17 @@ -import EmailPreview from 'admin/models/email-preview'; +import { default as EmailPreview, oneWeekAgo } from 'admin/models/email-preview'; export default Discourse.Route.extend({ model() { - return EmailPreview.findDigest(); + return EmailPreview.findDigest(this.currentUser.get('username')); }, afterModel(model) { const controller = this.controllerFor('adminEmailPreviewDigest'); controller.setProperties({ - model: model, - lastSeen: moment().subtract(7, 'days').format('YYYY-MM-DD'), + model, + username: this.currentUser.get('username'), + lastSeen: oneWeekAgo(), showHtml: true }); } diff --git a/app/assets/javascripts/admin/routes/admin-group.js.es6 b/app/assets/javascripts/admin/routes/admin-group.js.es6 index 3575496edd..0009f834e1 100644 --- a/app/assets/javascripts/admin/routes/admin-group.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-group.js.es6 @@ -4,7 +4,7 @@ export default Discourse.Route.extend({ model(params) { if (params.name === 'new') { - return Group.create({ automatic: false, visible: true }); + return Group.create({ automatic: false, visibility_level: 0 }); } const group = this.modelFor('adminGroupsType').findBy('name', params.name); diff --git a/app/assets/javascripts/admin/templates/email-preview-digest.hbs b/app/assets/javascripts/admin/templates/email-preview-digest.hbs index 7f31c6e9bf..87372268c7 100644 --- a/app/assets/javascripts/admin/templates/email-preview-digest.hbs +++ b/app/assets/javascripts/admin/templates/email-preview-digest.hbs @@ -1,11 +1,11 @@

{{i18n 'admin.email.preview_digest_desc'}}

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

- {{{unbound post.excerpt}}} + {{{post.excerpt}}}

diff --git a/app/assets/javascripts/discourse/templates/components/queued-post.hbs b/app/assets/javascripts/discourse/templates/components/queued-post.hbs index 6ddd37b864..4b094fdb21 100644 --- a/app/assets/javascripts/discourse/templates/components/queued-post.hbs +++ b/app/assets/javascripts/discourse/templates/components/queued-post.hbs @@ -34,7 +34,7 @@ {{#if editing}} {{d-editor value=buffered.raw}} {{else}} - {{{cook-text post.raw}}} + {{cook-text post.raw}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs index 71a9c1b619..e3ac643ec6 100644 --- a/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs +++ b/app/assets/javascripts/discourse/templates/components/search-advanced-options.hbs @@ -41,6 +41,9 @@
{{tag-chooser tags=searchedTerms.tags blacklist=searchedTerms.tags allowCreate=false placeholder="" everyTag="true" unlimitedTagCount="true" width="70%"}} +
+ +
diff --git a/app/assets/javascripts/discourse/templates/components/stream-item.hbs b/app/assets/javascripts/discourse/templates/components/stream-item.hbs index fed530753e..ea3c9dcec9 100644 --- a/app/assets/javascripts/discourse/templates/components/stream-item.hbs +++ b/app/assets/javascripts/discourse/templates/components/stream-item.hbs @@ -1,11 +1,21 @@
{{avatar item imageSize="large" extraClasses="actor" ignoreTitle="true"}}
{{format-date item.created_at}} + {{expand-post item=item}} {{topic-status topic=item disableActions=true}} {{{item.title}}}
{{category-link item.category}}
+ + {{#if item.deleted_by}} + + {{fa-icon "trash-o"}} + {{avatar item.deleted_by imageSize="tiny" extraClasses="actor" ignoreTitle="true"}} + {{format-date item.deleted_at leaveAgo="true"}} + + {{/if}} + {{plugin-outlet name="user-stream-item-header" args=(hash item=item)}}
@@ -20,7 +30,7 @@ {{#each child.items as |grandChild|}} {{#if grandChild.removableBookmark}} - {{else}} diff --git a/app/assets/javascripts/discourse/templates/components/topic-progress.hbs b/app/assets/javascripts/discourse/templates/components/topic-progress.hbs index 1e2dfce6cc..e368600dda 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-progress.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-progress.hbs @@ -1,3 +1,7 @@ +{{#unless hideProgress}} + {{yield}} +{{/unless}} + {{#if showBackButton}}
{{d-button label="topic.timeline.back" class="btn-primary progress-back" action="goBack" icon="arrow-down"}} diff --git a/app/assets/javascripts/discourse/templates/components/topic-title.hbs b/app/assets/javascripts/discourse/templates/components/topic-title.hbs new file mode 100644 index 0000000000..8cea0f5c6c --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/topic-title.hbs @@ -0,0 +1,6 @@ +
+
+ {{yield}} +
+ {{plugin-outlet name="topic-title" args=(hash model=model)}} +
diff --git a/app/assets/javascripts/discourse/templates/components/user-stream.hbs b/app/assets/javascripts/discourse/templates/components/user-stream.hbs new file mode 100644 index 0000000000..31783b50bd --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/user-stream.hbs @@ -0,0 +1,3 @@ +{{#each stream.content as |item|}} + {{stream-item item=item removeBookmark=(action "removeBookmark")}} +{{/each}} diff --git a/app/assets/javascripts/discourse/templates/emoji-toolbar.raw.hbs b/app/assets/javascripts/discourse/templates/emoji-toolbar.raw.hbs index b7e39bb37b..fb684b57d6 100644 --- a/app/assets/javascripts/discourse/templates/emoji-toolbar.raw.hbs +++ b/app/assets/javascripts/discourse/templates/emoji-toolbar.raw.hbs @@ -14,22 +14,32 @@
-
- diff --git a/app/assets/javascripts/discourse/templates/full-page-search.hbs b/app/assets/javascripts/discourse/templates/full-page-search.hbs index bfeab09cfe..e3962efe12 100644 --- a/app/assets/javascripts/discourse/templates/full-page-search.hbs +++ b/app/assets/javascripts/discourse/templates/full-page-search.hbs @@ -107,7 +107,7 @@ {{#if result.blurb}} - {{#highlight-text highlight=q}} + {{#highlight-text highlight=highlightQuery}} {{{unbound result.blurb}}} {{/highlight-text}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/group-activity-posts.hbs b/app/assets/javascripts/discourse/templates/group-activity-posts.hbs index 7dbeebb300..fd8a69a47d 100644 --- a/app/assets/javascripts/discourse/templates/group-activity-posts.hbs +++ b/app/assets/javascripts/discourse/templates/group-activity-posts.hbs @@ -1 +1,11 @@ -{{group-post-stream posts=model emptyText=emptyText loadMore="loadMore" loading=loading}} +{{#load-more selector=".user-stream .item" action=(action "loadMore")}} +
+ {{#each model as |post|}} + {{group-post post=post}} + {{else}} +
{{i18n emptyText}}
+ {{/each}} +
+{{/load-more}} + +{{conditional-loading-spinner condition=loading}} diff --git a/app/assets/javascripts/discourse/templates/group.hbs b/app/assets/javascripts/discourse/templates/group.hbs index 95fcabb36a..24afa5a0c7 100644 --- a/app/assets/javascripts/discourse/templates/group.hbs +++ b/app/assets/javascripts/discourse/templates/group.hbs @@ -43,9 +43,7 @@ {{/each}} {{/mobile-nav}} - {{group-membership-button model=model - createNewMessageViaParams='createNewMessageViaParams' - showLogin='showLogin'}} + {{group-membership-button model=model showLogin='showLogin'}} diff --git a/app/assets/javascripts/discourse/templates/groups.hbs b/app/assets/javascripts/discourse/templates/groups.hbs index 98faa7e417..296324e773 100644 --- a/app/assets/javascripts/discourse/templates/groups.hbs +++ b/app/assets/javascripts/discourse/templates/groups.hbs @@ -45,7 +45,6 @@ {{#group-membership-button model=group - createNewMessageViaParams='createNewMessageViaParams' showMembershipStatus=true groupUserIds=groups.extras.group_user_ids showLogin='showLogin'}} diff --git a/app/assets/javascripts/discourse/templates/invites/show.hbs b/app/assets/javascripts/discourse/templates/invites/show.hbs index 80eb832a63..69f3611a79 100644 --- a/app/assets/javascripts/discourse/templates/invites/show.hbs +++ b/app/assets/javascripts/discourse/templates/invites/show.hbs @@ -41,7 +41,7 @@ {{password-field value=accountPassword type="password" id="new-account-password" capsLockOn=capsLockOn}}  {{input-tip validation=passwordValidation}}
- {{passwordInstructions}} + {{passwordInstructions}} {{i18n 'invites.optional_description'}}
{{i18n 'login.caps_lock_warning'}}
diff --git a/app/assets/javascripts/discourse/templates/modal/forgot-password.hbs b/app/assets/javascripts/discourse/templates/modal/forgot-password.hbs index 8a8db45cce..c739f77197 100644 --- a/app/assets/javascripts/discourse/templates/modal/forgot-password.hbs +++ b/app/assets/javascripts/discourse/templates/modal/forgot-password.hbs @@ -1,9 +1,27 @@
- {{#d-modal-body}} - - {{text-field value=accountEmailOrUsername placeholderKey="login.email_placeholder" id="username-or-email" autocorrect="off" autocapitalize="off"}} + {{#d-modal-body class="forgot-password-modal"}} + {{#unless offerHelp}} + + {{text-field value=accountEmailOrUsername placeholderKey="login.email_placeholder" id="username-or-email" autocorrect="off" autocapitalize="off"}} + {{else}} + {{{offerHelp}}} + {{/unless}} {{/d-modal-body}}
diff --git a/app/assets/javascripts/discourse/templates/navigation/categories.hbs b/app/assets/javascripts/discourse/templates/navigation/categories.hbs index a324ccebc7..567e484a99 100644 --- a/app/assets/javascripts/discourse/templates/navigation/categories.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/categories.hbs @@ -7,6 +7,11 @@ {{categories-admin-dropdown}} {{/if}} {{#if canCreateTopic}} - + {{d-button + id="create-topic" + action="createTopic" + icon="plus" + label="topic.create" + }} {{/if}} {{/d-section}} diff --git a/app/assets/javascripts/discourse/templates/navigation/default.hbs b/app/assets/javascripts/discourse/templates/navigation/default.hbs index c8cc96a9e7..07fb2f693d 100644 --- a/app/assets/javascripts/discourse/templates/navigation/default.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/default.hbs @@ -4,6 +4,6 @@ {{navigation-bar navItems=navItems filterMode=filterMode}} {{#if canCreateTopic}} - + {{/if}} {{/d-section}} diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 24f4950982..35b9ed3375 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -10,59 +10,53 @@ {{#if model.postStream.loaded}} {{#if model.postStream.firstPostPresent}} -
-
+ {{#topic-title cancelled="cancelEditingTopic" save="finishedEditingTopic"}} + {{#if editingTopic}} + {{#if model.isPrivateMessage}} + {{fa-icon "envelope"}} + {{/if}} -
- {{#if editingTopic}} - {{#if model.isPrivateMessage}} + {{text-field id="edit-title" value=buffered.title maxlength=siteSettings.max_topic_title_length autofocus="true"}} + {{#if showCategoryChooser}} +
+ {{category-chooser valueAttribute="id" value=buffered.category_id}} + {{/if}} + + {{#if canEditTags}} +
+ {{tag-chooser tags=buffered.tags categoryId=buffered.category_id}} + {{/if}} + + {{plugin-outlet name="edit-topic" args=(hash model=model buffered=buffered)}} + +
+ {{d-button action="finishedEditingTopic" class="btn-primary btn-small submit-edit" icon="check"}} + {{d-button action="cancelEditingTopic" class="btn-small cancel-edit" icon="times"}} + {{else}} +

+ {{#unless model.is_warning}} + {{fa-icon "envelope"}} - {{/if}} + + {{/unless}} - {{text-field id="edit-title" value=buffered.title maxlength=siteSettings.max_topic_title_length autofocus="true"}} - {{#if showCategoryChooser}} -
- {{category-chooser valueAttribute="id" value=buffered.category_id}} - {{/if}} - - {{#if canEditTags}} -
- {{tag-chooser tags=buffered.tags categoryId=buffered.category_id}} - {{/if}} - - {{plugin-outlet name="edit-topic" args=(hash model=model buffered=buffered)}} - -
- {{d-button action="finishedEditingTopic" class="btn-primary btn-small submit-edit" icon="check"}} - {{d-button action="cancelEditingTopic" class="btn-small cancel-edit" icon="times"}} - {{else}} -

- {{#unless model.is_warning}} - - {{fa-icon "envelope"}} - - {{/unless}} - - {{#if model.details.loaded}} - {{topic-status topic=model}} - - {{{model.fancyTitle}}} - - {{/if}} - - {{#if model.details.can_edit}} - {{fa-icon "pencil"}} - {{/if}} -

- - {{#unless model.isPrivateMessage}} - {{topic-category topic=model class="topic-category"}} - {{/unless}} + {{#if model.details.loaded}} + {{topic-status topic=model}} + + {{{model.fancyTitle}}} + {{/if}} -
- {{plugin-outlet name="topic-title" args=(hash model=model)}} -
-
+ + {{#if model.details.can_edit}} + {{fa-icon "pencil"}} + {{/if}} + + + {{#unless model.isPrivateMessage}} + {{topic-category topic=model class="topic-category"}} + {{/unless}} + {{/if}} + {{/topic-title}} {{/if}}
@@ -71,24 +65,24 @@
{{#topic-navigation topic=model jumpToIndex=(action "jumpToIndex") as |info|}} - {{#if info.renderAdminMenuButton}} - {{topic-admin-menu-button - topic=model - fixed="true" - toggleMultiSelect=(action "toggleMultiSelect") - deleteTopic=(action "deleteTopic") - recoverTopic=(action "recoverTopic") - toggleClosed=(action "toggleClosed") - toggleArchived=(action "toggleArchived") - toggleVisibility=(action "toggleVisibility") - showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate") - showFeatureTopic=(action "topicRouteAction" "showFeatureTopic") - showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp") - convertToPublicTopic=(action "convertToPublicTopic") - convertToPrivateMessage=(action "convertToPrivateMessage")}} - {{/if}} - {{#if info.renderTimeline}} + {{#if info.renderAdminMenuButton}} + {{topic-admin-menu-button + topic=model + fixed="true" + toggleMultiSelect=(action "toggleMultiSelect") + deleteTopic=(action "deleteTopic") + recoverTopic=(action "recoverTopic") + toggleClosed=(action "toggleClosed") + toggleArchived=(action "toggleArchived") + toggleVisibility=(action "toggleVisibility") + showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate") + showFeatureTopic=(action "topicRouteAction" "showFeatureTopic") + showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp") + convertToPublicTopic=(action "convertToPublicTopic") + convertToPrivateMessage=(action "convertToPrivateMessage")}} + {{/if}} + {{topic-timeline topic=model prevEvent=info.prevEvent @@ -113,11 +107,29 @@ convertToPublicTopic=(action "convertToPublicTopic") convertToPrivateMessage=(action "convertToPrivateMessage")}} {{else}} - {{topic-progress + {{#topic-progress prevEvent=info.prevEvent topic=model expanded=info.topicProgressExpanded jumpToPost=(action "jumpToPost")}} + {{#if info.renderAdminMenuButton}} + {{topic-admin-menu-button + topic=model + openUpwards="true" + rightSide="true" + toggleMultiSelect=(action "toggleMultiSelect") + deleteTopic=(action "deleteTopic") + recoverTopic=(action "recoverTopic") + toggleClosed=(action "toggleClosed") + toggleArchived=(action "toggleArchived") + toggleVisibility=(action "toggleVisibility") + showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate") + showFeatureTopic=(action "topicRouteAction" "showFeatureTopic") + showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp") + convertToPublicTopic=(action "convertToPublicTopic") + convertToPrivateMessage=(action "convertToPrivateMessage")}} + {{/if}} + {{/topic-progress}} {{/if}} {{/topic-navigation}} diff --git a/app/assets/javascripts/discourse/templates/user-invited-show.hbs b/app/assets/javascripts/discourse/templates/user-invited-show.hbs index 0a78d6e64f..12a26aa3a0 100644 --- a/app/assets/javascripts/discourse/templates/user-invited-show.hbs +++ b/app/assets/javascripts/discourse/templates/user-invited-show.hbs @@ -19,7 +19,12 @@ {{csv-uploader uploading=uploading}} {{fa-icon "question-circle"}} {{/if}} - {{#if showReinviteAllButton}} + {{#if showBulkActionButtons}} + {{#if rescindedAll}} + {{i18n 'user.invited.rescinded_all'}} + {{else}} + {{d-button icon="times" action="rescindAll" class="btn" label="user.invited.rescind_all"}} + {{/if}} {{#if reinvitedAll}} {{i18n 'user.invited.reinvited_all'}} {{else}} diff --git a/app/assets/javascripts/discourse/templates/user/posts.hbs b/app/assets/javascripts/discourse/templates/user/posts.hbs index 08fc6f5f27..b1743bf077 100644 --- a/app/assets/javascripts/discourse/templates/user/posts.hbs +++ b/app/assets/javascripts/discourse/templates/user/posts.hbs @@ -1,30 +1 @@ -{{#user-stream stream=model}} - {{#each model.content as |p|}} -
-
- -
- {{avatar p imageSize="large" extraClasses="actor" ignoreTitle="true"}} -
-
- - {{format-date p.created_at leaveAgo="true"}} - - - {{unbound p.topic_title}} - - - {{category-link p.category}} - - {{#if p.deleted}} - - {{avatar p.deleted_by imageSize="tiny" extraClasses="actor" ignoreTitle="true"}} {{format-date p.deleted_at leaveAgo="true"}} - - {{/if}} -
-

- {{{p.excerpt}}} -

-
- {{/each}} -{{/user-stream}} +{{user-stream stream=model}} diff --git a/app/assets/javascripts/discourse/templates/user/stream.hbs b/app/assets/javascripts/discourse/templates/user/stream.hbs index 220ae57ba8..2801c293ba 100644 --- a/app/assets/javascripts/discourse/templates/user/stream.hbs +++ b/app/assets/javascripts/discourse/templates/user/stream.hbs @@ -3,8 +3,4 @@ {{{model.noContentHelp}}} {{/if}} -{{#user-stream stream=model}} - {{#each model.content as |item|}} - {{stream-item item=item removeBookmark="removeBookmark"}} - {{/each}} -{{/user-stream}} +{{user-stream stream=model}} diff --git a/app/assets/javascripts/discourse/templates/user/summary.hbs b/app/assets/javascripts/discourse/templates/user/summary.hbs index 679bd10259..19150c2be8 100644 --- a/app/assets/javascripts/discourse/templates/user/summary.hbs +++ b/app/assets/javascripts/discourse/templates/user/summary.hbs @@ -139,7 +139,7 @@ -
+