diff --git a/assets/javascripts/discourse/lib/babble.js.es6 b/assets/javascripts/discourse/lib/babble.js.es6 index 2dddca3..e1096e4 100644 --- a/assets/javascripts/discourse/lib/babble.js.es6 +++ b/assets/javascripts/discourse/lib/babble.js.es6 @@ -294,6 +294,8 @@ export default Ember.Object.create({ post.created_at = moment(post.created_at.replace(' UTC', 'Z')).local().toString() } + post.yours = post.user_id == User.currentProp('id') + if (data.is_edit || data.is_delete) { topic.postStream.storePost(post) if (topic.get('loadingEditId') == data.id) { @@ -332,6 +334,14 @@ export default Ember.Object.create({ handleTyping(topic, data) { if (data.id == User.currentProp('id')) { return } topic.typing[data.username] = { user: data, lastTyped: moment() } + + forEachTopicContainer(topic, $container => { + const postNumber = lastVisibleElement($container.find('.babble-chat'), '.babble-post', 'post-number') + if (postNumber === topic.highest_post_number) { + scrollToPost(topic, postNumber, 400, 80) + } + }) + rerender(topic) }, diff --git a/assets/javascripts/discourse/templates/admin/chat.hbs b/assets/javascripts/discourse/templates/admin/chat.hbs index a8ac947..3fd8bf1 100644 --- a/assets/javascripts/discourse/templates/admin/chat.hbs +++ b/assets/javascripts/discourse/templates/admin/chat.hbs @@ -39,7 +39,7 @@ <div class='buttons'> <button {{action "save"}} disabled={{disableSave}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button> {{#if model.id}} - <button {{action "destroy"}} class='btn btn-danger'>{{d-icon "trash-o"}}{{i18n 'admin.customize.delete'}}</button> + <button {{action "destroy"}} class='btn btn-danger'>{{d-icon "far-trash-alt"}}{{i18n 'admin.customize.delete'}}</button> {{/if}} </div> </form> diff --git a/assets/javascripts/discourse/widgets/templates/babble-chat.js.es6 b/assets/javascripts/discourse/widgets/templates/babble-chat.js.es6 index 83a6dfa..6851702 100644 --- a/assets/javascripts/discourse/widgets/templates/babble-chat.js.es6 +++ b/assets/javascripts/discourse/widgets/templates/babble-chat.js.es6 @@ -18,7 +18,6 @@ export default Ember.Object.create({ h('ul', {className: 'babble-posts'}, this.chatView()), this.pressurePlate('asc') ]), - this.widget.attach('babble-typing', { topic: this.topic }), this.widget.attach('babble-composer', { topic: this.topic, csrf: this.csrf }) ] if (!this.widget.attrs.fullpage) { @@ -54,16 +53,13 @@ export default Ember.Object.create({ break } - if (this.topic.loadingPosts) { - return h('span.babble-load-message', I18n.t('babble.loading_messages')) - } else if (canLoadMore) { + if (canLoadMore) { return this.widget.attach('button', { - label: 'babble.load_more', + label: this.topic.loadingPosts ? 'babble.loading_messages' : 'babble.load_more', className: `babble-load-message babble-pressure-plate ${order}`, + disabled: this.topic.loadingPosts, action: actionName }) - } else { - return h('span.babble-load-message', I18n.t('babble.no_more_messages')) } }, @@ -118,7 +114,7 @@ export default Ember.Object.create({ isFollowOn: isFollowOn(post, posts[index-1]), isNewDay: isNewDay(post, posts[index-1]) }) - }) + }).concat(this.widget.attach('babble-typing', { topic: this.topic })) } else { return h('li.babble-empty-topic-message', I18n.t('babble.empty_topic_message')) } diff --git a/assets/javascripts/discourse/widgets/templates/babble-post-actions.js.es6 b/assets/javascripts/discourse/widgets/templates/babble-post-actions.js.es6 index 9a721b5..bff1a57 100644 --- a/assets/javascripts/discourse/widgets/templates/babble-post-actions.js.es6 +++ b/assets/javascripts/discourse/widgets/templates/babble-post-actions.js.es6 @@ -50,7 +50,7 @@ export default Ember.Object.create({ delete() { if (this.post.can_delete) { - return this.widget.attach('link', { className: 'btn', icon: 'trash-o', action: 'delete', label: 'user.admin_delete' }) + return this.widget.attach('link', { className: 'btn', icon: 'far-trash-alt', action: 'delete', label: 'user.admin_delete' }) } } diff --git a/assets/javascripts/discourse/widgets/templates/babble-post.js.es6 b/assets/javascripts/discourse/widgets/templates/babble-post.js.es6 index 373d61e..b18ccde 100644 --- a/assets/javascripts/discourse/widgets/templates/babble-post.js.es6 +++ b/assets/javascripts/discourse/widgets/templates/babble-post.js.es6 @@ -1,6 +1,6 @@ import { h } from 'virtual-dom' import RawHtml from 'discourse/widgets/raw-html'; -import { relativeAge } from 'discourse/lib/formatter' +import { relativeAge, longDate } from 'discourse/lib/formatter' import { avatarImg } from 'discourse/widgets/post' import { emojiUnescape } from 'discourse/lib/text' import { iconNode } from "discourse-common/lib/icon-library"; @@ -29,14 +29,12 @@ export default Ember.Object.create({ contents() { return [ - h('a.babble-avatar-wrapper', { attributes: { - 'data-user-card': this.post.username, - 'href': `/u/${this.post.username}` - } }, this.avatar()), + this.avatarWrapper(), h('div.babble-post-content-wrapper', [ this.title(), - this.body() - ]) + this.body(), + ]), + this.post.yours ? null : this.actions() ] }, @@ -59,13 +57,22 @@ export default Ember.Object.create({ } }, + avatarWrapper() { + if (this.post.yours) { return } + + return h('a.babble-avatar-wrapper', { attributes: { + 'data-user-card': this.post.username, + 'href': `/u/${this.post.username}` + } }, this.avatar()) + }, + avatar() { if (this.isFollowOn) { return h('div.babble-avatar-placeholder') } else if (this.post.user_id) { return avatarImg('medium', {template: this.post.avatar_template, username: this.post.username}) } else { - return iconNode('trash-o', { class: 'deleted-user-avatar'} ) + return iconNode('far-trash-alt', { class: 'deleted-user-avatar'} ) } }, @@ -74,29 +81,42 @@ export default Ember.Object.create({ }, postDate() { - return h('div.babble-post-date', relativeAge(new Date(this.post.created_at))) + const timestamp = new Date(this.post.created_at) + return h('div.babble-post-date', + { attributes: { title: longDate(timestamp) } }, + relativeAge(timestamp) + ) }, postEdited() { - if (!(this.post.self_edits > 0)) { return } - return h('div.babble-post-explainer', I18n.t('babble.post_edited')) + if (this.post.yours || !(this.post.self_edits > 0)) { return } + + return h('div.babble-post-explainer', + { attributes: { title: I18n.t('babble.post_edited') } }, + iconNode('pencil-alt') + ) }, postFlagged() { if (!this.post.has_flagged) { return } - return h('div.babble-post-explainer', `(${I18n.t('babble.flagged').toLowerCase()})`) + + return h('div.babble-post-explainer', + { attributes: { title: I18n.t('babble.flagged') } }, + iconNode('flag') + ) }, title() { + const actions = this.post.yours ? this.actions() : null if (this.isFollowOn) { - return h('div.babble-post-meta-data', this.actions()) + return h('div.babble-post-meta-data', actions) } else { return h('div.babble-post-meta-data', [ this.postName(), this.postDate(), this.postFlagged(), this.postEdited(), - this.actions() + actions ]) } }, diff --git a/assets/javascripts/discourse/widgets/templates/babble-typing.js.es6 b/assets/javascripts/discourse/widgets/templates/babble-typing.js.es6 index ded654c..65e890f 100644 --- a/assets/javascripts/discourse/widgets/templates/babble-typing.js.es6 +++ b/assets/javascripts/discourse/widgets/templates/babble-typing.js.es6 @@ -1,22 +1,24 @@ import { h } from 'virtual-dom' +import { avatarImg } from 'discourse/widgets/post' export default Ember.Object.create({ render(widget) { this.widget = widget - return h('div.babble-typing.clearfix', this.typingSentence(widget.state.topic.typing)) - }, - typingSentence(typing) { - let usernames = _.filter(_.keys(typing), function(username) { - return typing[username].lastTyped > moment().add(-1, 'second') - }) + const typing = widget.state.topic.typing + const now = moment().add(-3, 'second') - if (!usernames.length) { return } + const typers = Object.values(widget.state.topic.typing).filter(({ lastTyped }) => lastTyped > now) + if (!typers.length) { return } setTimeout(() => { this.widget.scheduleRerender() }, 2000) - switch(usernames.length) { - case 1: return I18n.t("babble.typing.single", { first: usernames[0] }) - case 2: return I18n.t("babble.typing.double", { first: usernames[0], second: usernames[1] }) - case 3: return I18n.t("babble.typing.several") - } + + return typers.map(({ user: { username, avatar_template } }) => ( + h('div.babble-post-container.babble-typing-container', [ + h('a.babble-avatar-wrapper', { + attributes: { 'data-user-card': username, 'href': `/u/${username}` } + }, avatarImg('medium', { username, template: avatar_template })), + h('div.babble-typing', [h('span'), h('span'), h('span')]) + ]) + )) } }) diff --git a/assets/stylesheets/babble.scss b/assets/stylesheets/babble.scss index 0c51361..d053b38 100644 --- a/assets/stylesheets/babble.scss +++ b/assets/stylesheets/babble.scss @@ -126,6 +126,18 @@ $default-chat-width: 300px; /* Post styles */ .babble-posts { margin: 0; + min-height: calc(100% - 10px); + display: flex; + flex-direction: column; + width: 100%; + + > :first-child { + margin-top: auto; + } + + > :last-child { + margin-bottom: 8px; + } } .babble-post { @@ -200,13 +212,25 @@ $default-chat-width: 300px; border-radius: 100%; } +[data-my-post=true] .babble-post-container .babble-post-content-wrapper { + margin-left: auto; + background: dark-light-choose($tertiary-low, $tertiary-high); +} + + .babble-post-container { - margin: 8px 0; + margin-top: 8px; position: relative; display: flex; + align-items: flex-start; + + &.babble-typing-container { + align-items: center; + margin-bottom: 4px; + } &.babble-follow-on { - margin: 4px 0; + margin-top: 4px; .babble-post-content-wrapper { flex-direction: row; justify-content: space-between; @@ -228,18 +252,29 @@ $default-chat-width: 300px; .babble-post-date { flex-grow: 1; + font-size: 14px; } .babble-post-explainer { + position: absolute; + height: 16px; + width: 16px; font-size: 12px; - flex-grow: 10; + top: 8px; + right: 4px; + opacity: 0.4; } .babble-post-content-wrapper { + background: dark-light-choose($primary-very-low, $primary-very-low); + border-radius: 5px; + max-width: 80%; + padding: 7px; display: flex; flex-direction: column; flex-grow: 1; word-break: break-word; + position: relative; a.mention { display: inline; } @@ -254,6 +289,7 @@ $default-chat-width: 300px; min-height: 26px; display: flex; flex-direction: row; + align-items: baseline; justify-content: space-between; } @@ -261,6 +297,15 @@ $default-chat-width: 300px; margin: 5px 0 0 0 !important; } + .babble-post-actions.closed { + opacity: 0; + transition: opacity 0.3s ease-in-out; + } + + &:hover .babble-post-actions.closed { + opacity: 1; + } + .babble-post-actions-menu { position: fixed; display: flex; @@ -339,7 +384,37 @@ $default-chat-width: 300px; font-size: 12px; line-height: 14px; min-height: 14px; - padding: 5px 8px; + width: auto; + border-radius: 50px; + padding: 4px 0; + margin: 0 4px; + display: table; + position: relative; + span { + height: 8px; + width: 8px; + float: left; + margin-right: 4px; + background-color: #9E9EA1; + display: block; + border-radius: 50%; + opacity: 0.4; + position: relative; + top: 0; + @for $i from 1 through 3 { + &:nth-of-type(#{$i}) { + animation: 1s blink infinite ($i * .3333s); + } + } + } +} + +@keyframes blink { + 25% { top: 0; } + 33% { top: 1px; } + 50% { top: 2px; } + 66% { top: 1px; } + 75% { top: 0; } } /* Composer styles */ diff --git a/plugin.rb b/plugin.rb index ae20500..02425b4 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,6 +1,6 @@ # name: babble # about: Shoutbox plugin for Discourse -# version: 4.1.3 +# version: 4.2.0 # authors: James Kiesel (gdpelican) # url: https://github.com/gdpelican/babble