Clean up JS, add YUIDoc support, automatically create IIFE via asset pipeline

This commit is contained in:
Robin Ward 2013-02-22 15:41:12 -05:00
parent 0321643636
commit e461c84253
217 changed files with 11996 additions and 12131 deletions

View file

@ -1,5 +1,3 @@
(function() {
/**
This controller supports interface for creating custom CSS skins in Discourse.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminCustomizeController = Ember.Controller.extend({
Discourse.AdminCustomizeController = Ember.Controller.extend({
/**
Create a new customization style
@ -59,5 +57,3 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
This controller supports the default interface when you enter the admin section.
@ -8,9 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminDashboardController = Ember.Controller.extend({
Discourse.AdminDashboardController = Ember.Controller.extend({
loading: true,
versionCheck: null
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
This controller supports the interface for reviewing email logs.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminEmailLogsController = Ember.ArrayController.extend(Discourse.Presence, {
Discourse.AdminEmailLogsController = Ember.ArrayController.extend(Discourse.Presence, {
/**
Is the "send test email" button disabled?
@ -39,5 +37,3 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
This controller supports the interface for dealing with flags in the admin section.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminFlagsController = Ember.Controller.extend({
Discourse.AdminFlagsController = Ember.Controller.extend({
/**
Clear all flags on a post
@ -59,5 +57,3 @@
}).property('query')
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
This controller supports the interface for SiteSettings.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminSiteSettingsController = Ember.ArrayController.extend(Discourse.Presence, {
Discourse.AdminSiteSettingsController = Ember.ArrayController.extend(Discourse.Presence, {
filter: null,
onlyOverridden: false,
@ -70,5 +68,3 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
This controller supports the interface for listing users in the admin section.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminUsersListController = Ember.ArrayController.extend(Discourse.Presence, {
Discourse.AdminUsersListController = Ember.ArrayController.extend(Discourse.Presence, {
username: null,
query: null,
selectAll: false,
@ -107,5 +105,3 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Our data model for dealing with users from the admin section.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.AdminUser = Discourse.Model.extend({
Discourse.AdminUser = Discourse.Model.extend({
deleteAllPosts: function() {
this.set('can_delete_all_posts', false);
@ -186,5 +184,3 @@
return result;
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Our data model for representing an email log.
@ -8,9 +6,9 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.EmailLog = Discourse.Model.extend({});
Discourse.EmailLog = Discourse.Model.extend({});
window.Discourse.EmailLog.reopenClass({
Discourse.EmailLog.reopenClass({
create: function(attrs) {
if (attrs.user) {
@ -35,4 +33,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Our data model for interacting with flagged posts.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.FlaggedPost = Discourse.Post.extend({
Discourse.FlaggedPost = Discourse.Post.extend({
flaggers: (function() {
var r,
@ -97,7 +95,7 @@
});
window.Discourse.FlaggedPost.reopenClass({
Discourse.FlaggedPost.reopenClass({
findAll: function(filter) {
var result;
result = Em.A();
@ -121,4 +119,4 @@
}
});
}).call(this);

View file

@ -1,6 +1,3 @@
(function() {
var SiteCustomizations;
/**
Our data model for interacting with site customizations.
@ -9,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.SiteCustomization = Discourse.Model.extend({
Discourse.SiteCustomization = Discourse.Model.extend({
trackedProperties: ['enabled', 'name', 'stylesheet', 'header', 'override_default_style'],
init: function() {
@ -78,7 +75,7 @@
});
SiteCustomizations = Ember.ArrayProxy.extend({
var SiteCustomizations = Ember.ArrayProxy.extend({
selectedItemChanged: (function() {
var selected;
selected = this.get('selectedItem');
@ -113,5 +110,3 @@
return content;
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Our data model for interacting with site settings.
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.SiteSetting = Discourse.Model.extend({
Discourse.SiteSetting = Discourse.Model.extend({
// Whether a property is short.
short: (function() {
@ -48,7 +46,7 @@
}
});
window.Discourse.SiteSetting.reopenClass({
Discourse.SiteSetting.reopenClass({
findAll: function() {
var result;
result = Em.A();
@ -62,4 +60,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Our data model for determining whether there's a new version of Discourse
@ -8,7 +6,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.VersionCheck = Discourse.Model.extend({
Discourse.VersionCheck = Discourse.Model.extend({
upToDate: function() {
return this.get('latest_version') === this.get('installed_version');
}.property('latest_version', 'installed_version'),
@ -35,5 +33,3 @@
return promise;
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles routes related to customization
@ -18,4 +16,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles the default admin route
@ -30,4 +28,3 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles routes related to viewing email logs.
@ -18,4 +16,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles routes related to viewing active flags.
@ -22,4 +20,4 @@
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles routes related to viewing old flags.
@ -22,4 +20,4 @@
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Basic route for admin flags
@ -14,4 +12,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
The base admin route
@ -14,4 +12,4 @@
}
});
}).call(this);

View file

@ -1,10 +1,11 @@
(function() {
/**
Declare all the routes used in the admin section.
Builds the routes for the admin section
@method buildRoutes
@for Discourse.AdminRoute
**/
Discourse.buildRoutes(function() {
return this.resource('admin', { path: '/admin' }, function() {
this.resource('admin', { path: '/admin' }, function() {
this.route('dashboard', { path: '/' });
this.route('site_settings', { path: '/site_settings' });
@ -28,4 +29,4 @@
});
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles routes related to viewing and editing site settings.
@ -18,4 +16,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles routes related to users in the admin section.
@ -19,4 +17,4 @@
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles the route that lists active users.
@ -14,4 +12,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles the route that lists new users.
@ -14,4 +12,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles the route that lists pending users.
@ -14,4 +12,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
Handles the route that deals with listing users
@ -14,4 +12,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,4 @@
/*global ace:true */
(function() {
/**
A view that wraps the ACE editor (http://ace.ajax.org/)
@ -63,4 +62,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,4 @@
/*global Mousetrap:true */
(function() {
/**
A view to handle site customizations
@ -48,4 +47,4 @@
});
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
The default view in the admin section
@ -30,4 +28,4 @@
}.property('controller.versionCheck.critical_updates')
});
}).call(this);

View file

@ -31,6 +31,7 @@
//= require ./discourse/views/view
//= require ./discourse/components/debounce
//= require ./discourse/controllers/controller
//= require ./discourse/controllers/object_controller
//= require ./discourse/views/modal/modal_body_view
//= require ./discourse/models/model
//= require ./discourse/routes/discourse_route

View file

@ -2,10 +2,9 @@
/*global assetPath:true*/
/*global FastClick:true*/
(function() {
var csrf_token;
window.Discourse = Ember.Application.createWithMixins({
Discourse = Ember.Application.createWithMixins({
rootElement: '#main',
// Data we want to remember for a short period
@ -34,7 +33,7 @@
title += "" + (this.get('title')) + " - ";
}
title += Discourse.SiteSettings.title;
jQuery('title').text(title);
$('title').text(title);
if (!this.get('hasFocus') && this.get('notify')) {
title = "(*) " + title;
}
@ -134,7 +133,7 @@
bindDOMEvents: function() {
var $html, hasTouch,
_this = this;
$html = jQuery('html');
$html = $('html');
/* Add the discourse touch event */
hasTouch = false;
@ -160,17 +159,17 @@
$html.addClass('discourse-no-touch');
this.touch = false;
}
jQuery('#main').on('click.discourse', '[data-not-implemented=true]', function(e) {
$('#main').on('click.discourse', '[data-not-implemented=true]', function(e) {
e.preventDefault();
alert(Em.String.i18n('not_implemented'));
return false;
});
jQuery('#main').on('click.discourse', 'a', function(e) {
$('#main').on('click.discourse', 'a', function(e) {
var $currentTarget, href;
if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
return;
}
$currentTarget = jQuery(e.currentTarget);
$currentTarget = $(e.currentTarget);
href = $currentTarget.attr('href');
if (href === void 0) {
return;
@ -197,7 +196,7 @@
_this.routeTo(href);
return false;
});
return jQuery(window).focus(function() {
return $(window).focus(function() {
_this.set('hasFocus', true);
return _this.set('notify', false);
}).blur(function() {
@ -310,7 +309,7 @@
Discourse.insertProbes();
// subscribe to any site customizations that are loaded
jQuery('link.custom-css').each(function() {
$('link.custom-css').each(function() {
var id, split, stylesheet,
_this = this;
split = this.href.split("/");
@ -318,18 +317,18 @@
stylesheet = this;
return Discourse.MessageBus.subscribe("/file-change/" + id, function(data) {
var orig, sp;
if (!jQuery(stylesheet).data('orig')) {
jQuery(stylesheet).data('orig', stylesheet.href);
if (!$(stylesheet).data('orig')) {
$(stylesheet).data('orig', stylesheet.href);
}
orig = jQuery(stylesheet).data('orig');
orig = $(stylesheet).data('orig');
sp = orig.split(".css?");
stylesheet.href = sp[0] + ".css?" + data;
});
});
jQuery('header.custom').each(function() {
$('header.custom').each(function() {
var header;
header = jQuery(this);
return Discourse.MessageBus.subscribe("/header-change/" + (jQuery(this).data('key')), function(data) {
header = $(this);
return Discourse.MessageBus.subscribe("/header-change/" + ($(this).data('key')), function(data) {
return header.html(data);
});
});
@ -359,12 +358,12 @@
});
});
} else {
return jQuery('link').each(function() {
return $('link').each(function() {
if (this.href.match(me.name) && me.hash) {
if (!jQuery(this).data('orig')) {
jQuery(this).data('orig', this.href);
if (!$(this).data('orig')) {
$(this).data('orig', this.href);
}
this.href = jQuery(this).data('orig') + "&hash=" + me.hash;
this.href = $(this).data('orig') + "&hash=" + me.hash;
}
});
}
@ -373,12 +372,12 @@
}
});
window.Discourse.Router = Discourse.Router.reopen({
Discourse.Router = Discourse.Router.reopen({
location: 'discourse_location'
});
// since we have no jquery-rails these days, hook up csrf token
csrf_token = jQuery('meta[name=csrf-token]').attr('content');
csrf_token = $('meta[name=csrf-token]').attr('content');
jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
if (!options.crossDomain) {
@ -386,4 +385,4 @@
}
});
}).call(this);

View file

@ -1,15 +1,16 @@
(function() {
/**
This is a jQuery plugin to support autocompleting values in our text fields.
(function($) {
var template;
template = null;
@module $.fn.autocomplete
**/
$.fn.autocomplete = function(options) {
var addInputSelectedItem, autocompleteOptions, closeAutocomplete, completeEnd, completeStart, completeTerm, div, height;
var inputSelectedItems, isInput, markSelected, me, oldClose, renderAutocomplete, selectedOption, updateAutoComplete, vals;
var width, wrap, _this = this;
if (this.length === 0) {
return;
}
if (this.length === 0) return;
if (options && options.cancel && this.data("closeAutocomplete")) {
this.data("closeAutocomplete")();
return this;
@ -23,17 +24,17 @@
completeEnd = null;
me = this;
div = null;
/* input is handled differently
*/
// input is handled differently
isInput = this[0].tagName === "INPUT";
inputSelectedItems = [];
addInputSelectedItem = function(item) {
var d, prev, transformed;
if (options.transformComplete) {
transformed = options.transformComplete(item);
}
d = jQuery("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>");
d = $("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>");
prev = me.parent().find('.item:last');
if (prev.length === 0) {
me.parent().prepend(d);
@ -47,12 +48,13 @@
return d.find('a').click(function() {
closeAutocomplete();
inputSelectedItems.splice(jQuery.inArray(item), 1);
jQuery(this).parent().parent().remove();
$(this).parent().parent().remove();
if (options.onChangeItems) {
return options.onChangeItems(inputSelectedItems);
}
});
};
if (isInput) {
width = this.width();
height = this.height();
@ -76,12 +78,14 @@
return true;
});
}
markSelected = function() {
var links;
links = div.find('li a');
links.removeClass('selected');
return jQuery(links[selectedOption]).addClass('selected');
return $(links[selectedOption]).addClass('selected');
};
renderAutocomplete = function() {
var borderTop, mePos, pos, ul;
if (div) {
@ -90,7 +94,7 @@
if (autocompleteOptions.length === 0) {
return;
}
div = jQuery(options.template({
div = $(options.template({
options: autocompleteOptions
}));
ul = div.find('ul');
@ -125,6 +129,7 @@
left: (mePos.left + pos.left + 27) + 'px'
});
};
updateAutoComplete = function(r) {
if (completeStart === null) return;
@ -135,6 +140,7 @@
return renderAutocomplete();
}
};
closeAutocomplete = function() {
if (div) {
div.hide().remove();
@ -143,9 +149,8 @@
completeStart = null;
autocompleteOptions = null;
};
/* chain to allow multiples
*/
// chain to allow multiples
oldClose = me.data("closeAutocomplete");
me.data("closeAutocomplete", function() {
if (oldClose) {
@ -153,6 +158,7 @@
}
return closeAutocomplete();
});
completeTerm = function(term) {
var text;
if (term) {
@ -171,7 +177,8 @@
}
return closeAutocomplete();
};
jQuery(this).keypress(function(e) {
$(this).keypress(function(e) {
var caretPosition, prevChar, term;
if (!options.key) {
return;
@ -189,7 +196,8 @@
}
}
});
return jQuery(this).keydown(function(e) {
return $(this).keydown(function(e) {
var c, caretPosition, i, initial, next, nextIsGood, prev, prevIsGood, stopFound, term, total, userToComplete;
if (!options.key) {
completeStart = 0;
@ -307,7 +315,3 @@
}
});
};
return $.fn.autocomplete;
})(jQuery);
}).call(this);

View file

@ -1,44 +1,31 @@
/*global HANDLEBARS_TEMPLATES:true*/
(function() {
/**
Support for BBCode rendering
@class BBCode
@namespace Discourse
@module Discourse
**/
Discourse.BBCode = {
QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im,
/* Define our replacers
*/
QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im,
// Define our replacers
replacers: {
base: {
withoutArgs: {
"ol": function(_, content) {
return "<ol>" + content + "</ol>";
},
"li": function(_, content) {
return "<li>" + content + "</li>";
},
"ul": function(_, content) {
return "<ul>" + content + "</ul>";
},
"code": function(_, content) {
return "<pre>" + content + "</pre>";
},
"url": function(_, url) {
return "<a href=\"" + url + "\">" + url + "</a>";
},
"email": function(_, address) {
return "<a href=\"mailto:" + address + "\">" + address + "</a>";
},
"img": function(_, src) {
return "<img src=\"" + src + "\">";
}
"ol": function(_, content) { return "<ol>" + content + "</ol>"; },
"li": function(_, content) { return "<li>" + content + "</li>"; },
"ul": function(_, content) { return "<ul>" + content + "</ul>"; },
"code": function(_, content) { return "<pre>" + content + "</pre>"; },
"url": function(_, url) { return "<a href=\"" + url + "\">" + url + "</a>"; },
"email": function(_, address) { return "<a href=\"mailto:" + address + "\">" + address + "</a>"; },
"img": function(_, src) { return "<img src=\"" + src + "\">"; }
},
withArgs: {
"url": function(_, href, title) {
return "<a href=\"" + href + "\">" + title + "</a>";
},
"email": function(_, address, title) {
return "<a href=\"mailto:" + address + "\">" + title + "</a>";
},
"url": function(_, href, title) { return "<a href=\"" + href + "\">" + title + "</a>"; },
"email": function(_, address, title) { return "<a href=\"mailto:" + address + "\">" + title + "</a>"; },
"color": function(_, color, content) {
if (!/^(\#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?)|(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)$/.test(color)) {
return content;
@ -47,26 +34,15 @@
}
}
},
/* For HTML emails
*/
// For HTML emails
email: {
withoutArgs: {
"b": function(_, content) {
return "<b>" + content + "</b>";
},
"i": function(_, content) {
return "<i>" + content + "</i>";
},
"u": function(_, content) {
return "<u>" + content + "</u>";
},
"s": function(_, content) {
return "<s>" + content + "</s>";
},
"spoiler": function(_, content) {
return "<span style='background-color: #000'>" + content + "</span>";
}
"b": function(_, content) { return "<b>" + content + "</b>"; },
"i": function(_, content) { return "<i>" + content + "</i>"; },
"u": function(_, content) { return "<u>" + content + "</u>"; },
"s": function(_, content) { return "<s>" + content + "</s>"; },
"spoiler": function(_, content) { return "<span style='background-color: #000'>" + content + "</span>"; }
},
withArgs: {
"size": function(_, size, content) {
@ -74,25 +50,15 @@
}
}
},
/* For sane environments that support CSS
*/
// For sane environments that support CSS
"default": {
withoutArgs: {
"b": function(_, content) {
return "<span class='bbcode-b'>" + content + "</span>";
},
"i": function(_, content) {
return "<span class='bbcode-i'>" + content + "</span>";
},
"u": function(_, content) {
return "<span class='bbcode-u'>" + content + "</span>";
},
"s": function(_, content) {
return "<span class='bbcode-s'>" + content + "</span>";
},
"spoiler": function(_, content) {
return "<span class=\"spoiler\">" + content + "</span>";
"b": function(_, content) { return "<span class='bbcode-b'>" + content + "</span>"; },
"i": function(_, content) { return "<span class='bbcode-i'>" + content + "</span>"; },
"u": function(_, content) { return "<span class='bbcode-u'>" + content + "</span>"; },
"s": function(_, content) { return "<span class='bbcode-s'>" + content + "</span>"; },
"spoiler": function(_, content) { return "<span class=\"spoiler\">" + content + "</span>";
}
},
withArgs: {
@ -103,7 +69,7 @@
}
},
/* Apply a particular set of replacers */
// Apply a particular set of replacers
apply: function(text, environment) {
var replacer;
replacer = Discourse.BBCode.parsedReplacers()[environment];
@ -116,9 +82,8 @@
parsedReplacers: function() {
var result;
if (this.parsed) {
return this.parsed;
}
if (this.parsed) return this.parsed;
result = {};
Object.keys(Discourse.BBCode.replacers, function(name, rules) {
var parsed;
@ -207,6 +172,14 @@
}
return text;
},
/**
Format a text string using BBCode
@method format
@param {String} text The text we want to format
@param {Object} opts Rendering options
**/
format: function(text, opts) {
var environment;
if (opts && opts.environment) environment = opts.environment;
@ -218,5 +191,3 @@
return text;
}
};
}).call(this);

View file

@ -1,14 +1,4 @@
/* caret position in textarea ... very hacky ... sorry
*/
(function() {
(function($) {
/* http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
*/
// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
var clone, getCaret;
getCaret = function(el) {
var r, rc, re;
@ -26,14 +16,22 @@
}
return 0;
};
clone = null;
/**
This is a jQuery plugin to retrieve the caret position in a textarea
@module $.fn.caretPosition
**/
$.fn.caretPosition = function(options) {
var after, before, getStyles, guard, html, important, insertSpaceAfterBefore, letter, makeCursor, p, pPos, pos, span, styles, textarea, val;
if (clone) {
clone.remove();
}
span = jQuery("#pos span");
textarea = jQuery(this);
span = $("#pos span");
textarea = $(this);
getStyles = function(el, prop) {
if (el.currentStyle) {
return el.currentStyle;
@ -41,14 +39,17 @@
return document.defaultView.getComputedStyle(el, "");
}
};
styles = getStyles(textarea[0]);
clone = jQuery("<div><p></p></div>").appendTo("body");
clone = $("<div><p></p></div>").appendTo("body");
p = clone.find("p");
clone.width(textarea.width());
clone.height(textarea.height());
important = function(prop) {
return styles.getPropertyValue(prop);
};
clone.css({
border: "1px solid black",
padding: important("padding"),
@ -59,6 +60,7 @@
position: "absolute",
left: "-7000px"
});
p.css({
margin: 0,
padding: 0,
@ -68,6 +70,7 @@
"font-size": important("font-size"),
"line-height": important("line-height")
});
before = void 0;
after = void 0;
pos = options && (options.pos || options.pos === 0) ? options.pos : getCaret(textarea[0]);
@ -78,12 +81,12 @@
before = pos - 1;
after = pos;
insertSpaceAfterBefore = false;
/* if before and after are \n insert a space
*/
// if before and after are \n insert a space
if (val[before] === "\n" && val[after] === "\n") {
insertSpaceAfterBefore = true;
}
guard = function(v) {
var buf;
buf = v.replace(/</g, "&lt;");
@ -91,14 +94,14 @@
buf = buf.replace(/[ ]/g, "&#x200b;&nbsp;&#x200b;");
return buf.replace(/\n/g, "<br />");
};
makeCursor = function(pos, klass, color) {
var l;
l = val.substring(pos, pos + 1);
if (l === "\n") {
return "<br>";
}
if (l === "\n") return "<br>";
return "<span class='" + klass + "' style='background-color:" + color + "; margin:0; padding: 0'>" + guard(l) + "</span>";
};
html = "";
if (before >= 0) {
html += guard(val.substring(0, pos - 1)) + makeCursor(before, "before", "#d0ffff");
@ -106,12 +109,14 @@
html += makeCursor(0, "post-before", "#d0ffff");
}
}
if (after >= 0) {
html += makeCursor(after, "after", "#ffd0ff");
if (after - 1 < val.length) {
html += guard(val.substring(after + 1));
}
}
p.html(html);
clone.scrollTop(textarea.scrollTop());
letter = p.find("span:first");
@ -119,17 +124,11 @@
if (letter.hasClass("before")) {
pos.left = pos.left + letter.width();
}
pPos = p.offset();
return {
/*clone.hide().remove()
*/
left: pos.left - pPos.left,
top: (pos.top - pPos.top) - clone.scrollTop()
};
};
return $.fn.caretPosition;
})(jQuery);
}).call(this);

View file

@ -1,51 +1,49 @@
/**
Used for tracking when the user clicks on a link
/* We use this object to keep track of click counts.
*/
@class ClickTrack
@namespace Discourse
@module Discourse
**/
Discourse.ClickTrack = {
/**
Track a click on a link
(function() {
window.Discourse.ClickTrack = {
/* Pass the event of the click here and we'll do the magic!
*/
@method trackClick
@param {jQuery.Event} e The click event that occurred
**/
trackClick: function(e) {
var $a, $article, $badge, count, destination, href, ownLink, postId, topicId, trackingUrl, userId;
$a = jQuery(e.currentTarget);
$a = $(e.currentTarget);
if ($a.hasClass('lightbox')) {
return;
}
e.preventDefault();
/* We don't track clicks on quote back buttons
*/
if ($a.hasClass('back') || $a.hasClass('quote-other-topic')) {
return true;
}
/* Remove the href, put it as a data attribute
*/
// We don't track clicks on quote back buttons
if ($a.hasClass('back') || $a.hasClass('quote-other-topic')) return true;
// Remove the href, put it as a data attribute
if (!$a.data('href')) {
$a.addClass('no-href');
$a.data('href', $a.attr('href'));
$a.attr('href', null);
/* Don't route to this URL
*/
// Don't route to this URL
$a.data('auto-route', true);
}
href = $a.data('href');
$article = $a.closest('article');
postId = $article.data('post-id');
topicId = jQuery('#topic').data('topic-id');
topicId = $('#topic').data('topic-id');
userId = $a.data('user-id');
if (!userId) {
userId = $article.data('user-id');
}
ownLink = userId && (userId === Discourse.get('currentUser.id'));
/* Build a Redirect URL
*/
// Build a Redirect URL
trackingUrl = "/clicks/track?url=" + encodeURIComponent(href);
if (postId && (!$a.data('ignore-post-id'))) {
trackingUrl += "&post_id=" + encodeURI(postId);
@ -53,27 +51,24 @@
if (topicId) {
trackingUrl += "&topic_id=" + encodeURI(topicId);
}
/* Update badge clicks unless it's our own
*/
// Update badge clicks unless it's our own
if (!ownLink) {
$badge = jQuery('span.badge', $a);
$badge = $('span.badge', $a);
if ($badge.length === 1) {
count = parseInt($badge.html(), 10);
$badge.html(count + 1);
}
}
/* If they right clicked, change the destination href
*/
// If they right clicked, change the destination href
if (e.which === 3) {
destination = Discourse.SiteSettings.track_external_right_clicks ? trackingUrl : href;
$a.attr('href', destination);
return true;
}
/* if they want to open in a new tab, do an AJAX request
*/
// if they want to open in a new tab, do an AJAX request
if (e.metaKey || e.ctrlKey || e.which === 2) {
jQuery.get("/clicks/track", {
url: href,
@ -84,9 +79,8 @@
window.open(href, '_blank');
return false;
}
/* If we're on the same site, use the router and track via AJAX
*/
// If we're on the same site, use the router and track via AJAX
if (href.indexOf(window.location.origin) === 0) {
jQuery.get("/clicks/track", {
url: href,
@ -97,12 +91,11 @@
Discourse.routeTo(href);
return false;
}
/* Otherwise, use a custom URL with a redirect
*/
// Otherwise, use a custom URL with a redirect
window.location = trackingUrl;
return false;
}
};
}).call(this);

View file

@ -1,4 +1,14 @@
window.Discourse.debounce = function(func, wait, trickle) {
/**
Debounce a Javascript function. This means if it's called many times in a time limit it
should only be executed once.
@method debounce
@module Discourse
@param {function} func The function to debounce
@param {Numbers} wait how long to wait
@param {Boolean} trickle
**/
Discourse.debounce = function(func, wait, trickle) {
var timeout;
timeout = null;
@ -12,7 +22,7 @@ window.Discourse.debounce = function(func, wait, trickle) {
};
if (timeout && trickle) {
/* already queued, let it through */
// already queued, let it through
return;
}

View file

@ -1,10 +0,0 @@
(function() {
Discourse.TextField = Ember.TextField.extend({
attributeBindings: ['autocorrect', 'autocapitalize'],
placeholder: (function() {
return Em.String.i18n(this.get('placeholderKey'));
}).property('placeholderKey')
});
}).call(this);

View file

@ -1,11 +1,11 @@
/**
This is a jQuery plugin to support resizing text areas.
/*based off text area resizer by Ryan O'Dell : http://plugins.jquery.com/misc/textarea.js
*/
Originally based off text area resizer by Ryan O'Dell : http://plugins.jquery.com/misc/textarea.js
@module $.fn.DivResizer
**/
(function() {
(function($) {
var div, endDrag, grip, lastMousePos, min, mousePosition, originalDivHeight, originalPos, performDrag, startDrag, wrappedEndDrag, wrappedPerformDrag;
div = void 0;
originalPos = void 0;
@ -15,8 +15,9 @@
grip = void 0;
wrappedEndDrag = void 0;
wrappedPerformDrag = void 0;
startDrag = function(e, opts) {
div = jQuery(e.data.el);
div = $(e.data.el);
div.addClass('clear-transitions');
div.blur();
lastMousePos = mousePosition(e).y;
@ -32,15 +33,16 @@
return endDrag(e, opts);
};
})();
jQuery(document).mousemove(wrappedPerformDrag).mouseup(wrappedEndDrag);
$(document).mousemove(wrappedPerformDrag).mouseup(wrappedEndDrag);
return false;
};
performDrag = function(e, opts) {
var size, sizePx, thisMousePos;
thisMousePos = mousePosition(e).y;
size = originalDivHeight + (originalPos - thisMousePos);
lastMousePos = thisMousePos;
size = Math.min(size, jQuery(window).height());
size = Math.min(size, $(window).height());
size = Math.max(min, size);
sizePx = size + "px";
if (typeof opts.onDrag === "function") {
@ -52,8 +54,9 @@
}
return false;
};
endDrag = function(e, opts) {
jQuery(document).unbind("mousemove", wrappedPerformDrag).unbind("mouseup", wrappedEndDrag);
$(document).unbind("mousemove", wrappedPerformDrag).unbind("mouseup", wrappedEndDrag);
div.removeClass('clear-transitions');
div.focus();
if (typeof opts.resize === "function") {
@ -61,19 +64,19 @@
}
div = null;
};
mousePosition = function(e) {
return {
x: e.clientX + document.documentElement.scrollLeft,
y: e.clientY + document.documentElement.scrollTop
};
};
$.fn.DivResizer = function(opts) {
return this.each(function() {
var grippie, start, staticOffset;
div = jQuery(this);
if (div.hasClass("processed")) {
return;
}
div = $(this);
if (div.hasClass("processed")) return;
div.addClass("processed");
staticOffset = null;
start = function() {
@ -86,7 +89,3 @@
}, start());
});
};
return $.fn.DivResizer;
})(jQuery);
}).call(this);

View file

@ -1,129 +1,97 @@
/**
Track visible elemnts on the screen.
/* Track visible elements on the screen
*/
You can register for triggers on:
`focusChanged` the top element we're focusing on
/* You can register for triggers on:
*/
`seenElement` if we've seen the element
/* focusChanged: -> the top element we're focusing on
*/
/* seenElement: -> if we've seen the element
*/
(function() {
Discourse.Eyeline = (function() {
function Eyeline(selector) {
@class Eyeline
@namespace Discourse
@module Discourse
@uses RSVP.EventTarget
**/
Discourse.Eyeline = function Eyeline(selector) {
this.selector = selector;
}
/* Call this whenever we want to consider what is currently being seen by the browser
*/
/**
Call this whenever you want to consider what is being seen by the browser
Eyeline.prototype.update = function() {
@method update
**/
Discourse.Eyeline.prototype.update = function() {
var $elements, $results, atBottom, bottomOffset, docViewBottom, docViewTop, documentHeight, foundElement, windowHeight,
_this = this;
docViewTop = jQuery(window).scrollTop();
windowHeight = jQuery(window).height();
docViewTop = $(window).scrollTop();
windowHeight = $(window).height();
docViewBottom = docViewTop + windowHeight;
documentHeight = jQuery(document).height();
$elements = jQuery(this.selector);
documentHeight = $(document).height();
$elements = $(this.selector);
atBottom = false;
if (bottomOffset = $elements.last().offset()) {
atBottom = (bottomOffset.top <= docViewBottom) && (bottomOffset.top >= docViewTop);
}
/* Whether we've seen any elements in this search
*/
// Whether we've seen any elements in this search
foundElement = false;
$results = jQuery(this.selector);
$results = $(this.selector);
return $results.each(function(i, elem) {
var $elem, elemBottom, elemTop, markSeen;
$elem = jQuery(elem);
$elem = $(elem);
elemTop = $elem.offset().top;
elemBottom = elemTop + $elem.height();
markSeen = false;
/* It's seen if...
*/
// It's seen if...
/* ...the element is vertically within the top and botom
*/
// ...the element is vertically within the top and botom
if ((elemTop <= docViewBottom) && (elemTop >= docViewTop)) markSeen = true;
if ((elemTop <= docViewBottom) && (elemTop >= docViewTop)) {
markSeen = true;
}
/* ...the element top is above the top and the bottom is below the bottom (large elements)
*/
// ...the element top is above the top and the bottom is below the bottom (large elements)
if ((elemTop <= docViewTop) && (elemBottom >= docViewBottom)) markSeen = true;
if ((elemTop <= docViewTop) && (elemBottom >= docViewBottom)) {
markSeen = true;
}
/* ...we're at the bottom and the bottom of the element is visible (large bottom elements)
*/
// ...we're at the bottom and the bottom of the element is visible (large bottom elements)
if (atBottom && (elemBottom >= docViewTop)) markSeen = true;
if (atBottom && (elemBottom >= docViewTop)) {
markSeen = true;
}
if (!markSeen) {
return true;
}
/* If you hit the bottom we mark all the elements as seen. Otherwise, just the first one
*/
if (!markSeen) return true;
// If you hit the bottom we mark all the elements as seen. Otherwise, just the first one
if (!atBottom) {
_this.trigger('saw', {
detail: $elem
});
if (i === 0) {
_this.trigger('sawTop', {
detail: $elem
});
_this.trigger('sawTop', { detail: $elem });
}
return false;
}
if (i === 0) {
_this.trigger('sawTop', {
detail: $elem
});
_this.trigger('sawTop', { detail: $elem });
}
if (i === ($results.length - 1)) {
return _this.trigger('sawBottom', {
detail: $elem
});
return _this.trigger('sawBottom', { detail: $elem });
}
});
};
/* Call this when we know aren't loading any more elements. Mark the rest
*/
/**
Call this when we know aren't loading any more elements. Mark the rest as seen
/* as seen
*/
Eyeline.prototype.flushRest = function() {
@method flushRest
**/
Discourse.Eyeline.prototype.flushRest = function() {
var _this = this;
return jQuery(this.selector).each(function(i, elem) {
return $(this.selector).each(function(i, elem) {
var $elem;
$elem = jQuery(elem);
return _this.trigger('saw', {
detail: $elem
});
$elem = $(elem);
return _this.trigger('saw', { detail: $elem });
});
};
return Eyeline;
})();
RSVP.EventTarget.mixin(Discourse.Eyeline.prototype);
}).call(this);

View file

@ -1,19 +1,19 @@
/**
A simple key value store that uses LocalStorage
/* key value store
*/
@class KeyValueStore
@namespace Discourse
@module Discourse
**/
Discourse.KeyValueStore = {
initialized: false,
context: "",
(function() {
window.Discourse.KeyValueStore = (function() {
var context, initialized;
initialized = false;
context = "";
return {
init: function(ctx, messageBus) {
initialized = true;
context = ctx;
},
abandonLocal: function() {
var i, k;
if (!(localStorage && initialized)) {
@ -29,22 +29,23 @@
}
return true;
},
remove: function(key) {
return localStorage.removeItem(context + key);
},
set: function(opts) {
if (!(localStorage && initialized)) {
return false;
}
localStorage[context + opts.key] = opts.value;
},
get: function(key) {
if (!localStorage) {
return null;
}
return localStorage[context + key];
}
};
})();
}
}).call(this);

View file

@ -1,23 +1,19 @@
/**
Helper object for lightboxes.
/* Helper object for light boxes. Uses highlight.js which is loaded
*/
/* on demand.
*/
(function() {
window.Discourse.Lightbox = {
@class Lightbox
@namespace Discourse
@module Discourse
**/
Discourse.Lightbox = {
apply: function($elem) {
var _this = this;
return jQuery('a.lightbox', $elem).each(function(i, e) {
return $('a.lightbox', $elem).each(function(i, e) {
return $LAB.script("/javascripts/jquery.colorbox-min.js").wait(function() {
return jQuery(e).colorbox();
return $(e).colorbox();
});
});
}
};
}
}).call(this);

View file

@ -0,0 +1,59 @@
/**
Helps us determine whether someone has been mentioned by looking up their username.
@class Mention
@namespace Discourse
@module Discourse
**/
Discourse.Mention = (function() {
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(name, valid) {
localCache[name] = valid;
};
lookupCache = function(name) {
return localCache[name];
};
lookup = function(name, callback) {
var cached;
cached = lookupCache(name);
if (cached === true || cached === false) {
callback(cached);
return false;
} else {
jQuery.get("/users/is_local_username", {
username: name
}, function(r) {
cache(name, r.valid);
return callback(r.valid);
});
return true;
}
};
load = function(e) {
var $elem, loading, username;
$elem = $(e);
if ($elem.data('mention-tested')) {
return;
}
username = $elem.text();
username = username.substr(1);
loading = lookup(username, function(valid) {
if (valid) {
return $elem.replaceWith("<a href='/users/" + (username.toLowerCase()) + "' class='mention'>@" + username + "</a>");
} else {
return $elem.removeClass('mention-loading').addClass('mention-tested');
}
});
if (loading) {
return $elem.addClass('mention-loading');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();

View file

@ -1,11 +1,16 @@
/*jshint bitwise: false*/
(function() {
window.Discourse.MessageBus = (function() {
/* http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
*/
/**
Message Bus functionality.
@class MessageBus
@namespace Discourse
@module Discourse
**/
Discourse.MessageBus = (function() {
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
var callbacks, clientId, failCount, interval, isHidden, queue, responseCallbacks, uniqueId;
uniqueId = function() {
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r, v;
@ -14,12 +19,14 @@
return v.toString(16);
});
};
clientId = uniqueId();
responseCallbacks = {};
callbacks = [];
queue = [];
interval = null;
failCount = 0;
isHidden = function() {
if (document.hidden !== void 0) {
return document.hidden;
@ -30,29 +37,26 @@
} else if (document.mozHidden !== void 0) {
return document.mozHidden;
} else {
/* fallback to problamatic window.focus
*/
// fallback to problamatic window.focus
return !Discourse.get('hasFocus');
}
};
return {
enableLongPolling: true,
callbackInterval: 60000,
maxPollInterval: 3 * 60 * 1000,
callbacks: callbacks,
clientId: clientId,
/*TODO
*/
stop: false,
/* Start polling
*/
// Start polling
start: function(opts) {
var poll,
_this = this;
if (!opts) opts = {};
poll = function() {
var data, gotData;
if (callbacks.length === 0) {
@ -117,9 +121,8 @@
};
poll();
},
/* Subscribe to a channel
*/
// Subscribe to a channel
subscribe: function(channel, func, lastId) {
callbacks.push({
channel: channel,
@ -130,13 +133,10 @@
return this.longPoll.abort();
}
},
/* Unsubscribe from a channel
*/
// Unsubscribe from a channel
unsubscribe: function(channel) {
/* TODO proper globbing
*/
// TODO proper globbing
var glob;
if (channel.endsWith("*")) {
channel = channel.substr(0, channel.length - 1);
@ -155,5 +155,3 @@
}
};
})();
}).call(this);

View file

@ -0,0 +1,89 @@
/**
A helper for looking up oneboxes and displaying them
For now it only stores in a var, in future we can change it so it uses localStorage.
@class Notification
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Onebox = (function() {
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(url, contents) {
localCache[url] = contents;
return null;
};
lookupCache = function(url) {
var cached;
cached = localCache[url];
if (cached && cached.then) {
return null;
} else {
return cached;
}
};
lookup = function(url, refresh, callback) {
var cached;
cached = localCache[url];
if (refresh && cached && !cached.then) {
cached = null;
}
if (cached) {
if (cached.then) {
cached.then(callback(lookupCache(url)));
} else {
callback(cached);
}
return false;
} else {
cache(url, jQuery.get("/onebox", {
url: url,
refresh: refresh
}, function(html) {
cache(url, html);
return callback(html);
}));
return true;
}
};
load = function(e, refresh) {
var $elem, loading, url;
if (!refresh) refresh = false;
url = e.href;
$elem = $(e);
if ($elem.data('onebox-loaded')) {
return;
}
loading = lookup(url, refresh, function(html) {
$elem.removeClass('loading-onebox');
$elem.data('onebox-loaded');
if (!html) {
return;
}
if (html.trim().length === 0) {
return;
}
return $elem.replaceWith(html);
});
if (loading) {
return $elem.addClass('loading-onebox');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();

View file

@ -1,38 +0,0 @@
/*global Markdown:true*/
(function() {
window.Discourse.PagedownEditor = Ember.ContainerView.extend({
elementId: 'pagedown-editor',
init: function() {
this._super();
/* Add a button bar
*/
this.pushObject(Em.View.create({
elementId: 'wmd-button-bar'
}));
this.pushObject(Em.TextArea.create({
valueBinding: 'parentView.value',
elementId: 'wmd-input'
}));
return this.pushObject(Em.View.createWithMixins(Discourse.Presence, {
elementId: 'wmd-preview',
classNameBindings: [':preview', 'hidden'],
hidden: (function() {
return this.blank('parentView.value');
}).property('parentView.value')
}));
},
didInsertElement: function() {
var $wmdInput;
$wmdInput = jQuery('#wmd-input');
$wmdInput.data('init', true);
this.editor = new Markdown.Editor(Discourse.Utilities.markdownConverter({
sanitize: true
}));
return this.editor.run();
}
});
}).call(this);

View file

@ -5,27 +5,26 @@
*
* Examples:
*
someFunction = window.probes.measure(someFunction, {
name: "somename" // or function(args) { return "name"; },
before: function(data, owner, args) {
// if owner is true, we are not in a recursive function call.
//
// data contains the bucker of data already measuer
// data.count >= 0
// data.time is the total time measured till now
//
// arguments contains the original arguments sent to the function
},
after: function(data, owner, args) {
// same format as before
}
});
// minimal
someFunction = window.probes.measure(someFunction, "someFunction");
*
* someFunction = window.probes.measure(someFunction, {
* name: "somename" // or function(args) { return "name"; },
* before: function(data, owner, args) {
* // if owner is true, we are not in a recursive function call.
* //
* // data contains the bucker of data already measuer
* // data.count >= 0
* // data.time is the total time measured till now
* //
* // arguments contains the original arguments sent to the function
* },
* after: function(data, owner, args) {
* // same format as before
* }
* });
*
*
* // minimal
* someFunction = window.probes.measure(someFunction, "someFunction");
*
*
* */

View file

@ -1,92 +0,0 @@
// Sam: I wrote this but it is totally unsafe so I ported Google Cajole
// Thing is Cajole is old and complex (albeit super duper fast)
//
// I would like this ported to: https://github.com/tautologistics/node-htmlparser , perf tested
// and move off cajole
//
// See also: http://stackoverflow.com/questions/14971083/is-jquerys-safe-from-xss
//
// (function( $ ) {
//
// var elements = ["a", "abbr", "aside", "b", "bdo", "blockquote", "br",
// "caption", "cite", "code", "col", "colgroup", "dd", "div",
// "del", "dfn", "dl", "dt", "em", "hr", "figcaption", "figure",
// "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "i", "img", "ins",
// "kbd", "li", "mark", "ol", "p", "pre", "q", "rp", "rt", "ruby",
// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
// "table", "tbody", "td", "tfoot", "th", "thead", "time", "tr", "u",
// "ul", "var", "wbr"];
//
// var attributes = {
// 'all' : ['dir', 'lang', 'title', 'class'],
// 'aside' : ['data-post', 'data-full', 'data-topic'],
// 'a' : ['href'],
// 'blockquote' : ['cite'],
// 'col' : ['span', 'width'],
// 'colgroup' : ['span', 'width'],
// 'del' : ['cite', 'datetime'],
// 'img' : ['align', 'alt', 'height', 'src', 'width'],
// 'ins' : ['cite', 'datetime'],
// 'ol' : ['start', 'reversed', 'type'],
// 'q' : ['cite'],
// 'span' : ['style'],
// 'table' : ['summary', 'width', 'style', 'cellpadding', 'cellspacing'],
// 'td' : ['abbr', 'axis', 'colspan', 'rowspan', 'width', 'style'],
// 'th' : ['abbr', 'axis', 'colspan', 'rowspan', 'scope', 'width', 'style'],
// 'time' : ['datetime', 'pubdate'],
// 'ul' : ['type']
//
// };
//
// var elementMap = {};
// jQuery.each(elements, function(idx,e){
// elementMap[e] = true;
// });
//
// var scrubAttributes = function(e){
// jQuery.each(e.attributes, function(idx, attr){
//
// if(jQuery.inArray(attr.name, attributes.all) === -1 &&
// jQuery.inArray(attr.name, attributes[e.tagName.toLowerCase()]) === -1) {
// e.removeAttribute(attr.name);
// }
// });
// return(e);
// };
//
// var scrubNode = function(e){
// if (!e.tagName) { return(e); }
// if(elementMap[e.tagName.toLowerCase()]){
// return scrubAttributes(e);
// }
// else
// {
// return null;
// }
// };
//
// var scrubTree = function(e) {
// if (!e) { return; }
//
// var clean = scrubNode(e);
// if(!clean){
// e.parentNode.removeChild(e);
// }
// else {
// jQuery.each(clean.children, function(idx, inner){
// scrubTree(inner);
// });
// }
// };
//
// $.fn.sanitize = function() {
// clean = this.filter(function(){
// return scrubNode(this);
// }).each(function(){
// scrubTree(this);
// });
//
// return clean;
// };
// })( jQuery );

View file

@ -1,52 +1,47 @@
/**
We use this class to track how long posts in a topic are on the screen.
/* We use this class to track how long posts in a topic are on the screen.
*/
/* This could be a potentially awesome metric to keep track of.
*/
(function() {
window.Discourse.ScreenTrack = Ember.Object.extend({
/* Don't send events if we haven't scrolled in a long time
*/
@class ScreenTrack
@extends Ember.Object
@namespace Discourse
@module Discourse
**/
Discourse.ScreenTrack = Ember.Object.extend({
// Don't send events if we haven't scrolled in a long time
PAUSE_UNLESS_SCROLLED: 1000 * 60 * 3,
/* After 6 minutes stop tracking read position on post
*/
// After 6 minutes stop tracking read position on post
MAX_TRACKING_TIME: 1000 * 60 * 6,
totalTimings: {},
/* Elements to track
*/
totalTimings: {},
// Elements to track
timings: {},
topicTime: 0,
cancelled: false,
track: function(elementId, postNumber) {
this.timings["#" + elementId] = {
time: 0,
postNumber: postNumber
};
},
guessedSeen: function(postNumber) {
if (postNumber > (this.highestSeen || 0)) {
this.highestSeen = postNumber;
}
},
/* Reset our timers
*/
// Reset our timers
reset: function() {
this.lastTick = new Date().getTime();
this.lastFlush = 0;
this.cancelled = false;
},
/* Start tracking
*/
// Start tracking
start: function() {
var _this = this;
this.reset();
@ -55,9 +50,8 @@
return _this.tick();
}, 1000);
},
/* Cancel and eject any tracking we have buffered
*/
// Cancel and eject any tracking we have buffered
cancel: function() {
this.cancelled = true;
this.timings = {};
@ -65,26 +59,25 @@
clearInterval(this.interval);
this.interval = null;
},
/* Stop tracking and flush buffered read records
*/
// Stop tracking and flush buffered read records
stop: function() {
clearInterval(this.interval);
this.interval = null;
return this.flush();
},
scrolled: function() {
this.lastScrolled = new Date().getTime();
},
flush: function() {
var highestSeenByTopic, newTimings, topicId,
_this = this;
if (this.cancelled) {
return;
}
/* We don't log anything unless we're logged in
*/
// We don't log anything unless we're logged in
if (!Discourse.get('currentUser')) {
return;
}
@ -122,10 +115,9 @@
}
this.lastFlush = 0;
},
tick: function() {
/* If the user hasn't scrolled the browser in a long time, stop tracking time read
*/
tick: function() {
// If the user hasn't scrolled the browser in a long time, stop tracking time read
var diff, docViewBottom, docViewTop, sinceScrolled,
_this = this;
sinceScrolled = new Date().getTime() - this.lastScrolled;
@ -139,24 +131,21 @@
if (this.lastFlush > (Discourse.SiteSettings.flush_timings_secs * 1000)) {
this.flush();
}
/* Don't track timings if we're not in focus
*/
if (!Discourse.get("hasFocus")) {
return;
}
// Don't track timings if we're not in focus
if (!Discourse.get("hasFocus")) return;
this.topicTime += diff;
docViewTop = jQuery(window).scrollTop() + jQuery('header').height();
docViewBottom = docViewTop + jQuery(window).height();
docViewTop = $(window).scrollTop() + $('header').height();
docViewBottom = docViewTop + $(window).height();
return Object.keys(this.timings, function(id) {
var $element, elemBottom, elemTop, timing;
$element = jQuery(id);
$element = $(id);
if ($element.length === 1) {
elemTop = $element.offset().top;
elemBottom = elemTop + $element.height();
/* If part of the element is on the screen, increase the counter
*/
// If part of the element is on the screen, increase the counter
if (((docViewTop <= elemTop && elemTop <= docViewBottom)) || ((docViewTop <= elemBottom && elemBottom <= docViewBottom))) {
timing = _this.timings[id];
timing.time = timing.time + diff;
@ -164,6 +153,7 @@
}
});
}
});
}).call(this);

View file

@ -1,13 +1,23 @@
/*global hljs:true */
/* Helper object for syntax highlighting. Uses highlight.js which is loaded
on demand. */
(function() {
/**
Helper object for syntax highlighting. Uses highlight.js which is loaded on demand.
window.Discourse.SyntaxHighlighting = {
@class SyntaxHighlighting
@namespace Discourse
@module Discourse
**/
Discourse.SyntaxHighlighting = {
/**
Apply syntax highlighting to a jQuery element
@method apply
@param {jQuery.selector} $elem The element we want to apply our highlighting to
**/
apply: function($elem) {
var _this = this;
return jQuery('pre code[class]', $elem).each(function(i, e) {
return $('pre code[class]', $elem).each(function(i, e) {
return $LAB.script("/javascripts/highlight-handlebars.pack.js").wait(function() {
return hljs.highlightBlock(e);
});
@ -15,4 +25,4 @@
}
};
}).call(this);

View file

@ -1,17 +1,15 @@
/**
CSS transitions are a PITA, often we need to queue some js after a transition, this helper ensures
it happens after the transition.
/* CSS transitions are a PITA, often we need to queue some js after a transition, this helper ensures
*/
SO: http://stackoverflow.com/questions/9943435/css3-animation-end-techniques
/* it happens after the transition
*/
@class TransitionHelper
@namespace Discourse
@module Discourse
**/
/* SO: http://stackoverflow.com/questions/9943435/css3-animation-end-techniques
*/
(function() {
var dummy, eventNameHash, transitionEnd, _getTransitionEndEventName;
dummy = document.createElement("div");
@ -38,8 +36,8 @@
window.Discourse.TransitionHelper = {
after: function(element, callback) {
return jQuery(element).on(transitionEnd, callback);
return $(element).on(transitionEnd, callback);
}
};
}).call(this);

View file

@ -1,4 +1,10 @@
(function() {
/**
Helper for searching for Users
@class UserSearch
@namespace Discourse
@module Discourse
**/
var cache, cacheTime, cacheTopicId, debouncedSearch, doSearch;
cache = {};
@ -25,7 +31,7 @@
debouncedSearch = Discourse.debounce(doSearch, 200);
window.Discourse.UserSearch = {
Discourse.UserSearch = {
search: function(options) {
var callback, exclude, limit, success, term, topicId;
term = options.term || "";
@ -36,9 +42,8 @@
if (!callback) {
throw "missing callback";
}
/*TODO site setting for allowed regex in username ?
*/
// TODO site setting for allowed regex in username
if (term.match(/[^a-zA-Z0-9\_\.]/)) {
callback([]);
return true;
@ -73,4 +78,4 @@
}
};
}).call(this);

View file

@ -1,13 +1,14 @@
/*global sanitizeHtml:true Markdown:true */
(function() {
var baseUrl, site;
baseUrl = null;
site = null;
/**
General utility functions
@class Utilities
@namespace Discourse
@module Discourse
**/
Discourse.Utilities = {
translateSize: function(size) {
switch (size) {
case 'tiny':
@ -24,6 +25,7 @@
}
return size;
},
categoryUrlId: function(category) {
var id, slug;
if (!category) {
@ -36,9 +38,8 @@
}
return slug;
},
/* Create a badge like category link
*/
// Create a badge like category link
categoryLink: function(category) {
var color, name, description, result;
if (!category) return "";
@ -48,15 +49,14 @@
description = Em.get(category, 'description');
// Build the HTML link
result = "<a href=\"/category/" +
this.categoryUrlId(category) +
"\" class=\"badge-category excerptable\" data-excerpt-size=\"medium\" ";
result = "<a href=\"/category/" + this.categoryUrlId(category) + "\" class=\"badge-category\" ";
// Add description if we have it
if (description) result += "title=\"" + description + "\" ";
return result + "style=\"background-color: #" + color + "\">" + name + "</a>";
},
avatarUrl: function(username, size, template) {
var rawSize;
if (!username) {
@ -69,6 +69,7 @@
}
return "/users/" + (username.toLowerCase()) + "/avatar/" + rawSize + "?__ws=" + (encodeURIComponent(Discourse.BaseUrl || ""));
},
avatarImg: function(options) {
var extraClasses, size, title, url;
size = Discourse.Utilities.translateSize(options.size);
@ -78,6 +79,7 @@
return "<img width='" + size + "' height='" + size + "' src='" + url + "' class='avatar " +
(extraClasses || "") + "' title='" + (Handlebars.Utils.escapeExpression(title || "")) + "'>";
},
postUrl: function(slug, topicId, postNumber) {
var url;
url = "/t/";
@ -90,14 +92,14 @@
}
return url;
},
emailValid: function(email) {
/* see: http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
*/
emailValid: function(email) {
// see: http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
var re;
re = /^[a-zA-Z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-zA-Z0-9!#$%&'\*+\/=?\^_`{|}~\-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/;
return re.test(email);
},
selectedText: function() {
var t;
t = '';
@ -110,9 +112,8 @@
}
return String(t).trim();
},
/* Determine the position of the caret in an element
*/
// Determine the position of the caret in an element
caretPosition: function(el) {
var r, rc, re;
if (el.selectionStart) {
@ -131,9 +132,8 @@
}
return 0;
},
/* Set the caret's position
*/
// Set the caret's position
setCaretPosition: function(ctrl, pos) {
var range;
if (ctrl.setSelectionRange) {
@ -149,6 +149,7 @@
return range.select();
}
},
markdownConverter: function(opts) {
var converter, mentionLookup,
_this = this;
@ -157,9 +158,8 @@
mentionLookup = opts.mentionLookup;
}
mentionLookup = mentionLookup || Discourse.Mention.lookupCache;
/* Before cooking callbacks
*/
// Before cooking callbacks
converter.hooks.chain("preConversion", function(text) {
_this.trigger('beforeCook', {
detail: text,
@ -167,17 +167,15 @@
});
return _this.textResult || text;
});
/* Support autolinking of www.something.com
*/
// Support autolinking of www.something.com
converter.hooks.chain("preConversion", function(text) {
return text.replace(/(^|[\s\n])(www\.[a-z\.\-\_\(\)\/\?\=\%0-9]+)/gim, function(full, _, rest) {
return " <a href=\"http://" + rest + "\">" + rest + "</a>";
});
});
/* newline prediction in trivial cases
*/
// newline prediction in trivial cases
if (!Discourse.SiteSettings.traditional_markdown_linebreaks) {
converter.hooks.chain("preConversion", function(text) {
return text.replace(/(^[\w<][^\n]*\n+)/gim, function(t) {
@ -188,9 +186,8 @@
});
});
}
/* github style fenced code
*/
// github style fenced code
converter.hooks.chain("preConversion", function(text) {
return text.replace(/^`{3}(?:(.*$)\n)?([\s\S]*?)^`{3}/gm, function(wholeMatch, m1, m2) {
var escaped;
@ -198,19 +195,16 @@
return "<pre><code class='" + (m1 || 'lang-auto') + "'>" + escaped + "</code></pre>";
});
});
converter.hooks.chain("postConversion", function(text) {
if (!text) {
return "";
}
/* don't to mention voodoo in pres
*/
converter.hooks.chain("postConversion", function(text) {
if (!text) return "";
// don't to mention voodoo in pres
text = text.replace(/<pre>([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
return "<pre>" + (inner.replace(/@/g, '&#64;')) + "</pre>";
});
/* Add @mentions of names
*/
// Add @mentions of names
text = text.replace(/([\s\t>,:'|";\]])(@[A-Za-z0-9_-|\.]*[A-Za-z0-9_-|]+)(?=[\s\t<\!:|;',"\?\.])/g, function(x, pre, name) {
if (mentionLookup(name.substr(1))) {
return "" + pre + "<a href='/users/" + (name.substr(1).toLowerCase()) + "' class='mention'>" + name + "</a>";
@ -218,13 +212,10 @@
return "" + pre + "<span class='mention'>" + name + "</span>";
}
});
/* a primitive attempt at oneboxing, this regex gives me much eye sores
*/
// a primitive attempt at oneboxing, this regex gives me much eye sores
text = text.replace(/(<li>)?((<p>|<br>)[\s\n\r]*)(<a href=["]([^"]+)[^>]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|<br>))/gi, function() {
/* We don't onebox items in a list
*/
// We don't onebox items in a list
var onebox, url;
if (arguments[1]) {
return arguments[0];
@ -246,6 +237,7 @@
converter.hooks.chain("postConversion", function(text) {
return Discourse.BBCode.format(text, opts);
});
if (opts.sanitize) {
converter.hooks.chain("postConversion", function(text) {
if (!window.sanitizeHtml) {
@ -256,9 +248,8 @@
}
return converter;
},
/* Takes raw input and cooks it to display nicely (mostly markdown)
*/
// Takes raw input and cooks it to display nicely (mostly markdown)
cook: function(raw, opts) {
if (!opts) opts = {};
@ -273,5 +264,3 @@
};
RSVP.EventTarget.mixin(Discourse.Utilities);
}).call(this);

View file

@ -1,7 +1,16 @@
/*global _gaq:true */
window.Discourse.ApplicationController = Ember.Controller.extend({
/**
The base controller for all things Discourse
@class ApplicationController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ApplicationController = Discourse.Controller.extend({
needs: ['modal'],
showLogin: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.LoginView.create()) : void 0;

View file

@ -1,17 +1,24 @@
(function() {
/**
This controller supports composing new posts and topics.
window.Discourse.ComposerController = Ember.Controller.extend(Discourse.Presence, {
@class ComposerController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ComposerController = Discourse.Controller.extend({
needs: ['modal', 'topic'],
hasReply: false,
togglePreview: function() {
return this.get('content').togglePreview();
},
/* Import a quote from the post
*/
// Import a quote from the post
importQuote: function() {
return this.get('content').importQuote();
},
appendText: function(text) {
var c;
c = this.get('content');
@ -19,6 +26,7 @@
return c.appendText(text);
}
},
save: function() {
var composer,
_this = this;
@ -34,36 +42,37 @@
} else {
Discourse.set('currentUser.reply_count', Discourse.get('currentUser.reply_count') + 1);
}
return Discourse.routeTo(opts.post.get('url'));
Discourse.routeTo(opts.post.get('url'));
}, function(error) {
composer.set('disableDrafts', false);
return bootbox.alert(error);
bootbox.alert(error);
});
},
checkReplyLength: function() {
if (this.present('content.reply')) {
return this.set('hasReply', true);
this.set('hasReply', true);
} else {
return this.set('hasReply', false);
this.set('hasReply', false);
}
},
saveDraft: function() {
var model;
model = this.get('content');
if (model) {
return model.saveDraft();
}
if (model) model.saveDraft();
},
/*
Open the reply view
opts:
action - The action we're performing: edit, reply or createTopic
post - The post we're replying to, if present
topic - The topic we're replying to, if present
quote - If we're opening a reply from a quote, the quote we're making
*/
/**
Open the composer view
@method open
@param {Object} opts Options for creating a post
@param {String} opts.action The action we're performing: edit, reply or createTopic
@param {Discourse.Post} [opts.post] The post we're replying to
@param {Discourse.Topic} [opts.topic] The topic we're replying to
@param {String} [opts.quote] If we're opening a reply from a quote, the quote we're making
**/
open: function(opts) {
var composer, promise, view,
_this = this;
@ -75,22 +84,17 @@
alert("composer was opened without a draft key");
throw "composer opened without a proper draft key";
}
/* ensure we have a view now, without it transitions are going to be messed
*/
// ensure we have a view now, without it transitions are going to be messed
view = this.get('view');
if (!view) {
view = Discourse.ComposerView.create({
controller: this
});
view.appendTo(jQuery('#main'));
view.appendTo($('#main'));
this.set('view', view);
/* the next runloop is too soon, need to get the control rendered and then
*/
/* we need to change stuff, otherwise css animations don't kick in
*/
// the next runloop is too soon, need to get the control rendered and then
// we need to change stuff, otherwise css animations don't kick in
Em.run.next(function() {
return Em.run.next(function() {
return _this.open(opts);
@ -98,11 +102,13 @@
});
return promise;
}
composer = this.get('content');
if (composer && opts.draftKey !== composer.draftKey && composer.composeState === Discourse.Composer.DRAFT) {
this.close();
composer = null;
}
if (composer && !opts.tested && composer.wouldLoseChanges()) {
if (composer.composeState === Discourse.Composer.DRAFT && composer.draftKey === opts.draftKey && composer.action === opts.action) {
composer.set('composeState', Discourse.Composer.OPEN);
@ -120,9 +126,8 @@
return promise;
}
}
/* we need a draft sequence, without it drafts are bust
*/
// we need a draft sequence, without it drafts are bust
if (opts.draftSequence === void 0) {
Discourse.Draft.get(opts.draftKey).then(function(data) {
opts.draftSequence = data.draft_sequence;
@ -131,31 +136,34 @@
});
return promise;
}
if (opts.draft) {
composer = Discourse.Composer.loadDraft(opts.draftKey, opts.draftSequence, opts.draft);
if (composer) {
composer.set('topic', opts.topic);
}
}
composer = composer || Discourse.Composer.open(opts);
this.set('content', composer);
this.set('view.content', composer);
promise.resolve();
return promise;
},
wouldLoseChanges: function() {
var composer;
composer = this.get('content');
return composer && composer.wouldLoseChanges();
},
/* View a new reply we've made
*/
// View a new reply we've made
viewNewReply: function() {
Discourse.routeTo(this.get('createdPost.url'));
this.close();
return false;
},
destroyDraft: function() {
var key;
key = this.get('content.draftKey');
@ -163,6 +171,7 @@
return Discourse.Draft.clear(key, this.get('content.draftSequence'));
}
},
cancel: function(success, fail) {
var _this = this;
if (this.get('content.hasMetaData') || ((this.get('content.reply') || "") !== (this.get('content.originalText') || ""))) {
@ -180,9 +189,7 @@
}
});
} else {
/* it is possible there is some sort of crazy draft with no body ... just give up on it
*/
// it is possible there is some sort of crazy draft with no body ... just give up on it
this.destroyDraft();
this.close();
if (typeof success === "function") {
@ -190,11 +197,13 @@
}
}
},
click: function() {
if (this.get('content.composeState') === Discourse.Composer.DRAFT) {
return this.set('content.composeState', Discourse.Composer.OPEN);
}
},
shrink: function() {
if (this.get('content.reply') === this.get('content.originalText')) {
return this.close();
@ -202,27 +211,28 @@
return this.collapse();
}
},
collapse: function() {
this.saveDraft();
return this.set('content.composeState', Discourse.Composer.DRAFT);
this.set('content.composeState', Discourse.Composer.DRAFT);
},
close: function() {
this.set('content', null);
return this.set('view.content', null);
this.set('view.content', null);
},
closeIfCollapsed: function() {
if (this.get('content.composeState') === Discourse.Composer.DRAFT) {
return this.close();
this.close();
}
},
closeAutocomplete: function() {
return jQuery('#wmd-input').autocomplete({
cancel: true
});
},
/* Toggle the reply view
*/
closeAutocomplete: function() {
$('#wmd-input').autocomplete({ cancel: true });
},
// Toggle the reply view
toggle: function() {
this.closeAutocomplete();
switch (this.get('content.composeState')) {
@ -241,14 +251,14 @@
}
return false;
},
/* ESC key hit
*/
// ESC key hit
hitEsc: function() {
if (this.get('content.composeState') === Discourse.Composer.OPEN) {
return this.shrink();
this.shrink();
}
},
showOptions: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.ArchetypeOptionsModalView.create({
@ -258,4 +268,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,12 @@
(function() {
/**
A base controller for Discourse that includes Presence support.
@class Controller
@extends Ember.Controller
@namespace Discourse
@uses Discourse.Presence
@module Discourse
**/
Discourse.Controller = Ember.Controller.extend(Discourse.Presence);
}).call(this);

View file

@ -1,15 +1,21 @@
(function() {
/**
This controller supports actions on the site header
Discourse.HeaderController = Ember.Controller.extend(Discourse.Presence, {
@class HeaderController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.HeaderController = Discourse.Controller.extend({
topic: null,
showExtraInfo: false,
toggleStar: function() {
var _ref;
if (_ref = this.get('topic')) {
_ref.toggleStar();
}
var topic = this.get('topic');
if (topic) topic.toggleStar();
return false;
}
});
}).call(this);

View file

@ -1,7 +1,14 @@
(function() {
/**
This controller supports actions when listing categories
Discourse.ListCategoriesController = Ember.ObjectController.extend(Discourse.Presence, {
@class ListCategoriesController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.ListCategoriesController = Discourse.ObjectController.extend({
needs: ['modal'],
categoriesEven: (function() {
if (this.blank('categories')) {
return Em.A();
@ -10,6 +17,7 @@
return (index % 2) === 0;
});
}).property('categories.@each'),
categoriesOdd: (function() {
if (this.blank('categories')) {
return Em.A();
@ -18,17 +26,18 @@
return (index % 2) === 1;
});
}).property('categories.@each'),
editCategory: function(category) {
this.get('controllers.modal').show(Discourse.EditCategoryView.create({
category: category
}));
this.get('controllers.modal').show(Discourse.EditCategoryView.create({ category: category }));
return false;
},
canEdit: (function() {
var u;
u = Discourse.get('currentUser');
return u && u.admin;
}).property()
});
}).call(this);

View file

@ -1,12 +1,19 @@
(function() {
/**
This controller supports actions when listing topics or categories
Discourse.ListController = Ember.Controller.extend(Discourse.Presence, {
@class ListController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ListController = Discourse.Controller.extend({
currentUserBinding: 'Discourse.currentUser',
categoriesBinding: 'Discourse.site.categories',
categoryBinding: 'topicList.category',
canCreateCategory: false,
canCreateTopic: false,
needs: ['composer', 'modal', 'listTopics'],
availableNavItems: (function() {
var hasCategories, loggedOn, summary;
summary = this.get('filterSummary');
@ -22,6 +29,7 @@
return i !== null;
});
}).property('filterSummary'),
load: function(filterMode) {
var current,
_this = this;
@ -54,9 +62,8 @@
});
}
},
/* Put in the appropriate page title based on our view
*/
// Put in the appropriate page title based on our view
updateTitle: (function() {
if (this.get('filterMode') === 'categories') {
return Discourse.set('title', Em.String.i18n('categories_list'));
@ -68,9 +75,8 @@
}
}
}).observes('filterMode', 'category'),
/* Create topic button
*/
// Create topic button
createTopic: function() {
var topicList;
topicList = this.get('controllers.listTopics.content');
@ -84,14 +90,16 @@
draftSequence: topicList.get('draft_sequence')
});
},
createCategory: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.EditCategoryView.create()) : void 0;
}
});
Discourse.ListController.reopenClass({
filters: ['popular', 'favorited', 'read', 'unread', 'new', 'posted']
});
}).call(this);

View file

@ -1,18 +1,22 @@
(function() {
/**
This controller supports actions when listing topics or categories
Discourse.ListTopicsController = Ember.ObjectController.extend({
@class ListTopicsController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.ListTopicsController = Discourse.ObjectController.extend({
needs: ['list', 'composer'],
/* If we're changing our channel
*/
// If we're changing our channel
previousChannel: null,
popular: (function() {
return this.get('content.filter') === 'popular';
}).property('content.filter'),
filterModeChanged: (function() {
/* Unsubscribe from a previous channel if necessary
*/
filterModeChanged: (function() {
// Unsubscribe from a previous channel if necessary
var channel, filterMode, previousChannel,
_this = this;
if (previousChannel = this.get('previousChannel')) {
@ -41,33 +45,29 @@
});
}
}).observes('content.draft'),
/* Star a topic
*/
// Star a topic
toggleStar: function(topic) {
topic.toggleStar();
return false;
},
createTopic: function() {
this.get('controllers.list').createTopic();
return false;
},
observer: (function() {
return this.set('filterMode', this.get('controllser.list.filterMode'));
}).observes('controller.list.filterMode'),
/* Show newly inserted topics
*/
// Show newly inserted topics
showInserted: function(e) {
/* Move inserted into topics
*/
// Move inserted into topics
this.get('content.topics').unshiftObjects(this.get('content.inserted'));
/* Clear inserted
*/
// Clear inserted
this.set('content.inserted', Em.A());
return false;
}
});
}).call(this);

View file

@ -1,9 +1,15 @@
(function() {
/**
This controller supports actions related to showing modals
Discourse.ModalController = Ember.Controller.extend(Discourse.Presence, {
@class ModalController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ModalController = Discourse.Controller.extend({
show: function(view) {
return this.set('currentView', view);
this.set('currentView', view);
}
});
}).call(this);

View file

@ -0,0 +1,12 @@
/**
A custom object controller for Discourse
@class ObjectController
@extends Ember.ObjectController
@namespace Discourse
@uses Discourse.Presence
@module Discourse
**/
Discourse.ObjectController = Ember.ObjectController.extend(Discourse.Presence);

View file

@ -1,132 +1,63 @@
(function() {
Discourse.PreferencesController = Ember.ObjectController.extend(Discourse.Presence, {
/* By default we haven't saved anything
*/
/**
This controller supports actions related to updating one's preferences
@class PreferencesController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesController = Discourse.ObjectController.extend({
// By default we haven't saved anything
saved: false,
saveDisabled: (function() {
if (this.get('saving')) {
return true;
}
if (this.blank('content.name')) {
return true;
}
if (this.blank('content.email')) {
return true;
}
if (this.get('saving')) return true;
if (this.blank('content.name')) return true;
if (this.blank('content.email')) return true;
return false;
}).property('saving', 'content.name', 'content.email'),
digestFrequencies: (function() {
var freqs;
freqs = Em.A();
freqs.addObject({
name: Em.String.i18n('user.email_digests.daily'),
value: 1
});
freqs.addObject({
name: Em.String.i18n('user.email_digests.weekly'),
value: 7
});
freqs.addObject({
name: Em.String.i18n('user.email_digests.bi_weekly'),
value: 14
});
freqs.addObject({ name: Em.String.i18n('user.email_digests.daily'), value: 1 });
freqs.addObject({ name: Em.String.i18n('user.email_digests.weekly'), value: 7 });
freqs.addObject({ name: Em.String.i18n('user.email_digests.bi_weekly'), value: 14 });
return freqs;
}).property(),
autoTrackDurations: (function() {
var freqs;
freqs = Em.A();
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.never'),
value: -1
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.always'),
value: 0
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_seconds', {
count: 30
}),
value: 30000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 1
}),
value: 60000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 2
}),
value: 120000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 3
}),
value: 180000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 4
}),
value: 240000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 5
}),
value: 300000
});
freqs.addObject({
name: Em.String.i18n('user.auto_track_options.after_n_minutes', {
count: 10
}),
value: 600000
});
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.never'), value: -1 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.always'), value: 0 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_seconds', { count: 30 }), value: 30000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 1 }), value: 60000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 2 }), value: 120000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 3 }), value: 180000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 4 }), value: 240000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 5 }), value: 300000 });
freqs.addObject({ name: Em.String.i18n('user.auto_track_options.after_n_minutes', { count: 10 }), value: 600000 });
return freqs;
}).property(),
considerNewTopicOptions: (function() {
var opts;
opts = Em.A();
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.not_viewed'),
value: -1
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.after_n_days', {
count: 1
}),
value: 60 * 24
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.after_n_days', {
count: 2
}),
value: 60 * 48
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.after_n_weeks', {
count: 1
}),
value: 7 * 60 * 24
});
opts.addObject({
name: Em.String.i18n('user.new_topic_duration.last_here'),
value: -2
});
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.not_viewed'), value: -1 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.after_n_days', { count: 1 }), value: 60 * 24 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.after_n_days', { count: 2 }), value: 60 * 48 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.after_n_weeks', { count: 1 }), value: 7 * 60 * 24 });
opts.addObject({ name: Em.String.i18n('user.new_topic_duration.last_here'), value: -2 });
return opts;
}).property(),
save: function() {
var _this = this;
this.set('saving', true);
this.set('saved', false);
/* Cook the bio for preview
*/
// Cook the bio for preview
return this.get('content').save(function(result) {
_this.set('saving', false);
if (result) {
@ -137,12 +68,12 @@
}
});
},
saveButtonText: (function() {
if (this.get('saving')) {
return Em.String.i18n('saving');
}
if (this.get('saving')) return Em.String.i18n('saving');
return Em.String.i18n('save');
}).property('saving'),
changePassword: function() {
var _this = this;
if (!this.get('passwordProgress')) {
@ -155,4 +86,4 @@
}
});
}).call(this);

View file

@ -1,36 +1,37 @@
(function() {
/**
This controller supports actions related to updating one's email address
Discourse.PreferencesEmailController = Ember.ObjectController.extend(Discourse.Presence, {
@class PreferencesEmailController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesEmailController = Discourse.ObjectController.extend({
taken: false,
saving: false,
error: false,
success: false,
saveDisabled: (function() {
if (this.get('saving')) {
return true;
}
if (this.blank('newEmail')) {
return true;
}
if (this.get('taken')) {
return true;
}
if (this.get('unchanged')) {
return true;
}
if (this.get('saving')) return true;
if (this.blank('newEmail')) return true;
if (this.get('taken')) return true;
if (this.get('unchanged')) return true;
}).property('newEmail', 'taken', 'unchanged', 'saving'),
unchanged: (function() {
return this.get('newEmail') === this.get('content.email');
}).property('newEmail', 'content.email'),
initializeEmail: (function() {
return this.set('newEmail', this.get('content.email'));
this.set('newEmail', this.get('content.email'));
}).observes('content.email'),
saveButtonText: (function() {
if (this.get('saving')) {
return Em.String.i18n("saving");
}
if (this.get('saving')) return Em.String.i18n("saving");
return Em.String.i18n("user.change_email.action");
}).property('saving'),
changeEmail: function() {
var _this = this;
this.set('saving', true);
@ -43,6 +44,7 @@
return _this.set('saving', false);
});
}
});
}).call(this);

View file

@ -1,42 +1,37 @@
(function() {
/**
This controller supports actions related to updating one's username
Discourse.PreferencesUsernameController = Ember.ObjectController.extend(Discourse.Presence, {
@class PreferencesUsernameController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.PreferencesUsernameController = Discourse.ObjectController.extend({
taken: false,
saving: false,
error: false,
errorMessage: null,
saveDisabled: (function() {
if (this.get('saving')) {
return true;
}
if (this.blank('newUsername')) {
return true;
}
if (this.get('taken')) {
return true;
}
if (this.get('unchanged')) {
return true;
}
if (this.get('errorMessage')) {
return true;
}
if (this.get('saving')) return true;
if (this.blank('newUsername')) return true;
if (this.get('taken')) return true;
if (this.get('unchanged')) return true;
if (this.get('errorMessage')) return true;
return false;
}).property('newUsername', 'taken', 'errorMessage', 'unchanged', 'saving'),
unchanged: (function() {
return this.get('newUsername') === this.get('content.username');
}).property('newUsername', 'content.username'),
checkTaken: (function() {
var _this = this;
this.set('taken', false);
this.set('errorMessage', null);
if (this.blank('newUsername')) {
return;
}
if (this.get('unchanged')) {
return;
}
return Discourse.User.checkUsername(this.get('newUsername')).then(function(result) {
if (this.blank('newUsername')) return;
if (this.get('unchanged')) return;
Discourse.User.checkUsername(this.get('newUsername')).then(function(result) {
if (result.errors) {
return _this.set('errorMessage', result.errors.join(' '));
} else if (result.available === false) {
@ -44,12 +39,12 @@
}
});
}).observes('newUsername'),
saveButtonText: (function() {
if (this.get('saving')) {
return Em.String.i18n("saving");
}
if (this.get('saving')) return Em.String.i18n("saving");
return Em.String.i18n("user.change_username.action");
}).property('saving'),
changeUsername: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("user.change_username.confirm"), Em.String.i18n("no_value"), Em.String.i18n("yes_value"), function(result) {
@ -68,4 +63,4 @@
}
});
}).call(this);

View file

@ -1,44 +1,45 @@
(function() {
/**
This controller supports the pop up quote button
@class QuoteButtonController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.QuoteButtonController = Discourse.Controller.extend({
needs: ['topic', 'composer'],
started: null,
/* 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')) {
return this.set('post', null);
}
}).observes('buffer'),
mouseDown: function(e) {
this.started = [e.pageX, e.pageY];
},
mouseUp: function(e) {
if (this.started[1] > e.pageY) {
this.started = [e.pageX, e.pageY];
}
},
selectText: function(e) {
var $quoteButton, left, selectedText, top;
if (!Discourse.get('currentUser')) {
return;
}
if (!this.get('controllers.topic.content.can_create_post')) {
return;
}
if (!Discourse.get('currentUser')) return;
if (!this.get('controllers.topic.content.can_create_post')) return;
selectedText = Discourse.Utilities.selectedText();
if (this.get('buffer') === selectedText) {
return;
}
if (this.get('lastSelected') === selectedText) {
return;
}
if (this.get('buffer') === selectedText) return;
if (this.get('lastSelected') === selectedText) return;
this.set('post', e.context);
this.set('buffer', selectedText);
top = e.pageY + 5;
left = e.pageX + 5;
$quoteButton = jQuery('.quote-button');
$quoteButton = $('.quote-button');
if (this.started) {
top = this.started[1] - 50;
left = ((left - this.started[0]) / 2) + this.started[0] - ($quoteButton.width() / 2);
@ -50,6 +51,7 @@
this.started = null;
return false;
},
quoteText: function(e) {
var buffer, composerController, composerOpts, composerPost, post, quotedText,
_this = this;
@ -61,9 +63,8 @@
action: Discourse.Composer.REPLY,
draftKey: this.get('post.topic.draft_key')
};
/* 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.
if (composerPost = composerController.get('content.post')) {
if (composerPost.get('id') !== this.get('post.id')) {
composerOpts.post = composerPost;
@ -71,6 +72,7 @@
}
buffer = this.get('buffer');
quotedText = Discourse.BBCode.buildQuoteBBCode(post, buffer);
if (composerController.wouldLoseChanges()) {
composerController.appendText(quotedText);
} else {
@ -82,5 +84,3 @@
return false;
}
});
}).call(this);

View file

@ -1,29 +1,33 @@
(function() {
/**
This controller supports the "share" link controls
Discourse.ShareController = Ember.Controller.extend({
/* When the user clicks the post number, we pop up a share box
*/
@class ShareController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.ShareController = Discourse.Controller.extend({
// When the user clicks the post number, we pop up a share box
shareLink: function(e, url) {
var x;
x = e.pageX - 150;
if (x < 25) {
x = 25;
}
jQuery('#share-link').css({
$('#share-link').css({
left: "" + x + "px",
top: "" + (e.pageY - 100) + "px"
});
this.set('link', url);
return false;
},
/* Close the share controller
*/
// Close the share controller
close: function() {
this.set('link', '');
return false;
}
});
}).call(this);

View file

@ -1,15 +1,21 @@
(function() {
/**
This controller supports displaying static content.
Discourse.StaticController = Ember.Controller.extend({
@class StaticController
@extends Discourse.Controller
@namespace Discourse
@module Discourse
**/
Discourse.StaticController = Discourse.Controller.extend({
content: null,
loadPath: function(path) {
var $preloaded, text,
_this = this;
this.set('content', null);
/* Load from <noscript> if we have it.
*/
$preloaded = jQuery("noscript[data-path=\"" + path + "\"]");
// Load from <noscript> if we have it.
$preloaded = $("noscript[data-path=\"" + path + "\"]");
if ($preloaded.length) {
text = $preloaded.text();
text = text.replace(/<header[\s\S]*<\/header\>/, '');
@ -29,4 +35,4 @@
pages: ['faq', 'tos', 'privacy']
});
}).call(this);

View file

@ -1,13 +1,20 @@
(function() {
/**
This controller supports the admin menu on topics
Discourse.TopicAdminMenuController = Ember.ObjectController.extend({
@class TopicAdminMenuController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.TopicAdminMenuController = Discourse.ObjectController.extend({
visible: false,
show: function() {
return this.set('visible', true);
},
hide: function() {
return this.set('visible', false);
}
});
}).call(this);
show: function() {
this.set('visible', true);
},
hide: function() {
this.set('visible', false);
}
});

View file

@ -1,64 +1,56 @@
(function() {
Discourse.TopicController = Ember.ObjectController.extend(Discourse.Presence, {
/* A list of usernames we want to filter by
*/
/**
This controller supports all actions related to a topic
@class TopicController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.TopicController = Discourse.ObjectController.extend({
userFilters: new Em.Set(),
multiSelect: false,
bestOf: false,
showExtraHeaderInfo: false,
needs: ['header', 'modal', 'composer', 'quoteButton'],
filter: (function() {
if (this.get('bestOf') === true) {
return 'best_of';
}
if (this.get('userFilters').length > 0) {
return 'user';
}
if (this.get('bestOf') === true) return 'best_of';
if (this.get('userFilters').length > 0) return 'user';
return null;
}).property('userFilters.[]', 'bestOf'),
filterDesc: (function() {
var filter;
if (!(filter = this.get('filter'))) {
return null;
}
if (!(filter = this.get('filter'))) return null;
return Em.String.i18n("topic.filters." + filter);
}).property('filter'),
selectedPosts: (function() {
var posts;
if (!(posts = this.get('content.posts'))) {
return null;
}
if (!(posts = this.get('content.posts'))) return null;
return posts.filterProperty('selected');
}).property('content.posts.@each.selected'),
selectedCount: (function() {
if (!this.get('selectedPosts')) {
return 0;
}
if (!this.get('selectedPosts')) return 0;
return this.get('selectedPosts').length;
}).property('selectedPosts'),
canMoveSelected: (function() {
if (!this.get('content.can_move_posts')) {
return false;
}
/* For now, we can move it if we can delete it since the posts
*/
/* need to be deleted.
*/
if (!this.get('content.can_move_posts')) return false;
// For now, we can move it if we can delete it since the posts need to be deleted.
return this.get('canDeleteSelected');
}).property('canDeleteSelected'),
showExtraHeaderInfoChanged: (function() {
return this.set('controllers.header.showExtraInfo', this.get('showExtraHeaderInfo'));
this.set('controllers.header.showExtraInfo', this.get('showExtraHeaderInfo'));
}).observes('showExtraHeaderInfo'),
canDeleteSelected: (function() {
var canDelete, selectedPosts;
selectedPosts = this.get('selectedPosts');
if (!(selectedPosts && selectedPosts.length > 0)) {
return false;
}
if (!(selectedPosts && selectedPosts.length > 0)) return false;
canDelete = true;
selectedPosts.each(function(p) {
if (!p.get('can_delete')) {
@ -68,10 +60,9 @@
});
return canDelete;
}).property('selectedPosts'),
multiSelectChanged: (function() {
/* Deselect all posts when multi select is turned off
*/
multiSelectChanged: (function() {
// Deselect all posts when multi select is turned off
var posts;
if (!this.get('multiSelect')) {
if (posts = this.get('content.posts')) {
@ -81,24 +72,22 @@
}
}
}).observes('multiSelect'),
hideProgress: (function() {
if (!this.get('content.loaded')) {
return true;
}
if (!this.get('currentPost')) {
return true;
}
if (this.get('content.highest_post_number') < 2) {
return true;
}
if (!this.get('content.loaded')) return true;
if (!this.get('currentPost')) return true;
if (this.get('content.highest_post_number') < 2) return true;
return this.present('filter');
}).property('filter', 'content.loaded', 'currentPost'),
selectPost: function(post) {
return post.toggleProperty('selected');
post.toggleProperty('selected');
},
toggleMultiSelect: function() {
return this.toggleProperty('multiSelect');
this.toggleProperty('multiSelect');
},
moveSelected: function() {
var _ref;
return (_ref = this.get('controllers.modal')) ? _ref.show(Discourse.MoveSelectedView.create({
@ -106,6 +95,7 @@
selectedPosts: this.get('selectedPosts')
})) : void 0;
},
deleteSelected: function() {
var _this = this;
return bootbox.confirm(Em.String.i18n("post.delete.confirm", {
@ -117,22 +107,25 @@
}
});
},
jumpTop: function() {
return Discourse.routeTo(this.get('content.url'));
Discourse.routeTo(this.get('content.url'));
},
jumpBottom: function() {
return Discourse.routeTo(this.get('content.lastPostUrl'));
Discourse.routeTo(this.get('content.lastPostUrl'));
},
cancelFilter: function() {
this.set('bestOf', false);
return this.get('userFilters').clear();
this.get('userFilters').clear();
},
replyAsNewTopic: function(post) {
var composerController, postLink, postUrl, promise;
composerController = this.get('controllers.composer');
/*TODO shut down topic draft cleanly if it exists ...
*/
// TODO shut down topic draft cleanly if it exists ...
promise = composerController.open({
action: Discourse.Composer.CREATE_TOPIC,
draftKey: Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY
@ -147,9 +140,8 @@
});
});
},
/* Topic related
*/
// Topic related
reply: function() {
var composerController;
composerController = this.get('controllers.composer');
@ -160,6 +152,7 @@
draftSequence: this.get('content.draft_sequence')
});
},
toggleParticipant: function(user) {
var userFilters, username;
this.set('bestOf', false);
@ -172,17 +165,20 @@
}
return false;
},
enableBestOf: function(e) {
this.set('bestOf', true);
this.get('userFilters').clear();
return false;
},
showBestOf: (function() {
if (this.get('bestOf') === true) {
return false;
}
return this.get('content.has_best_of') === true;
}).property('bestOf', 'content.has_best_of'),
postFilters: (function() {
if (this.get('bestOf') === true) {
return {
@ -193,17 +189,14 @@
userFilters: this.get('userFilters')
};
}).property('userFilters.[]', 'bestOf'),
reloadTopics: (function() {
var posts, topic,
_this = this;
topic = this.get('content');
if (!topic) {
return;
}
if (!topic) return;
posts = topic.get('posts');
if (!posts) {
return;
}
if (!posts) return;
posts.clear();
this.set('content.loaded', false);
return Discourse.Topic.find(this.get('content.id'), this.get('postFilters')).then(function(result) {
@ -212,67 +205,68 @@
if (first) {
_this.set('currentPost', first.post_number);
}
jQuery('#topic-progress .solid').data('progress', false);
$('#topic-progress .solid').data('progress', false);
result.posts.each(function(p) {
return posts.pushObject(Discourse.Post.create(p, topic));
});
return _this.set('content.loaded', true);
});
}).observes('postFilters'),
deleteTopic: function(e) {
var _this = this;
this.unsubscribe();
return this.get('content')["delete"](function() {
this.get('content')["delete"](function() {
_this.set('message', "The topic has been deleted");
return _this.set('loaded', false);
_this.set('loaded', false);
});
},
toggleVisibility: function() {
return this.get('content').toggleStatus('visible');
this.get('content').toggleStatus('visible');
},
toggleClosed: function() {
return this.get('content').toggleStatus('closed');
this.get('content').toggleStatus('closed');
},
togglePinned: function() {
return this.get('content').toggleStatus('pinned');
this.get('content').toggleStatus('pinned');
},
toggleArchived: function() {
return this.get('content').toggleStatus('archived');
this.get('content').toggleStatus('archived');
},
convertToRegular: function() {
return this.get('content').convertArchetype('regular');
this.get('content').convertArchetype('regular');
},
startTracking: function() {
var screenTrack;
screenTrack = Discourse.ScreenTrack.create({
topic_id: this.get('content.id')
});
screenTrack = Discourse.ScreenTrack.create({ topic_id: this.get('content.id') });
screenTrack.start();
return this.set('content.screenTrack', screenTrack);
},
stopTracking: function() {
var _ref;
if (_ref = this.get('content.screenTrack')) {
_ref.stop();
}
return this.set('content.screenTrack', null);
var screenTrack = this.get('content.screenTrack');
if (screenTrack) screenTrack.stop();
this.set('content.screenTrack', null);
},
/* Toggle the star on the topic
*/
// Toggle the star on the topic
toggleStar: function(e) {
return this.get('content').toggleStar();
this.get('content').toggleStar();
},
/* Receive notifications for this topic
*/
// Receive notifications for this topic
subscribe: function() {
var bus,
_this = this;
bus = Discourse.MessageBus;
/* there is a condition where the view never calls unsubscribe, navigate to a topic from a topic
*/
// there is a condition where the view never calls unsubscribe, navigate to a topic from a topic
bus.unsubscribe('/topic/*');
return bus.subscribe("/topic/" + (this.get('content.id')), function(data) {
var posts, topic;
@ -295,6 +289,7 @@
return Discourse.notifyTitle();
});
},
unsubscribe: function() {
var bus, topicId;
topicId = this.get('content.id');
@ -304,9 +299,8 @@
bus = Discourse.MessageBus;
return bus.unsubscribe("/topic/" + topicId);
},
/* Post related methods
*/
// Post related methods
replyToPost: function(post) {
var composerController, promise, quoteController, quotedText,
_this = this;
@ -325,15 +319,12 @@
draftKey: post.get('topic.draft_key'),
draftSequence: post.get('topic.draft_sequence')
});
promise.then(function() {
return composerController.appendText(quotedText);
});
promise.then(function() { return composerController.appendText(quotedText); });
}
return false;
},
/* Edits a post
*/
// Edits a post
editPost: function(post) {
return this.get('controllers.composer').open({
post: post,
@ -342,6 +333,7 @@
draftSequence: post.get('topic.draft_sequence')
});
},
toggleBookmark: function(post) {
if (!Discourse.get('currentUser')) {
alert(Em.String.i18n("bookmarks.not_bookmarked"));
@ -350,16 +342,17 @@
post.toggleProperty('bookmarked');
return false;
},
clearFlags: function(actionType) {
return actionType.clearFlags();
},
/* Who acted on a particular post / action type
*/
clearFlags: function(actionType) {
actionType.clearFlags();
},
// Who acted on a particular post / action type
whoActed: function(actionType) {
actionType.loadUsers();
return false;
},
showPrivateInviteModal: function() {
var modal, _ref;
modal = Discourse.InvitePrivateModalView.create({
@ -370,6 +363,7 @@
}
return false;
},
showInviteModal: function() {
var _ref;
if (_ref = this.get('controllers.modal')) {
@ -379,6 +373,7 @@
}
return false;
},
// Clicked the flag button
showFlags: function(post) {
var flagView, _ref;
@ -388,23 +383,23 @@
});
return (_ref = this.get('controllers.modal')) ? _ref.show(flagView) : void 0;
},
showHistory: function(post) {
var view, _ref;
view = Discourse.HistoryView.create({
originalPost: post
});
view = Discourse.HistoryView.create({ originalPost: post });
if (_ref = this.get('controllers.modal')) {
_ref.show(view);
}
return false;
},
recoverPost: function(post) {
post.set('deleted_at', null);
return post.recover();
},
deletePost: function(post) {
/* Moderators can delete posts. Regular users can only create a deleted at message.
*/
// Moderators can delete posts. Regular users can only create a deleted at message.
if (Discourse.get('currentUser.moderator')) {
post.set('deleted_at', new Date());
} else {
@ -416,4 +411,4 @@
}
});
}).call(this);

View file

@ -1,12 +1,20 @@
(function() {
/**
This controller supports all actions on a user's activity stream
Discourse.UserActivityController = Ember.ObjectController.extend({
@class UserActivityController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserActivityController = Discourse.ObjectController.extend({
needs: ['composer'],
kickOffPrivateMessage: (function() {
if (this.get('content.openPrivateMessage')) {
return this.composePrivateMessage();
this.composePrivateMessage();
}
}).observes('content.openPrivateMessage'),
composePrivateMessage: function() {
return this.get('controllers.composer').open({
action: Discourse.Composer.PRIVATE_MESSAGE,
@ -17,4 +25,4 @@
}
});
}).call(this);

View file

@ -1,12 +1,21 @@
(function() {
/**
This controller handles general user actions
@class UserController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserController = Discourse.ObjectController.extend({
Discourse.UserController = Ember.ObjectController.extend({
viewingSelf: (function() {
return this.get('content.username') === Discourse.get('currentUser.username');
}).property('content.username', 'Discourse.currentUser.username'),
canSeePrivateMessages: (function() {
return this.get('viewingSelf') || Discourse.get('currentUser.admin');
}).property('viewingSelf', 'Discourse.currentUser')
});
}).call(this);

View file

@ -1,10 +1,16 @@
(function() {
/**
This controller handles actions related to a user's invitations
Discourse.UserInvitedController = Ember.ObjectController.extend({
@class UserInvitedController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserInvitedController = Discourse.ObjectController.extend({
rescind: function(invite) {
invite.rescind();
return false;
}
});
}).call(this);

View file

@ -1,9 +1,17 @@
(function() {
/**
This controller handles actions related to a user's private messages.
@class UserPrivateMessagesController
@extends Discourse.ObjectController
@namespace Discourse
@module Discourse
**/
Discourse.UserPrivateMessagesController = Discourse.ObjectController.extend({
Discourse.UserPrivateMessagesController = Ember.ObjectController.extend({
editPreferences: function() {
return Discourse.routeTo("/users/" + (this.get('content.username_lower')) + "/preferences");
},
composePrivateMessage: function() {
var composerController;
composerController = Discourse.get('router.composerController');
@ -13,6 +21,5 @@
draftKey: 'new_private_message'
});
}
});
}).call(this);
});

View file

@ -1,7 +1,11 @@
/*global humaneDate:true */
(function() {
/**
Breaks up a long string
@method breakUp
@for Handlebars
**/
Handlebars.registerHelper('breakUp', function(property, options) {
var prop, result, tokens;
prop = Ember.Handlebars.get(this, property, options);
@ -22,25 +26,47 @@
return result;
});
/**
Truncates long strings
@method shorten
@for Handlebars
**/
Handlebars.registerHelper('shorten', function(property, options) {
var str;
str = Ember.Handlebars.get(this, property, options);
return str.truncate(35);
return Ember.Handlebars.get(this, property, options).truncate(35);
});
/**
Produces a link to a topic
@method topicLink
@for Handlebars
**/
Handlebars.registerHelper('topicLink', function(property, options) {
var title, topic;
topic = Ember.Handlebars.get(this, property, options);
title = topic.get('fancy_title') || topic.get('title');
return "<a href='" + (topic.get('lastReadUrl')) + "' class='title excerptable'>" + title + "</a>";
return "<a href='" + (topic.get('lastReadUrl')) + "' class='title'>" + title + "</a>";
});
/**
Produces a link to a category
@method categoryLink
@for Handlebars
**/
Handlebars.registerHelper('categoryLink', function(property, options) {
var category;
category = Ember.Handlebars.get(this, property, options);
return new Handlebars.SafeString(Discourse.Utilities.categoryLink(category));
});
/**
Produces a link to a route with support for i18n on the title
@method titledLinkTo
@for Handlebars
**/
Handlebars.registerHelper('titledLinkTo', function(name, object) {
var options;
options = [].slice.call(arguments, -1)[0];
@ -54,12 +80,16 @@
}
});
/**
Shorten a URL for display by removing common components
@method shortenUrl
@for Handlebars
**/
Handlebars.registerHelper('shortenUrl', function(property, options) {
var url;
url = Ember.Handlebars.get(this, property, options);
/* Remove trailing slash if it's a top level URL
*/
// Remove trailing slash if it's a top level URL
if (url.match(/\//g).length === 3) {
url = url.replace(/\/$/, '');
}
@ -68,6 +98,12 @@
return url.truncate(80);
});
/**
Display a property in lower case
@method lower
@for Handlebars
**/
Handlebars.registerHelper('lower', function(property, options) {
var o;
o = Ember.Handlebars.get(this, property, options);
@ -78,6 +114,12 @@
}
});
/**
Show an avatar for a user, intelligently making use of available properties
@method avatar
@for Handlebars
**/
Handlebars.registerHelper('avatar', function(user, options) {
var title, username;
if (typeof user === 'string') {
@ -99,12 +141,24 @@
}));
});
/**
Nicely format a date without a binding since the date doesn't need to change.
@method unboundDate
@for Handlebars
**/
Handlebars.registerHelper('unboundDate', function(property, options) {
var dt;
dt = new Date(Ember.Handlebars.get(this, property, options));
return dt.format("{d} {Mon}, {yyyy} {hh}:{mm}");
});
/**
Display a date related to an edit of a post
@method editDate
@for Handlebars
**/
Handlebars.registerHelper('editDate', function(property, options) {
var dt, yesterday;
dt = Date.create(Ember.Handlebars.get(this, property, options));
@ -116,6 +170,12 @@
}
});
/**
Display logic for numbers.
@method number
@for Handlebars
**/
Handlebars.registerHelper('number', function(property, options) {
var n, orig, title;
orig = parseInt(Ember.Handlebars.get(this, property, options), 10);
@ -128,9 +188,7 @@
number: orig
});
}
/* Round off the thousands to one decimal place
*/
// Round off the thousands to one decimal place
n = orig;
if (orig > 999) {
n = (orig / 1000).toFixed(1) + "K";
@ -138,6 +196,12 @@
return new Handlebars.SafeString("<span class='number' title='" + title + "'>" + n + "</span>");
});
/**
Display logic for dates.
@method date
@for Handlebars
**/
Handlebars.registerHelper('date', function(property, options) {
var displayDate, dt, fiveDaysAgo, fullReadable, humanized, leaveAgo, val;
if (property.hash) {
@ -175,6 +239,12 @@
return new Handlebars.SafeString("<span class='date' title='" + fullReadable + "'>" + displayDate + "</span>");
});
/**
A personalized name for display
@method personalizedName
@for Handlebars
**/
Handlebars.registerHelper('personalizedName', function(property, options) {
var name, username;
name = Ember.Handlebars.get(this, property, options);
@ -187,4 +257,4 @@
return Em.String.i18n('you');
});
}).call(this);

View file

@ -1,9 +1,11 @@
(function() {
/**
Look up a translation for an i18n key in our dictionary.
@method i18n
@for Handlebars
**/
Ember.Handlebars.registerHelper('i18n', function(property, options) {
/* Resolve any properties
*/
// Resolve any properties
var params,
_this = this;
params = options.hash;
@ -16,15 +18,22 @@
/* We always prefix with .js to select exactly what we want passed through to the front end.
*/
/**
Look up a translation for an i18n key in our dictionary.
@method i18n
@for Ember.String
**/
Ember.String.i18n = function(scope, options) {
return I18n.translate("js." + scope, options);
};
/* Bind an i18n count
*/
/**
Set up an i18n binding that will update as a count changes, complete with pluralization.
@method countI18n
@for Handlebars
**/
Ember.Handlebars.registerHelper('countI18n', function(key, options) {
var view;
view = Discourse.View.extend({
@ -47,4 +56,4 @@
};
}
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
This mixin provides `blank` and `present` to determine whether properties are
there, accounting for more cases than just null and undefined.
@ -9,7 +7,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.Presence = Em.Mixin.create({
Discourse.Presence = Em.Mixin.create({
/**
Returns whether a property is blank. It considers empty arrays, string, objects, undefined and null
@ -45,4 +43,4 @@
}
});
}).call(this);

View file

@ -1,24 +1,37 @@
/**
This mixin adds support for being notified every time the browser window
is scrolled.
/* Use this mixin if you want to be notified every time the user scrolls the window
@class Discourse.Scrolling
@extends Ember.Mixin
@namespace Discourse
@module Discourse
**/
Discourse.Scrolling = Em.Mixin.create({
/**
Begin watching for scroll events. They will be called at max every 100ms.
@method bindScrolling
*/
(function() {
window.Discourse.Scrolling = Em.Mixin.create({
bindScrolling: function() {
var onScroll,
_this = this;
onScroll = Discourse.debounce(function() {
return _this.scrolled();
}, 100);
jQuery(document).bind('touchmove.discourse', onScroll);
return jQuery(window).bind('scroll.discourse', onScroll);
onScroll = Discourse.debounce(function() { return _this.scrolled(); }, 100);
$(document).bind('touchmove.discourse', onScroll);
$(window).bind('scroll.discourse', onScroll);
},
/**
Begin watching for scroll events. They will be called at max every 100ms.
@method unbindScrolling
*/
unbindScrolling: function() {
jQuery(window).unbind('scroll.discourse');
return jQuery(document).unbind('touchmove.discourse');
$(window).unbind('scroll.discourse');
$(document).unbind('touchmove.discourse');
}
});
}).call(this);

View file

@ -1,9 +1,14 @@
(function() {
/**
A data model for summarizing actions a user has taken, for example liking a post.
window.Discourse.ActionSummary = Discourse.Model.extend({
/* Description for the action
*/
@class ActionSummary
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.ActionSummary = Discourse.Model.extend({
// Description for the action
description: (function() {
if (this.get('acted')) {
return Em.String.i18n('post.actions.by_you_and_others', {
@ -17,43 +22,37 @@
});
}
}).property('count', 'acted', 'actionType'),
canAlsoAction: (function() {
if (this.get('hidden')) {
return false;
}
if (this.get('hidden')) return false;
return this.get('can_act');
}).property('can_act', 'hidden'),
/* Remove it
*/
// Remove it
removeAction: function() {
this.set('acted', false);
this.set('count', this.get('count') - 1);
this.set('can_act', true);
return this.set('can_undo', false);
},
/* Perform this action
*/
// Perform this action
act: function(opts) {
/* Mark it as acted
*/
// Mark it as acted
var promise,
_this = this;
this.set('acted', true);
this.set('count', this.get('count') + 1);
this.set('can_act', false);
this.set('can_undo', true);
/* Add ourselves to the users who liked it if present
*/
// Add ourselves to the users who liked it if present
if (this.present('users')) {
this.users.pushObject(Discourse.get('currentUser'));
}
/* Create our post action
*/
// Create our post action
promise = new RSVP.Promise();
jQuery.ajax({
url: "/post_actions",
@ -75,14 +74,12 @@
});
return promise;
},
/* Undo this action
*/
// Undo this action
undo: function() {
this.removeAction();
/* Remove our post action
*/
// Remove our post action
return jQuery.ajax({
url: "/post_actions/" + (this.get('post.id')),
type: 'DELETE',
@ -91,6 +88,7 @@
}
});
},
clearFlags: function() {
var _this = this;
return jQuery.ajax({
@ -106,6 +104,7 @@
}
});
},
loadUsers: function() {
var _this = this;
return jQuery.getJSON("/post_actions/users", {
@ -118,6 +117,7 @@
});
});
}
});
}).call(this);

View file

@ -1,15 +1,22 @@
(function() {
/**
A data model for archetypes such as polls, tasks, etc.
@class Archetype
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Archetype = Discourse.Model.extend({
window.Discourse.Archetype = Discourse.Model.extend({
hasOptions: (function() {
if (!this.get('options')) {
return false;
}
if (!this.get('options')) return false;
return this.get('options').length > 0;
}).property('options.@each'),
isDefault: (function() {
return this.get('id') === Discourse.get('site.default_archetype');
}).property('id')
});
}).call(this);

View file

@ -1,36 +1,45 @@
(function() {
/**
A data model that represents a category
@class Category
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Category = Discourse.Model.extend({
window.Discourse.Category = Discourse.Model.extend({
url: (function() {
return "/category/" + (this.get('slug'));
}).property('name'),
style: (function() {
return "background-color: #" + (this.get('color'));
}).property('color'),
moreTopics: (function() {
return this.get('topic_count') > Discourse.SiteSettings.category_featured_topics;
}).property('topic_count'),
save: function(args) {
var url,
_this = this;
url = "/categories";
if (this.get('id')) {
url = "/categories/" + (this.get('id'));
}
return this.ajax(url, {
data: {
name: this.get('name'),
color: this.get('color')
},
type: this.get('id') ? 'PUT' : 'POST',
success: function(result) {
return args.success(result);
},
error: function(errors) {
return args.error(errors);
}
success: function(result) { return args.success(result); },
error: function(errors) { return args.error(errors); }
});
},
"delete": function(callback) {
var _this = this;
return jQuery.ajax("/categories/" + (this.get('slug')), {
@ -40,6 +49,7 @@
}
});
}
});
}).call(this);

View file

@ -1,8 +1,15 @@
(function() {
/**
A data model for containing a list of categories
@class CategoryList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.CategoryList = Discourse.Model.extend({});
window.Discourse.CategoryList.reopenClass({
categoriesFrom: function(result) {
var categories, users;
categories = Em.A();
@ -22,6 +29,7 @@
});
return categories;
},
list: function(filter) {
var promise,
_this = this;
@ -36,6 +44,7 @@
});
return promise;
}
});
}).call(this);

View file

@ -1,34 +1,29 @@
/**
A data model for representing the composer's current state
/* The status the compose view can have
*/
@class Composer
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
(function() {
var CLOSED, CREATE_TOPIC, DRAFT, EDIT, OPEN, PRIVATE_MESSAGE, REPLY, REPLY_AS_NEW_TOPIC_KEY, SAVING;
CLOSED = 'closed';
SAVING = 'saving';
OPEN = 'open';
DRAFT = 'draft';
/* The actions the composer can take
*/
// The actions the composer can take
CREATE_TOPIC = 'createTopic';
PRIVATE_MESSAGE = 'privateMessage';
REPLY = 'reply';
EDIT = 'edit';
REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic";
window.Discourse.Composer = Discourse.Model.extend({
Discourse.Composer = Discourse.Model.extend({
archetypesBinding: 'Discourse.site.archetypes',
init: function() {
var val;
this._super();
@ -36,34 +31,37 @@
this.set('showPreview', val === 'true');
return this.set('archetypeId', Discourse.get('site.default_archetype'));
},
archetypesBinding: 'Discourse.site.archetypes',
creatingTopic: (function() {
return this.get('action') === CREATE_TOPIC;
}).property('action'),
creatingPrivateMessage: (function() {
return this.get('action') === PRIVATE_MESSAGE;
}).property('action'),
editingPost: (function() {
return this.get('action') === EDIT;
}).property('action'),
viewOpen: (function() {
return this.get('composeState') === OPEN;
}).property('composeState'),
archetype: (function() {
return this.get('archetypes').findProperty('id', this.get('archetypeId'));
}).property('archetypeId'),
archetypeChanged: (function() {
return this.set('metaData', Em.Object.create());
}).observes('archetype'),
editTitle: (function() {
if (this.get('creatingTopic') || this.get('creatingPrivateMessage')) {
return true;
}
if (this.get('editingPost') && this.get('post.post_number') === 1) {
return true;
}
if (this.get('creatingTopic') || this.get('creatingPrivateMessage')) return true;
if (this.get('editingPost') && this.get('post.post_number') === 1) return true;
return false;
}).property('editingPost', 'creatingTopic', 'post.post_number'),
togglePreview: function() {
this.toggleProperty('showPreview');
return Discourse.KeyValueStore.set({
@ -71,9 +69,8 @@
value: this.get('showPreview')
});
},
/* Import a quote from the post
*/
// Import a quote from the post
importQuote: function() {
var post, posts,
_this = this;
@ -94,12 +91,12 @@
});
}
},
appendText: function(text) {
return this.set('reply', (this.get('reply') || '') + text);
},
/* Determine the appropriate title for this action
*/
// Determine the appropriate title for this action
actionTitle: (function() {
var postLink, postNumber, replyAvatar, topic, topicLink;
topic = this.get('topic');
@ -136,49 +133,37 @@
});
}
}).property('action', 'post', 'topic', 'topic.title'),
toggleText: (function() {
if (this.get('showPreview')) {
return Em.String.i18n('composer.hide_preview');
}
return Em.String.i18n('composer.show_preview');
}).property('showPreview'),
hidePreview: (function() {
return !this.get('showPreview');
}).property('showPreview'),
/* Whether to disable the post button
*/
// Whether to disable the post button
cantSubmitPost: (function() {
/* Can't submit while loading
*/
if (this.get('loading')) {
return true;
}
/* Title is required on new posts
*/
// Can't submit while loading
if (this.get('loading')) return true;
// Title is required on new posts
if (this.get('creatingTopic')) {
if (this.blank('title')) {
return true;
if (this.blank('title')) return true;
if (this.get('title').trim().length < Discourse.SiteSettings.min_topic_title_length) return true;
}
if (this.get('title').trim().length < Discourse.SiteSettings.min_topic_title_length) {
return true;
}
}
/* Otherwise just reply is required
*/
if (this.blank('reply')) {
return true;
}
if (this.get('reply').trim().length < Discourse.SiteSettings.min_post_length) {
return true;
}
// Otherwise just reply is required
if (this.blank('reply')) return true;
if (this.get('reply').trim().length < Discourse.SiteSettings.min_post_length) return true;
return false;
}).property('reply', 'title', 'creatingTopic', 'loading'),
/* The text for the save button
*/
// The text for the save button
saveText: (function() {
switch (this.get('action')) {
case EDIT:
@ -191,6 +176,7 @@
return Em.String.i18n('composer.create_pm');
}
}).property('action'),
hasMetaData: (function() {
var metaData;
metaData = this.get('metaData');
@ -199,6 +185,7 @@
}
return Em.empty(Em.keys(this.get('metaData')));
}).property('metaData'),
wouldLoseChanges: function() {
return this.get('reply') !== this.get('originalText');
},
@ -212,7 +199,6 @@
topic - The topic we're replying to, if present
quote - If we're opening a reply from a quote, the quote we're making
*/
open: function(opts) {
var replyBlank, topicId,
_this = this;
@ -223,6 +209,7 @@
topicId = opts.topic.get('id');
}
replyBlank = (this.get("reply") || "") === "";
if (!replyBlank &&
(opts.action !== this.get('action') || ((opts.reply || opts.action === this.EDIT) && this.get('reply') !== this.get('originalText'))) &&
!opts.tested) {
@ -232,11 +219,10 @@
});
return;
}
this.set('draftKey', opts.draftKey);
this.set('draftSequence', opts.draftSequence);
if (!opts.draftKey) {
throw 'draft key is required';
}
if (!opts.draftKey) throw 'draft key is required';
if (opts.draftSequence === null) throw 'draft sequence is required';
this.set('composeState', opts.composerState || OPEN);
@ -249,6 +235,7 @@
this.set('topic', opts.post.get('topic'));
}
}
this.set('categoryName', opts.categoryName || this.get('topic.category.name'));
this.set('archetypeId', opts.archetypeId || Discourse.get('site.default_archetype'));
this.set('metaData', opts.metaData ? Em.Object.create(opts.metaData) : null);
@ -260,9 +247,8 @@
return _this.set('loading', false);
});
}
/* If we are editing a post, load it.
*/
// If we are editing a post, load it.
if (opts.action === EDIT && opts.post) {
this.set('title', this.get('topic.title'));
this.set('loading', true);
@ -272,9 +258,11 @@
return _this.set('loading', false);
});
}
if (opts.title) {
this.set('title', opts.title);
}
if (opts.draft) {
this.set('originalText', '');
} else if (opts.reply) {
@ -282,25 +270,21 @@
}
return false;
},
save: function(opts) {
if (this.get('editingPost')) {
return this.editPost(opts);
} else {
return this.createPost(opts);
}
},
/* When you edit a post
*/
save: function(opts) {
if (this.get('editingPost')) return this.editPost(opts);
return this.createPost(opts);
},
// When you edit a post
editPost: function(opts) {
var oldCooked, post, promise, topic,
_this = this;
promise = new RSVP.Promise();
post = this.get('post');
oldCooked = post.get('cooked');
/* Update the title if we've changed it
*/
// Update the title if we've changed it
if (this.get('title') && post.get('post_number') === 1) {
topic = this.get('topic');
topic.set('title', this.get('title'));
@ -309,15 +293,14 @@
}
post.set('raw', this.get('reply'));
post.set('imageSizes', opts.imageSizes);
post.set('cooked', jQuery('#wmd-preview').html());
post.set('cooked', $('#wmd-preview').html());
this.set('composeState', CLOSED);
post.save(function(savedPost) {
var idx, postNumber, posts;
posts = _this.get('topic.posts');
/* perhaps our post came from elsewhere eg. draft
*/
// perhaps our post came from elsewhere eg. draft
idx = -1;
postNumber = post.get('post_number');
posts.each(function(p, i) {
@ -342,9 +325,8 @@
});
return promise;
},
/* Create a new Post
*/
// Create a new Post
createPost: function(opts) {
var addedToStream, createdPost, diff, lastPost, post, promise, topic,
_this = this;
@ -359,7 +341,7 @@
reply_to_post_number: post ? post.get('post_number') : null,
imageSizes: opts.imageSizes,
post_number: this.get('topic.highest_post_number') + 1,
cooked: jQuery('#wmd-preview').html(),
cooked: $('#wmd-preview').html(),
reply_count: 0,
display_username: Discourse.get('currentUser.name'),
username: Discourse.get('currentUser.username'),
@ -372,39 +354,32 @@
newPost: true
});
addedToStream = false;
/* If we're in a topic, we can append the post instantly.
*/
// If we're in a topic, we can append the post instantly.
if (topic) {
/* Increase the reply count
*/
// Increase the reply count
if (post) {
post.set('reply_count', (post.get('reply_count') || 0) + 1);
}
topic.set('posts_count', topic.get('posts_count') + 1);
/* Update last post
*/
// Update last post
topic.set('last_posted_at', new Date());
topic.set('highest_post_number', createdPost.get('post_number'));
topic.set('last_poster', Discourse.get('currentUser'));
/* Set the topic view for the new post
*/
// Set the topic view for the new post
createdPost.set('topic', topic);
createdPost.set('created_at', new Date());
/* If we're near the end of the topic, load new posts
*/
// If we're near the end of the topic, load new posts
lastPost = topic.posts.last();
if (lastPost) {
diff = topic.get('highest_post_number') - lastPost.get('post_number');
/* If the new post is within a threshold of the end of the topic,
*/
/* add it and scroll there instead of adding the link.
*/
// If the new post is within a threshold of the end of the topic,
// add it and scroll there instead of adding the link.
if (diff < 5) {
createdPost.set('scrollToAfterInsert', createdPost.get('post_number'));
@ -413,24 +388,19 @@
}
}
}
/* Save callback
*/
// Save callback
createdPost.save(function(result) {
var addedPost, saving;
addedPost = false;
saving = true;
createdPost.updateFromSave(result);
if (topic) {
/* It's no longer a new post
*/
// It's no longer a new post
createdPost.set('newPost', false);
topic.set('draft_sequence', result.draft_sequence);
} else {
/* We created a new topic, let's show it.
*/
// We created a new topic, let's show it.
_this.set('composeState', CLOSED);
saving = false;
}
@ -455,18 +425,14 @@
});
return promise;
},
saveDraft: function() {
var data,
_this = this;
if (this.get('disableDrafts')) {
return;
}
if (!this.get('reply')) {
return;
}
if (this.get('reply').length < Discourse.SiteSettings.min_post_length) {
return;
}
if (this.get('disableDrafts')) return;
if (!this.get('reply')) return;
if (this.get('reply').length < Discourse.SiteSettings.min_post_length) return;
data = {
reply: this.get('reply'),
action: this.get('action'),
@ -477,13 +443,15 @@
metaData: this.get('metaData'),
usernames: this.get('targetUsernames')
};
this.set('draftStatus', Em.String.i18n('composer.saving_draft_tip'));
return Discourse.Draft.save(this.get('draftKey'), this.get('draftSequence'), data).then((function() {
return _this.set('draftStatus', Em.String.i18n('composer.saved_draft_tip'));
_this.set('draftStatus', Em.String.i18n('composer.saved_draft_tip'));
}), (function() {
return _this.set('draftStatus', 'drafts offline');
_this.set('draftStatus', 'drafts offline');
}));
},
resetDraftStatus: (function() {
var len, reply;
reply = this.get('reply');
@ -500,6 +468,7 @@
return this.set('draftStatus', null);
}
}).observes('reply', 'title'),
blank: function(prop) {
var p;
p = this.get(prop);
@ -508,12 +477,14 @@
});
Discourse.Composer.reopenClass({
open: function(opts) {
var composer;
composer = Discourse.Composer.create();
composer.open(opts);
return composer;
},
loadDraft: function(draftKey, draftSequence, draft, topic) {
var composer;
try {
@ -543,24 +514,21 @@
}
return composer;
},
/* The status the compose view can have
*/
// The status the compose view can have
CLOSED: CLOSED,
SAVING: SAVING,
OPEN: OPEN,
DRAFT: DRAFT,
/* The actions the composer can take
*/
// The actions the composer can take
CREATE_TOPIC: CREATE_TOPIC,
PRIVATE_MESSAGE: PRIVATE_MESSAGE,
REPLY: REPLY,
EDIT: EDIT,
/* Draft key
*/
// Draft key
REPLY_AS_NEW_TOPIC_KEY: REPLY_AS_NEW_TOPIC_KEY
});
}).call(this);

View file

@ -1,8 +1,15 @@
(function() {
/**
A data model representing a draft post
window.Discourse.Draft = Discourse.Model.extend({});
@class Draft
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Draft = Discourse.Model.extend({});
Discourse.Draft.reopenClass({
clear: function(key, sequence) {
return jQuery.ajax({
type: 'DELETE',
@ -12,10 +19,8 @@
sequence: sequence
}
});
/* Discourse.KeyValueStore.remove("draft_#{key}")
*/
},
get: function(key) {
var promise,
_this = this;
@ -32,18 +37,12 @@
});
return promise;
},
getLocal: function(key, current) {
var local;
return current;
/* disabling for now to see if it helps with siracusa issue.
local = Discourse.KeyValueStore.get("draft_" + key);
if (!current || (local && local.length > current.length)) {
return local;
} else {
return current;
}
*/
},
save: function(key, sequence, data) {
var promise;
promise = new RSVP.Promise();
@ -75,6 +74,5 @@
});
return promise;
}
});
}).call(this);
});

View file

@ -1,5 +1,11 @@
(function() {
/**
A trivial model we use to handle input validation
@class InputValidation
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
window.Discourse.InputValidation = Discourse.Model.extend({});
}).call(this);

View file

@ -1,18 +1,26 @@
(function() {
/**
A data model representing an Invite
@class Invite
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Invite = Discourse.Model.extend({
window.Discourse.Invite = Discourse.Model.extend({
rescind: function() {
jQuery.ajax('/invites', {
type: 'DELETE',
data: {
email: this.get('email')
}
data: { email: this.get('email') }
});
return this.set('rescinded', true);
this.set('rescinded', true);
}
});
window.Discourse.Invite.reopenClass({
Discourse.Invite.reopenClass({
create: function(invite) {
var result;
result = this._super(invite);
@ -21,6 +29,7 @@
}
return result;
}
});
}).call(this);

View file

@ -1,12 +1,19 @@
(function() {
/**
A data model representing a list of Invites
window.Discourse.InviteList = Discourse.Model.extend({
@class InviteList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.InviteList = Discourse.Model.extend({
empty: (function() {
return this.blank('pending') && this.blank('redeemed');
}).property('pending.@each', 'redeemed.@each')
});
window.Discourse.InviteList.reopenClass({
Discourse.InviteList.reopenClass({
findInvitedBy: function(user) {
var promise;
promise = new RSVP.Promise();
@ -31,6 +38,7 @@
});
return promise;
}
});
}).call(this);

View file

@ -1,54 +0,0 @@
(function() {
Discourse.Mention = (function() {
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(name, valid) {
localCache[name] = valid;
};
lookupCache = function(name) {
return localCache[name];
};
lookup = function(name, callback) {
var cached;
cached = lookupCache(name);
if (cached === true || cached === false) {
callback(cached);
return false;
} else {
jQuery.get("/users/is_local_username", {
username: name
}, function(r) {
cache(name, r.valid);
return callback(r.valid);
});
return true;
}
};
load = function(e) {
var $elem, loading, username;
$elem = jQuery(e);
if ($elem.data('mention-tested')) {
return;
}
username = $elem.text();
username = username.substr(1);
loading = lookup(username, function(valid) {
if (valid) {
return $elem.replaceWith("<a href='/users/" + (username.toLowerCase()) + "' class='mention'>@" + username + "</a>");
} else {
return $elem.removeClass('mention-loading').addClass('mention-tested');
}
});
if (loading) {
return $elem.addClass('mention-loading');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();
}).call(this);

View file

@ -1,5 +1,3 @@
(function() {
/**
A base object we can use to handle models in the Discourse client application.
@ -9,7 +7,7 @@
@namespace Discourse
@module Discourse
**/
window.Discourse.Model = Ember.Object.extend(Discourse.Presence, {
Discourse.Model = Ember.Object.extend(Discourse.Presence, {
/**
Our own AJAX handler that handles erronous responses
@ -53,7 +51,7 @@
}
});
window.Discourse.Model.reopenClass({
Discourse.Model.reopenClass({
/**
Given an array of values, return them in a hash
@ -77,4 +75,4 @@
}
});
}).call(this);

View file

@ -1,16 +1,16 @@
/**
A data model representing a navigation item on the list views
/* closure wrapping means this does not leak into global context
*/
(function() {
@class InviteList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
var validAnon, validNavNames;
validNavNames = ['read', 'popular', 'categories', 'favorited', 'category', 'unread', 'new', 'posted'];
validAnon = ['popular', 'category', 'categories'];
window.Discourse.NavItem = Discourse.Model.extend({
Discourse.NavItem = Discourse.Model.extend({
categoryName: (function() {
var split;
split = this.get('name').split('/');
@ -35,9 +35,8 @@
});
Discourse.NavItem.reopenClass({
/* create a nav item from the text, will return null if there is not valid nav item for this particular text
*/
// create a nav item from the text, will return null if there is not valid nav item for this particular text
fromText: function(text, opts) {
var countSummary, hasCategories, loggedOn, name, split, testName;
countSummary = opts.countSummary;
@ -46,15 +45,11 @@
split = text.split(",");
name = split[0];
testName = name.split("/")[0];
if (!loggedOn && !validAnon.contains(testName)) {
return null;
}
if (!hasCategories && testName === "categories") {
return null;
}
if (!validNavNames.contains(testName)) {
return null;
}
if (!loggedOn && !validAnon.contains(testName)) return null;
if (!hasCategories && testName === "categories") return null;
if (!validNavNames.contains(testName)) return null;
opts = {
name: name,
hasIcon: name === "unread" || name === "favorited",
@ -67,6 +62,7 @@
}
return Discourse.NavItem.create(opts);
}
});
}).call(this);

View file

@ -1,21 +1,25 @@
(function() {
/**
A data model representing a notification a user receives
@class Notification
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Notification = Discourse.Model.extend({
window.Discourse.Notification = Discourse.Model.extend({
readClass: (function() {
if (this.read) {
return 'read';
} else {
if (this.read) return 'read';
return '';
}
}).property('read'),
url: (function() {
var slug;
if (this.blank('data.topic_title')) {
return "";
}
if (this.blank('data.topic_title')) return "";
slug = this.get('slug');
return "/t/" + slug + "/" + (this.get('topic_id')) + "/" + (this.get('post_number'));
}).property(),
rendered: (function() {
var notificationName;
notificationName = Discourse.get('site.notificationLookup')[this.notification_type];
@ -24,9 +28,10 @@
link: "<a href='" + (this.get('url')) + "'>" + this.data.topic_title + "</a>"
});
}).property()
});
window.Discourse.Notification.reopenClass({
Discourse.Notification.reopenClass({
create: function(obj) {
var result;
result = this._super(obj);
@ -37,4 +42,4 @@
}
});
}).call(this);

View file

@ -1,83 +0,0 @@
(function() {
Discourse.Onebox = (function() {
/* for now it only stores in a var, in future we can change it so it uses localStorage,
*/
/* trouble with localStorage is that expire semantics need some thinking
*/
/*cacheKey = "__onebox__"
*/
var cache, load, localCache, lookup, lookupCache;
localCache = {};
cache = function(url, contents) {
localCache[url] = contents;
return null;
};
lookupCache = function(url) {
var cached;
cached = localCache[url];
if (cached && cached.then) {
return null;
} else {
return cached;
}
};
lookup = function(url, refresh, callback) {
var cached;
cached = localCache[url];
if (refresh && cached && !cached.then) {
cached = null;
}
if (cached) {
if (cached.then) {
cached.then(callback(lookupCache(url)));
} else {
callback(cached);
}
return false;
} else {
cache(url, jQuery.get("/onebox", {
url: url,
refresh: refresh
}, function(html) {
cache(url, html);
return callback(html);
}));
return true;
}
};
load = function(e, refresh) {
var $elem, loading, url;
if (!refresh) refresh = false;
url = e.href;
$elem = jQuery(e);
if ($elem.data('onebox-loaded')) {
return;
}
loading = lookup(url, refresh, function(html) {
$elem.removeClass('loading-onebox');
$elem.data('onebox-loaded');
if (!html) {
return;
}
if (html.trim().length === 0) {
return;
}
return $elem.replaceWith(html);
});
if (loading) {
return $elem.addClass('loading-onebox');
}
};
return {
load: load,
lookup: lookup,
lookupCache: lookupCache
};
})();
}).call(this);

View file

@ -1,41 +1,44 @@
(function() {
/**
A data model representing a post in a topic
window.Discourse.Post = Discourse.Model.extend({
/* Url to this post
*/
@class Post
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Post = Discourse.Model.extend({
url: (function() {
return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number'));
}).property('post_number', 'topic_id', 'topic.slug'),
originalPostUrl: (function() {
return "/t/" + (this.get('topic_id')) + "/" + (this.get('reply_to_post_number'));
}).property('reply_to_post_number'),
showUserReplyTab: (function() {
return this.get('reply_to_user') && (this.get('reply_to_post_number') < (this.get('post_number') - 1));
}).property('reply_to_user', 'reply_to_post_number', 'post_number'),
firstPost: (function() {
if (this.get('bestOfFirst') === true) {
return true;
}
if (this.get('bestOfFirst') === true) return true;
return this.get('post_number') === 1;
}).property('post_number'),
hasHistory: (function() {
return this.get('version') > 1;
}).property('version'),
postElementId: (function() {
return "post_" + (this.get('post_number'));
}).property(),
/*
The class for the read icon of the post. It starts with read-icon then adds 'seen' or
'last-read' if the post has been seen or is the highest post number seen so far respectively.
*/
// The class for the read icon of the post. It starts with read-icon then adds 'seen' or
// 'last-read' if the post has been seen or is the highest post number seen so far respectively.
bookmarkClass: (function() {
var result, topic;
result = 'read-icon';
if (this.get('bookmarked')) {
return result + ' bookmarked';
}
if (this.get('bookmarked')) return result + ' bookmarked';
topic = this.get('topic');
if (topic && topic.get('last_read_post_number') === this.get('post_number')) {
result += ' last-read';
@ -46,23 +49,19 @@
}
return result;
}).property('read', 'topic.last_read_post_number', 'bookmarked'),
/* Custom tooltips for the bookmark icons
*/
// Custom tooltips for the bookmark icons
bookmarkTooltip: (function() {
var topic;
if (this.get('bookmarked')) {
return Em.String.i18n('bookmarks.created');
}
if (!this.get('read')) {
return "";
}
if (this.get('bookmarked')) return Em.String.i18n('bookmarks.created');
if (!this.get('read')) return "";
topic = this.get('topic');
if (topic && topic.get('last_read_post_number') === this.get('post_number')) {
return Em.String.i18n('bookmarks.last_read');
}
return Em.String.i18n('bookmarks.not_bookmarked');
}).property('read', 'topic.last_read_post_number', 'bookmarked'),
bookmarkedChanged: (function() {
var _this = this;
return jQuery.ajax({
@ -79,48 +78,38 @@
}
});
}).observes('bookmarked'),
internalLinks: (function() {
if (this.blank('link_counts')) {
return null;
}
if (this.blank('link_counts')) return null;
return this.get('link_counts').filterProperty('internal').filterProperty('title');
}).property('link_counts.@each.internal'),
/* Edits are the version - 1, so version 2 = 1 edit
*/
// Edits are the version - 1, so version 2 = 1 edit
editCount: (function() {
return this.get('version') - 1;
}).property('version'),
historyHeat: (function() {
var rightNow, updatedAt, updatedAtDate;
if (!(updatedAt = this.get('updated_at'))) {
return;
}
if (!(updatedAt = this.get('updated_at'))) return;
rightNow = new Date().getTime();
/* Show heat on age
*/
// Show heat on age
updatedAtDate = Date.create(updatedAt).getTime();
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 12)) {
return 'heatmap-high';
}
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 24)) {
return 'heatmap-med';
}
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 48)) {
return 'heatmap-low';
}
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 12)) return 'heatmap-high';
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 24)) return 'heatmap-med';
if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 48)) return 'heatmap-low';
}).property('updated_at'),
flagsAvailable: (function() {
var _this = this;
return Discourse.get('site.flagTypes').filter(function(item) {
return _this.get("actionByName." + (item.get('name_key')) + ".can_act");
});
}).property('Discourse.site.flagTypes', 'actions_summary.@each.can_act'),
actionsHistory: (function() {
if (!this.present('actions_summary')) {
return null;
}
if (!this.present('actions_summary')) return null;
return this.get('actions_summary').filter(function(i) {
if (i.get('count') === 0) {
return false;
@ -131,15 +120,12 @@
return !i.get('hidden');
});
}).property('actions_summary.@each.users', 'actions_summary.@each.count'),
/* Save a post and call the callback when done.
*/
// Save a post and call the callback when done.
save: function(complete, error) {
var data, metaData;
if (!this.get('newPost')) {
/* We're updating a post
*/
// We're updating a post
return jQuery.ajax({
url: "/posts/" + (this.get('id')),
type: 'PUT',
@ -148,17 +134,11 @@
image_sizes: this.get('imageSizes')
},
success: function(result) {
console.log(result)
// If we received a category update, update it
if (result.category) Discourse.get('site').updateCategory(result.category);
return typeof complete === "function" ? complete(Discourse.Post.create(result.post)) : void 0;
},
error: function(result) {
return typeof error === "function" ? error(result) : void 0;
}
error: function(result) { return typeof error === "function" ? error(result) : void 0; }
});
} else {
@ -170,14 +150,11 @@
image_sizes: this.get('imageSizes'),
target_usernames: this.get('target_usernames')
};
/* Put the metaData into the request
*/
// Put the metaData into the request
if (metaData = this.get('metaData')) {
data.meta_data = {};
Ember.keys(metaData).forEach(function(key) {
data.meta_data[key] = metaData.get(key);
});
Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); });
}
return jQuery.ajax({
type: 'POST',
@ -192,12 +169,11 @@
});
}
},
recover: function() {
return jQuery.ajax("/posts/" + (this.get('id')) + "/recover", {
type: 'PUT',
cache: false
});
return jQuery.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false });
},
"delete": function(complete) {
return jQuery.ajax("/posts/" + (this.get('id')), {
type: 'DELETE',
@ -206,11 +182,9 @@
}
});
},
/*
Update the properties of this post from an obj, ignoring cooked as we should already
have that rendered.
*/
// Update the properties of this post from an obj, ignoring cooked as we should already
// have that rendered.
updateFromSave: function(obj) {
var lookup,
_this = this;
@ -225,9 +199,8 @@
return _this.set(key, val);
}
});
/* Rebuild actions summary
*/
// Rebuild actions summary
this.set('actions_summary', Em.A());
if (obj.actions_summary) {
lookup = Em.Object.create();
@ -262,6 +235,7 @@
});
return promise;
},
loadVersions: function(callback) {
return jQuery.get("/posts/" + (this.get('id')) + "/versions.json", function(result) {
return callback(result);
@ -272,35 +246,25 @@
showRepliesBelow: (function() {
var reply_count, _ref;
reply_count = this.get('reply_count');
/* We don't show replies if there aren't any
*/
if (reply_count === 0) {
return false;
}
/* Always show replies if the setting `supress_reply_directly_below` is false.
*/
// We don't show replies if there aren't any
if (reply_count === 0) return false;
if (!Discourse.SiteSettings.supress_reply_directly_below) {
return true;
}
/*Always show replies if there's more than one
*/
// Always show replies if the setting `supress_reply_directly_below` is false.
if (!Discourse.SiteSettings.supress_reply_directly_below) return true;
if (reply_count > 1) {
return true;
}
/* If we have *exactly* one reply, we have to consider if it's directly below us
*/
// Always show replies if there's more than one
if (reply_count > 1) return true;
// If we have *exactly* one reply, we have to consider if it's directly below us
if ((_ref = this.get('topic')) ? _ref.isReplyDirectlyBelow(this) : void 0) return false;
if ((_ref = this.get('topic')) ? _ref.isReplyDirectlyBelow(this) : void 0) {
return false;
}
return true;
}).property('reply_count')
});
window.Discourse.Post.reopenClass({
createActionSummary: function(result) {
var lookup;
if (result.actions_summary) {
@ -316,6 +280,7 @@
return result.set('actionByName', lookup);
}
},
create: function(obj, topic) {
var result;
result = this._super(obj);
@ -326,6 +291,7 @@
result.set('topic', topic);
return result;
},
deleteMany: function(posts) {
return jQuery.ajax("/posts/destroy_many", {
type: 'DELETE',
@ -336,18 +302,21 @@
}
});
},
loadVersion: function(postId, version, callback) {
var _this = this;
return jQuery.getJSON("/posts/" + postId + ".json?version=" + version, function(result) {
return callback(Discourse.Post.create(result));
});
},
loadByPostNumber: function(topicId, postId, callback) {
var _this = this;
return jQuery.getJSON("/posts/by_number/" + topicId + "/" + postId + ".json", function(result) {
return callback(Discourse.Post.create(result));
});
},
loadQuote: function(postId) {
var promise,
_this = this;
@ -359,12 +328,14 @@
});
return promise;
},
load: function(postId, callback) {
var _this = this;
return jQuery.getJSON("/posts/" + postId + ".json", function(result) {
return callback(Discourse.Post.create(result));
});
}
});
}).call(this);

View file

@ -1,16 +1,23 @@
(function() {
/**
A data model representing action types (flags, likes) against a Post
@class PostActionType
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.PostActionType = Discourse.Model.extend({
window.Discourse.PostActionType = Discourse.Model.extend({
alsoName: (function() {
if (this.get('is_flag')) {
return Em.String.i18n('post.actions.flag');
}
if (this.get('is_flag')) return Em.String.i18n('post.actions.flag');
return this.get('name');
}).property('is_flag', 'name'),
alsoNameLower: (function() {
var _ref;
return (_ref = this.get('alsoName')) ? _ref.toLowerCase() : void 0;
}).property('alsoName')
});
}).call(this);

View file

@ -1,6 +1,12 @@
(function() {
/**
A data model representing the site (instance of Discourse)
window.Discourse.Site = Discourse.Model.extend({
@class Site
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Site = Discourse.Model.extend({
notificationLookup: (function() {
var result;
@ -30,7 +36,7 @@
}
});
window.Discourse.Site.reopenClass({
Discourse.Site.reopenClass({
create: function(obj) {
var _this = this;
return Object.tap(this._super(obj), function(result) {
@ -57,4 +63,4 @@
}
});
}).call(this);

View file

@ -1,18 +1,24 @@
(function() {
/**
A data model representing a Topic
@class Topic
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.Topic = Discourse.Model.extend({
categoriesBinding: 'Discourse.site.categories',
fewParticipants: (function() {
if (!this.present('participants')) {
return null;
}
if (!this.present('participants')) return null;
return this.get('participants').slice(0, 3);
}).property('participants'),
canConvertToRegular: (function() {
var a;
a = this.get('archetype');
var a = this.get('archetype');
return a !== 'regular' && a !== 'private_message';
}).property('archetype'),
convertArchetype: function(archetype) {
var a;
a = this.get('archetype');
@ -24,22 +30,22 @@
});
}
},
category: (function() {
if (this.get('categories')) {
return this.get('categories').findProperty('name', this.get('categoryName'));
}
}).property('categoryName', 'categories'),
url: (function() {
var slug;
slug = this.get('slug');
var slug = this.get('slug');
if (slug.isBlank()) {
slug = "topic";
}
return "/t/" + slug + "/" + (this.get('id'));
}).property('id', 'slug'),
/* Helper to build a Url with a post number
*/
// Helper to build a Url with a post number
urlForPostNumber: function(postNumber) {
var url;
url = this.get('url');
@ -48,42 +54,35 @@
}
return url;
},
lastReadUrl: (function() {
return this.urlForPostNumber(this.get('last_read_post_number'));
}).property('url', 'last_read_post_number'),
lastPostUrl: (function() {
return this.urlForPostNumber(this.get('highest_post_number'));
}).property('url', 'highest_post_number'),
/* The last post in the topic
*/
// The last post in the topic
lastPost: function() {
return this.get('posts').last();
},
postsChanged: (function() {
var last, posts;
posts = this.get('posts');
last = posts.last();
if (!(last && last.set && !last.lastPost)) {
return;
}
if (!(last && last.set && !last.lastPost)) return;
posts.each(function(p) {
if (p.lastPost) {
return p.set('lastPost', false);
}
if (p.lastPost) return p.set('lastPost', false);
});
last.set('lastPost', true);
return true;
}).observes('posts.@each', 'posts'),
/* The amount of new posts to display. It might be different than what the server
*/
/* tells us if we are still asynchronously flushing our "recently read" data.
*/
/* So take what the browser has seen into consideration.
*/
// The amount of new posts to display. It might be different than what the server
// tells us if we are still asynchronously flushing our "recently read" data.
// So take what the browser has seen into consideration.
displayNewPosts: (function() {
var delta, highestSeen, result;
if (highestSeen = Discourse.get('highestSeenByTopic')[this.get('id')]) {
@ -98,52 +97,41 @@
}
return this.get('new_posts');
}).property('new_posts', 'id'),
/* The coldmap class for the age of the topic
*/
// The coldmap class for the age of the topic
ageCold: (function() {
var createdAt, createdAtDays, daysSinceEpoch, lastPost, nowDays;
if (!(lastPost = this.get('last_posted_at'))) {
return;
}
if (!(createdAt = this.get('created_at'))) {
return;
}
if (!(lastPost = this.get('last_posted_at'))) return;
if (!(createdAt = this.get('created_at'))) return;
daysSinceEpoch = function(dt) {
/* 1000 * 60 * 60 * 24 = days since epoch
*/
// 1000 * 60 * 60 * 24 = days since epoch
return dt.getTime() / 86400000;
};
/* Show heat on age
*/
// Show heat on age
nowDays = daysSinceEpoch(new Date());
createdAtDays = daysSinceEpoch(new Date(createdAt));
if (daysSinceEpoch(new Date(lastPost)) > nowDays - 90) {
if (createdAtDays < nowDays - 60) {
return 'coldmap-high';
}
if (createdAtDays < nowDays - 30) {
return 'coldmap-med';
}
if (createdAtDays < nowDays - 14) {
return 'coldmap-low';
}
if (createdAtDays < nowDays - 60) return 'coldmap-high';
if (createdAtDays < nowDays - 30) return 'coldmap-med';
if (createdAtDays < nowDays - 14) return 'coldmap-low';
}
return null;
}).property('age', 'created_at'),
archetypeObject: (function() {
return Discourse.get('site.archetypes').findProperty('id', this.get('archetype'));
}).property('archetype'),
isPrivateMessage: (function() {
return this.get('archetype') === 'private_message';
}).property('archetype'),
/* Does this topic only have a single post?
*/
// Does this topic only have a single post?
singlePost: (function() {
return this.get('posts_count') === 1;
}).property('posts_count'),
toggleStatus: function(property) {
this.toggleProperty(property);
return jQuery.post("" + (this.get('url')) + "/status", {
@ -152,6 +140,7 @@
enabled: this.get(property) ? 'true' : 'false'
});
},
toggleStar: function() {
var _this = this;
this.toggleProperty('starred');
@ -169,24 +158,19 @@
}
});
},
/* Save any changes we've made to the model
*/
// Save any changes we've made to the model
save: function() {
/* Don't save unless we can
*/
if (!this.get('can_edit')) {
return;
}
// Don't save unless we can
if (!this.get('can_edit')) return;
return jQuery.post(this.get('url'), {
_method: 'put',
title: this.get('title'),
category: this.get('category.name')
});
},
/* Reset our read data for this topic
*/
// Reset our read data for this topic
resetRead: function(callback) {
return jQuery.ajax("/t/" + (this.get('id')) + "/timings", {
type: 'DELETE',
@ -195,9 +179,8 @@
}
});
},
/* Invite a user to this topic
*/
// Invite a user to this topic
inviteUser: function(user) {
return jQuery.ajax({
type: 'POST',
@ -207,9 +190,8 @@
}
});
},
/* Delete this topic
*/
// Delete this topic
"delete": function(callback) {
return jQuery.ajax("/t/" + (this.get('id')), {
type: 'DELETE',
@ -218,47 +200,37 @@
}
});
},
/* Load the posts for this topic
*/
// Load the posts for this topic
loadPosts: function(opts) {
var _this = this;
if (!opts) {
opts = {};
}
/* Load the first post by default
*/
// Load the first post by default
if (!opts.bestOf) {
if (!opts.nearPost) opts.nearPost = 1
}
/* If we already have that post in the DOM, jump to it
*/
if (Discourse.TopicView.scrollTo(this.get('id'), opts.nearPost)) {
return;
}
// If we already have that post in the DOM, jump to it
if (Discourse.TopicView.scrollTo(this.get('id'), opts.nearPost)) return;
return Discourse.Topic.find(this.get('id'), {
nearPost: opts.nearPost,
bestOf: opts.bestOf,
trackVisit: opts.trackVisit
}).then(function(result) {
/* If loading the topic succeeded...
*/
/* Update the slug if different
*/
// If loading the topic succeeded...
// Update the slug if different
var closestPostNumber, lastPost, postDiff;
if (result.slug) {
_this.set('slug', result.slug);
}
/* If we want to scroll to a post that doesn't exist, just pop them to the closest
*/
/* one instead. This is likely happening due to a deleted post.
*/
// If we want to scroll to a post that doesn't exist, just pop them to the closest
// one instead. This is likely happening due to a deleted post.
opts.nearPost = parseInt(opts.nearPost, 10);
closestPostNumber = 0;
postDiff = Number.MAX_VALUE;
@ -273,6 +245,7 @@
}
}
});
opts.nearPost = closestPostNumber;
if (_this.get('participants')) {
_this.get('participants').clear();
@ -293,12 +266,8 @@
ignoreIfChanged: true
});
}
/* Okay this is weird, but let's store the length of the next post
*/
/* when there
*/
// Okay this is weird, but let's store the length of the next post when there
lastPost = null;
result.posts.each(function(p) {
var post;
@ -314,16 +283,16 @@
return _this.set('message', Em.String.i18n('topic.not_found.description'));
});
},
notificationReasonText: (function() {
var locale_string;
locale_string = "topic.notifications.reasons." + this.notification_level;
if (typeof this.notifications_reason_id === 'number') {
locale_string += "_" + this.notifications_reason_id;
}
return Em.String.i18n(locale_string, {
username: Discourse.currentUser.username.toLowerCase()
});
return Em.String.i18n(locale_string, { username: Discourse.currentUser.username.toLowerCase() });
}).property('notifications_reason_id'),
updateNotifications: function(v) {
this.set('notification_level', v);
this.set('notifications_reason_id', null);
@ -335,9 +304,8 @@
}
});
},
/* use to add post to topics protecting from dupes
*/
// use to add post to topics protecting from dupes
pushPosts: function(newPosts) {
var map, posts;
map = {};
@ -351,9 +319,8 @@
}
});
},
/* Is the reply to a post directly below it?
*/
// Is the reply to a post directly below it?
isReplyDirectlyBelow: function(post) {
var postBelow, posts;
posts = this.get('posts');
@ -361,8 +328,8 @@
return;
}
postBelow = posts[posts.indexOf(post) + 1];
/* If the post directly below's reply_to_post_number is our post number, it's
considered directly below. */
// If the post directly below's reply_to_post_number is our post number, it's
// considered directly below.
return (postBelow ? postBelow.get('reply_to_post_number') : void 0) === post.get('post_number');
}
});
@ -374,15 +341,10 @@
REGULAR: 1,
MUTE: 0
},
/* Load a topic, but accepts a set of filters
*/
/* options:
*/
/* onLoad - the callback after the topic is loaded
*/
// Load a topic, but accepts a set of filters
// options:
// onLoad - the callback after the topic is loaded
find: function(topicId, opts) {
var data, promise, url,
_this = this;
@ -400,24 +362,21 @@
if (opts.trackVisit) {
data.track_visit = true;
}
/* Add username filters if we have them
*/
// Add username filters if we have them
if (opts.userFilters && opts.userFilters.length > 0) {
data.username_filters = [];
opts.userFilters.forEach(function(username) {
return data.username_filters.push(username);
});
}
/* Add the best of filter if we have it
*/
// Add the best of filter if we have it
if (opts.bestOf === true) {
data.best_of = true;
}
/* Check the preload store. If not, load it via JSON
*/
// Check the preload store. If not, load it via JSON
promise = new RSVP.Promise();
PreloadStore.get("topic_" + topicId, function() {
return jQuery.getJSON(url + ".json", data);
@ -433,9 +392,8 @@
});
return promise;
},
/* Create a topic from posts
*/
// Create a topic from posts
movePosts: function(topicId, title, postIds) {
return jQuery.ajax("/t/" + topicId + "/move-posts", {
type: 'POST',
@ -445,6 +403,7 @@
}
});
},
create: function(obj, topicView) {
var _this = this;
return Object.tap(this._super(obj), function(result) {
@ -463,6 +422,7 @@
}
});
}
});
}).call(this);

View file

@ -1,6 +1,14 @@
(function() {
/**
A data model representing a list of topics
@class TopicList
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.TopicList = Discourse.Model.extend({
window.Discourse.TopicList = Discourse.Model.extend({
loadMoreTopics: function() {
var moreUrl, promise,
_this = this;
@ -33,6 +41,7 @@
}
return promise;
},
insert: function(json) {
var newTopic;
newTopic = Discourse.TopicList.decodeTopic(json);
@ -43,9 +52,11 @@
newTopic.set('highlightAfterInsert', true);
return this.get('inserted').unshiftObject(newTopic);
}
});
window.Discourse.TopicList.reopenClass({
Discourse.TopicList.reopenClass({
decodeTopic: function(result) {
var categories, topic, users;
categories = this.extractByKey(result.categories, Discourse.Category);
@ -57,10 +68,9 @@
});
return Discourse.Topic.create(topic);
},
topicsFrom: function(result) {
/* Stitch together our side loaded data
*/
topicsFrom: function(result) {
// Stitch together our side loaded data
var categories, topics, users;
categories = this.extractByKey(result.categories, Discourse.Category);
users = this.extractByKey(result.users, Discourse.User);
@ -74,6 +84,7 @@
});
return topics;
},
list: function(menuItem) {
var filter, found, list, promise, topic_list, url;
filter = menuItem.name;
@ -116,4 +127,4 @@
}
});
}).call(this);

View file

@ -1,24 +1,37 @@
(function() {
/**
A data model representing a user on Discourse
@class User
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.User = Discourse.Model.extend({
window.Discourse.User = Discourse.Model.extend({
avatarLarge: (function() {
return Discourse.Utilities.avatarUrl(this.get('username'), 'large', this.get('avatar_template'));
}).property('username'),
avatarSmall: (function() {
return Discourse.Utilities.avatarUrl(this.get('username'), 'small', this.get('avatar_template'));
}).property('username'),
websiteName: (function() {
return this.get('website').split("/")[2];
}).property('website'),
path: (function() {
return "/users/" + (this.get('username_lower'));
}).property('username'),
username_lower: (function() {
return this.get('username').toLowerCase();
}).property('username'),
trustLevel: (function() {
return Discourse.get('site.trust_levels').findProperty('id', this.get('trust_level'));
}).property('trust_level'),
changeUsername: function(newUsername) {
return jQuery.ajax({
url: "/users/" + (this.get('username_lower')) + "/preferences/username",
@ -28,6 +41,7 @@
}
});
},
changeEmail: function(email) {
return jQuery.ajax({
url: "/users/" + (this.get('username_lower')) + "/preferences/email",
@ -37,9 +51,11 @@
}
});
},
copy: function(deep) {
return Discourse.User.create(this.getProperties(Ember.keys(this)));
},
save: function(finished) {
var _this = this;
return jQuery.ajax("/users/" + this.get('username').toLowerCase(), {
@ -53,14 +69,11 @@
'digest_after_days',
'new_topic_duration_minutes'),
type: 'PUT',
success: function() {
return finished(true);
},
error: function() {
return finished(false);
}
success: function() { return finished(true); },
error: function() { return finished(false); }
});
},
changePassword: function(callback) {
var good;
good = false;
@ -71,9 +84,7 @@
username: this.get('username')
},
type: 'POST',
success: function() {
good = true;
},
success: function() { good = true; },
complete: function() {
var message;
message = "error";
@ -84,6 +95,7 @@
}
});
},
filterStream: function(filter) {
if (Discourse.UserAction.statGroups[filter]) {
filter = Discourse.UserAction.statGroups[filter].join(",");
@ -92,6 +104,7 @@
this.set('stream', Em.A());
return this.loadMoreUserActions();
},
loadUserAction: function(id) {
var stream,
_this = this;
@ -117,17 +130,18 @@
}
});
},
loadMoreUserActions: function(callback) {
var stream, url,
_this = this;
stream = this.get('stream');
if (!stream) {
return;
}
if (!stream) return;
url = "/user_actions?offset=" + stream.length + "&user_id=" + (this.get("id"));
if (this.get('streamFilter')) {
url += "&filter=" + (this.get('streamFilter'));
}
return jQuery.ajax({
url: url,
dataType: 'json',
@ -149,12 +163,11 @@
}
});
},
statsCountNonPM: (function() {
var stats, total;
total = 0;
if (!(stats = this.get('stats'))) {
return 0;
}
if (!(stats = this.get('stats'))) return 0;
this.get('stats').each(function(s) {
if (!s.get("isPM")) {
total += parseInt(s.count, 10);
@ -162,12 +175,11 @@
});
return total;
}).property('stats.@each'),
statsExcludingPms: (function() {
var r;
r = [];
if (this.blank('stats')) {
return r;
}
if (this.blank('stats')) return r;
this.get('stats').each(function(s) {
if (!s.get('isPM')) {
return r.push(s);
@ -175,19 +187,17 @@
});
return r;
}).property('stats.@each'),
statsPmsOnly: (function() {
var r;
r = [];
if (this.blank('stats')) {
return r;
}
if (this.blank('stats')) return r;
this.get('stats').each(function(s) {
if (s.get('isPM')) {
return r.push(s);
}
if (s.get('isPM')) return r.push(s);
});
return r;
}).property('stats.@each'),
inboxCount: (function() {
var r;
r = 0;
@ -199,6 +209,7 @@
});
return r;
}).property('stats.@each'),
sentItemsCount: (function() {
var r;
r = 0;
@ -212,7 +223,8 @@
}).property('stats.@each')
});
window.Discourse.User.reopenClass({
Discourse.User.reopenClass({
checkUsername: function(username, email) {
return jQuery.ajax({
url: '/users/check_username',
@ -223,6 +235,7 @@
}
});
},
groupStats: function(stats) {
var g,
_this = this;
@ -260,6 +273,7 @@
return !s;
});
},
find: function(username) {
var promise,
_this = this;
@ -267,9 +281,7 @@
jQuery.ajax({
url: "/users/" + username + '.json',
success: function(json) {
/* todo: decompose to object
*/
// todo: decompose to object
var user;
json.user.stats = _this.groupStats(json.user.stats.map(function(s) {
var obj;
@ -291,6 +303,7 @@
});
return promise;
},
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
return jQuery.ajax({
url: '/users',
@ -307,5 +320,3 @@
});
}
});
}).call(this);

View file

@ -1,22 +1,32 @@
(function() {
/**
A data model representing actions users have taken
@class UserAction
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserAction = Discourse.Model.extend({
window.Discourse.UserAction = Discourse.Model.extend({
postUrl: (function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('post_number'));
}).property(),
replyUrl: (function() {
return Discourse.Utilities.postUrl(this.get('slug'), this.get('topic_id'), this.get('reply_to_post_number'));
}).property(),
isPM: (function() {
var a;
a = this.get('action_type');
var a = this.get('action_type');
return a === Discourse.UserAction.NEW_PRIVATE_MESSAGE || a === Discourse.UserAction.GOT_PRIVATE_MESSAGE;
}).property(),
isPostAction: (function() {
var a;
a = this.get('action_type');
return a === Discourse.UserAction.RESPONSE || a === Discourse.UserAction.POST || a === Discourse.UserAction.NEW_TOPIC;
}).property(),
addChild: function(action) {
var bucket, current, groups, ua;
groups = this.get("childGroups");
@ -56,6 +66,7 @@
current.push(action);
}
},
children: (function() {
var g, rval;
g = this.get("childGroups");
@ -67,14 +78,15 @@
}
return rval;
}).property("childGroups"),
switchToActing: function() {
this.set('username', this.get('acting_username'));
this.set('avatar_template', this.get('acting_avatar_template'));
return this.set('name', this.get('acting_name'));
this.set('name', this.get('acting_name'));
}
});
window.Discourse.UserAction.reopenClass({
Discourse.UserAction.reopenClass({
collapseStream: function(stream) {
var collapse, collapsed, pos, uniq;
collapse = [this.LIKE, this.WAS_LIKED, this.STAR, this.EDIT, this.BOOKMARK];
@ -110,9 +122,8 @@
});
return collapsed;
},
/* in future we should be sending this through from the server
*/
// in future we should be sending this through from the server
LIKE: 1,
WAS_LIKED: 2,
BOOKMARK: 3,
@ -136,4 +147,4 @@
})()
});
}).call(this);

View file

@ -1,6 +1,12 @@
(function() {
/**
A data model representing a group of UserActions
window.Discourse.UserActionGroup = Discourse.Model.extend({
@class UserActionGroup
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserActionGroup = Discourse.Model.extend({
push: function(item) {
if (!this.items) {
this.items = [];
@ -9,4 +15,4 @@
}
});
}).call(this);

View file

@ -1,5 +1,11 @@
(function() {
/**
A data model representing a statistic on a UserAction
@class UserActionStat
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserActionStat = Discourse.Model.extend({});
window.Discourse.UserActionStat = Discourse.Model.extend({});
}).call(this);

Some files were not shown because too many files have changed in this diff Show more