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

View file

@ -1,21 +1,8 @@
/**
Build the BBCode for a Quote
@class BBCode
@namespace Discourse
@module Discourse
**/
Discourse.Quote = {
REGEXP: /\[quote=([^\]]*)\]((?:[\s\S](?!\[quote=[^\]]*\]))*?)\[\/quote\]/im,
/**
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 the BBCode quote around the selected text
build: function(post, contents) {
var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp;
if (!contents) contents = "";

View file

@ -1,6 +1,7 @@
import userSearch from 'discourse/lib/user-search';
import afterTransition from 'discourse/lib/after-transition';
import loadScript from 'discourse/lib/load-script';
import avatarTemplate from 'discourse/lib/avatar-template';
const ComposerView = Discourse.View.extend(Ember.Evented, {
_lastKeyTimeout: null,
@ -244,7 +245,10 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
if (posts) {
const quotedPost = posts.findProperty("post_number", postNumber);
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({
classNames: ['quote-button'],
classNameBindings: ['visible'],
@ -13,20 +5,12 @@ export default Discourse.View.extend({
isTouchInProgress: false,
/**
Determines whether the pop-up quote button should be visible.
The button is visible whenever there is something in the buffer
(ie. something has been selected)
@property visible
**/
visible: Em.computed.notEmpty('controller.buffer'),
/**
Renders the pop-up quote button.
@method render
**/
render: function(buffer) {
render(buffer) {
buffer.push(I18n.t("post.quote_reply"));
},
@ -38,15 +22,15 @@ export default Discourse.View.extend({
@method didInsertElement
**/
didInsertElement: function() {
var controller = this.get('controller'),
view = this;
didInsertElement() {
const controller = this.get('controller'),
view = this;
$(document)
.on("mousedown.quote-button", function(e) {
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
if ($target.hasClass('quote-button') ||
$target.closest('.create').length ||
@ -75,27 +59,17 @@ export default Discourse.View.extend({
});
},
/**
Selects the text
@method selectText
**/
selectText: function(target, controller) {
var $target = $(target);
selectText(target, controller) {
const $target = $(target);
// breaks if quoting has been disabled by the user
if (!Discourse.User.currentProp('enable_quoting')) return;
// 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
if (postId) controller.selectText(postId);
},
/**
Unbinds from global `mouseup, mousedown, selectionchange` events
@method willDestroyElement
**/
willDestroyElement: function() {
willDestroyElement() {
$(document)
.off("mousedown.quote-button")
.off("mouseup.quote-button")
@ -104,12 +78,7 @@ export default Discourse.View.extend({
.off("selectionchange");
},
/**
Quote the selected text when clicking on the quote button.
@method click
**/
click: function(e) {
click(e) {
e.stopPropagation();
return this.get('controller').quoteText(e);
}

View file

@ -20,12 +20,28 @@ module PrettyText
end
end
# function here are available to v8
# functions here are available to v8
def avatar_template(username)
return "" unless username
user = User.find_by(username_lower: username.downcase)
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
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 I18n = {}; I18n.t = function(a,b){ return helpers.t(a,b); }");
ctx.eval("var modules = {};")
decorate_context(ctx)
ctx_load(ctx,
@ -74,7 +92,7 @@ module PrettyText
"app/assets/javascripts/discourse/dialects/dialect.js",
"app/assets/javascripts/discourse/lib/utilities.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|