FIX: Quoting an avatar when `default_avatars` was set was broken.

This commit is contained in:
Robin Ward 2015-03-12 15:51:28 -04:00
parent 091af27a31
commit 893c1aa067
5 changed files with 63 additions and 99 deletions

View File

@ -8,9 +8,7 @@ export default DiscourseController.extend({
loadScript('defer/html-sanitizer-bundle'); loadScript('defer/html-sanitizer-bundle');
}.on('init'), }.on('init'),
/** // If the buffer is cleared, clear out other state (post)
If the buffer is cleared, clear out other state (post)
**/
bufferChanged: function() { bufferChanged: function() {
if (this.blank('buffer')) this.set('post', null); if (this.blank('buffer')) this.set('post', null);
}.observes('buffer'), }.observes('buffer'),
@ -18,40 +16,38 @@ export default DiscourseController.extend({
/** /**
Save the currently selected text and displays the Save the currently selected text and displays the
"quote reply" button "quote reply" button
@method selectText
**/ **/
selectText: function(postId) { selectText(postId) {
// anonymous users cannot "quote-reply" // anonymous users cannot "quote-reply"
if (!Discourse.User.current()) return; if (!Discourse.User.current()) return;
// don't display the "quote-reply" button if we can't create a post // don't display the "quote-reply" button if we can't create a post
if (!this.get('controllers.topic.model.details.can_create_post')) return; if (!this.get('controllers.topic.model.details.can_create_post')) return;
var selection = window.getSelection(); const selection = window.getSelection();
// no selections // no selections
if (selection.rangeCount === 0) return; if (selection.rangeCount === 0) return;
// retrieve the selected range // retrieve the selected range
var range = selection.getRangeAt(0), const range = selection.getRangeAt(0),
cloned = range.cloneRange(), cloned = range.cloneRange(),
$ancestor = $(range.commonAncestorContainer); $ancestor = $(range.commonAncestorContainer);
if ($ancestor.closest('.cooked').length === 0) { if ($ancestor.closest('.cooked').length === 0) {
this.set('buffer', ''); this.set('buffer', '');
return; return;
} }
var selectedText = Discourse.Utilities.selectedText(); const selectedText = Discourse.Utilities.selectedText();
if (this.get('buffer') === selectedText) return; if (this.get('buffer') === selectedText) return;
// we need to retrieve the post data from the posts collection in the topic controller // we need to retrieve the post data from the posts collection in the topic controller
var postStream = this.get('controllers.topic.postStream'); const postStream = this.get('controllers.topic.postStream');
this.set('post', postStream.findLoadedPost(postId)); this.set('post', postStream.findLoadedPost(postId));
this.set('buffer', selectedText); this.set('buffer', selectedText);
// create a marker element // create a marker element
var markerElement = document.createElement("span"); const markerElement = document.createElement("span");
// containing a single invisible character // containing a single invisible character
markerElement.appendChild(document.createTextNode("\u{feff}")); markerElement.appendChild(document.createTextNode("\u{feff}"));
@ -61,42 +57,37 @@ export default DiscourseController.extend({
range.insertNode(markerElement); range.insertNode(markerElement);
// retrieve the position of the market // retrieve the position of the market
var markerOffset = $(markerElement).offset(), const markerOffset = $(markerElement).offset(),
$quoteButton = $('.quote-button'); $quoteButton = $('.quote-button');
// remove the marker // remove the marker
markerElement.parentNode.removeChild(markerElement); markerElement.parentNode.removeChild(markerElement);
// work around Chrome that would sometimes lose the selection // work around Chrome that would sometimes lose the selection
var sel = window.getSelection(); const sel = window.getSelection();
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange(cloned); sel.addRange(cloned);
// move the quote button above the marker // move the quote button above the marker
Em.run.schedule('afterRender', function() { Em.run.schedule('afterRender', function() {
var top = markerOffset.top; let topOff = markerOffset.top;
var left = markerOffset.left; let leftOff = markerOffset.left;
if (Discourse.Mobile.isMobileDevice) { if (Discourse.Mobile.isMobileDevice) {
top = top + 20; topOff = topOff + 20;
left = Math.min(left + 10, $(window).width() - $quoteButton.outerWidth()); leftOff = Math.min(leftOff + 10, $(window).width() - $quoteButton.outerWidth());
} else { } else {
top = top - $quoteButton.outerHeight() - 5; topOff = topOff - $quoteButton.outerHeight() - 5;
} }
$quoteButton.offset({ top: top, left: left }); $quoteButton.offset({ top: topOff, left: leftOff });
}); });
}, },
/** quoteText() {
Quote the currently selected text const post = this.get('post');
const composerController = this.get('controllers.composer');
@method quoteText const composerOpts = {
**/
quoteText: function() {
var post = this.get('post');
var composerController = this.get('controllers.composer');
var composerOpts = {
action: Discourse.Composer.REPLY, action: Discourse.Composer.REPLY,
draftKey: this.get('post.topic.draft_key') draftKey: this.get('post.topic.draft_key')
}; };
@ -108,13 +99,13 @@ export default DiscourseController.extend({
} }
// If the composer is associated with a different post, we don't change it. // If the composer is associated with a different post, we don't change it.
var composerPost = composerController.get('content.post'); const composerPost = composerController.get('content.post');
if (composerPost && (composerPost.get('id') !== this.get('post.id'))) { if (composerPost && (composerPost.get('id') !== this.get('post.id'))) {
composerOpts.post = composerPost; composerOpts.post = composerPost;
} }
var buffer = this.get('buffer'); const buffer = this.get('buffer');
var quotedText = Discourse.Quote.build(post, buffer); const quotedText = Discourse.Quote.build(post, buffer);
composerOpts.quote = quotedText; composerOpts.quote = quotedText;
if (composerController.get('content.viewOpen') || composerController.get('content.viewDraft')) { if (composerController.get('content.viewOpen') || composerController.get('content.viewDraft')) {
composerController.appendBlockAtCursor(quotedText.trim()); composerController.appendBlockAtCursor(quotedText.trim());
@ -125,12 +116,7 @@ export default DiscourseController.extend({
return false; return false;
}, },
/** deselectText() {
Deselect the currently selected text
@method deselectText
**/
deselectText: function() {
// clear selected text // clear selected text
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
// clean up the buffer // clean up the buffer

View File

@ -1,21 +1,8 @@
/**
Build the BBCode for a Quote
@class BBCode
@namespace Discourse
@module Discourse
**/
Discourse.Quote = { Discourse.Quote = {
REGEXP: /\[quote=([^\]]*)\]((?:[\s\S](?!\[quote=[^\]]*\]))*?)\[\/quote\]/im, REGEXP: /\[quote=([^\]]*)\]((?:[\s\S](?!\[quote=[^\]]*\]))*?)\[\/quote\]/im,
/** // Build the BBCode quote around the selected text
Build the BBCode quote around the selected text
@method buildQuote
@param {Discourse.Post} post The post we are quoting
@param {String} contents The text selected
**/
build: function(post, contents) { build: function(post, contents) {
var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp; var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp;
if (!contents) contents = ""; if (!contents) contents = "";

View File

@ -1,6 +1,7 @@
import userSearch from 'discourse/lib/user-search'; import userSearch from 'discourse/lib/user-search';
import afterTransition from 'discourse/lib/after-transition'; import afterTransition from 'discourse/lib/after-transition';
import loadScript from 'discourse/lib/load-script'; import loadScript from 'discourse/lib/load-script';
import avatarTemplate from 'discourse/lib/avatar-template';
const ComposerView = Discourse.View.extend(Ember.Evented, { const ComposerView = Discourse.View.extend(Ember.Evented, {
_lastKeyTimeout: null, _lastKeyTimeout: null,
@ -244,7 +245,10 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
if (posts) { if (posts) {
const quotedPost = posts.findProperty("post_number", postNumber); const quotedPost = posts.findProperty("post_number", postNumber);
if (quotedPost) { if (quotedPost) {
return Discourse.Utilities.tinyAvatar(quotedPost.get("avatar_template")); const username = quotedPost.get('username'),
uploadId = quotedPost.get('uploaded_avatar_id');
return Discourse.Utilities.tinyAvatar(avatarTemplate(username, uploadId));
} }
} }
} }

View File

@ -1,11 +1,3 @@
/**
This view is used for rendering the pop-up quote button
@class QuoteButtonView
@extends Discourse.View
@namespace Discourse
@module Discourse
**/
export default Discourse.View.extend({ export default Discourse.View.extend({
classNames: ['quote-button'], classNames: ['quote-button'],
classNameBindings: ['visible'], classNameBindings: ['visible'],
@ -13,20 +5,12 @@ export default Discourse.View.extend({
isTouchInProgress: false, isTouchInProgress: false,
/** /**
Determines whether the pop-up quote button should be visible.
The button is visible whenever there is something in the buffer The button is visible whenever there is something in the buffer
(ie. something has been selected) (ie. something has been selected)
@property visible
**/ **/
visible: Em.computed.notEmpty('controller.buffer'), visible: Em.computed.notEmpty('controller.buffer'),
/** render(buffer) {
Renders the pop-up quote button.
@method render
**/
render: function(buffer) {
buffer.push(I18n.t("post.quote_reply")); buffer.push(I18n.t("post.quote_reply"));
}, },
@ -38,15 +22,15 @@ export default Discourse.View.extend({
@method didInsertElement @method didInsertElement
**/ **/
didInsertElement: function() { didInsertElement() {
var controller = this.get('controller'), const controller = this.get('controller'),
view = this; view = this;
$(document) $(document)
.on("mousedown.quote-button", function(e) { .on("mousedown.quote-button", function(e) {
view.set('isMouseDown', true); view.set('isMouseDown', true);
var $target = $(e.target); const $target = $(e.target);
// we don't want to deselect when we click on buttons that use it // we don't want to deselect when we click on buttons that use it
if ($target.hasClass('quote-button') || if ($target.hasClass('quote-button') ||
$target.closest('.create').length || $target.closest('.create').length ||
@ -75,27 +59,17 @@ export default Discourse.View.extend({
}); });
}, },
/** selectText(target, controller) {
Selects the text const $target = $(target);
@method selectText
**/
selectText: function(target, controller) {
var $target = $(target);
// breaks if quoting has been disabled by the user // breaks if quoting has been disabled by the user
if (!Discourse.User.currentProp('enable_quoting')) return; if (!Discourse.User.currentProp('enable_quoting')) return;
// retrieve the post id from the DOM // retrieve the post id from the DOM
var postId = $target.closest('.boxed').data('post-id'); const postId = $target.closest('.boxed').data('post-id');
// select the text // select the text
if (postId) controller.selectText(postId); if (postId) controller.selectText(postId);
}, },
/** willDestroyElement() {
Unbinds from global `mouseup, mousedown, selectionchange` events
@method willDestroyElement
**/
willDestroyElement: function() {
$(document) $(document)
.off("mousedown.quote-button") .off("mousedown.quote-button")
.off("mouseup.quote-button") .off("mouseup.quote-button")
@ -104,12 +78,7 @@ export default Discourse.View.extend({
.off("selectionchange"); .off("selectionchange");
}, },
/** click(e) {
Quote the selected text when clicking on the quote button.
@method click
**/
click: function(e) {
e.stopPropagation(); e.stopPropagation();
return this.get('controller').quoteText(e); return this.get('controller').quoteText(e);
} }

View File

@ -20,12 +20,28 @@ module PrettyText
end end
end end
# function here are available to v8 # functions here are available to v8
def avatar_template(username) def avatar_template(username)
return "" unless username return "" unless username
user = User.find_by(username_lower: username.downcase) user = User.find_by(username_lower: username.downcase)
return "" unless user.present? return "" unless user.present?
schemaless absolute user.avatar_template
# TODO: Add support for ES6 and call `avatar-template` directly
if !user.uploaded_avatar_id && SiteSetting.default_avatars.present?
split_avatars = SiteSetting.default_avatars.split("\n")
if split_avatars.present?
hash = username.each_char.reduce(0) do |result, char|
[((result << 5) - result) + char.ord].pack('L').unpack('l').first
end
avatar_template = split_avatars[hash.abs % split_avatars.size]
end
else
avatar_template = user.avatar_template
end
schemaless absolute avatar_template
end end
def is_username_valid(username) def is_username_valid(username)
@ -65,6 +81,8 @@ module PrettyText
ctx.eval("var window = {}; window.devicePixelRatio = 2;") # hack to make code think stuff is retina ctx.eval("var window = {}; window.devicePixelRatio = 2;") # hack to make code think stuff is retina
ctx.eval("var I18n = {}; I18n.t = function(a,b){ return helpers.t(a,b); }"); ctx.eval("var I18n = {}; I18n.t = function(a,b){ return helpers.t(a,b); }");
ctx.eval("var modules = {};")
decorate_context(ctx) decorate_context(ctx)
ctx_load(ctx, ctx_load(ctx,
@ -74,7 +92,7 @@ module PrettyText
"app/assets/javascripts/discourse/dialects/dialect.js", "app/assets/javascripts/discourse/dialects/dialect.js",
"app/assets/javascripts/discourse/lib/utilities.js", "app/assets/javascripts/discourse/lib/utilities.js",
"app/assets/javascripts/discourse/lib/html.js", "app/assets/javascripts/discourse/lib/html.js",
"app/assets/javascripts/discourse/lib/markdown.js" "app/assets/javascripts/discourse/lib/markdown.js",
) )
Dir["#{app_root}/app/assets/javascripts/discourse/dialects/**.js"].sort.each do |dialect| Dir["#{app_root}/app/assets/javascripts/discourse/dialects/**.js"].sort.each do |dialect|