From e461c842537b5c48e6f99fa658236948b850276b Mon Sep 17 00:00:00 2001
From: Robin Ward ";
- } else {
- return "";
+ logoSmall: (function() {
+ var logo;
+ logo = Discourse.SiteSettings.logo_small_url;
+ if (logo && logo.length > 1) {
+ return "
";
+ } else {
+ return "";
+ }
+ }).property(),
+
+ titleChanged: (function() {
+ var title;
+ title = "";
+ if (this.get('title')) {
+ title += "" + (this.get('title')) + " - ";
+ }
+ title += Discourse.SiteSettings.title;
+ $('title').text(title);
+ if (!this.get('hasFocus') && this.get('notify')) {
+ title = "(*) " + title;
+ }
+ // chrome bug workaround see: http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome
+ window.setTimeout((function() {
+ document.title = ".";
+ document.title = title;
+ }), 200);
+ }).observes('title', 'hasFocus', 'notify'),
+
+ currentUserChanged: (function() {
+ var bus, user;
+ bus = Discourse.MessageBus;
+
+ // We don't want to receive any previous user notifications
+ bus.unsubscribe("/notification/*");
+ bus.callbackInterval = Discourse.SiteSettings.anon_polling_interval;
+ bus.enableLongPolling = false;
+ user = this.get('currentUser');
+ if (user) {
+ bus.callbackInterval = Discourse.SiteSettings.polling_interval;
+ bus.enableLongPolling = true;
+ if (user.admin) {
+ bus.subscribe("/flagged_counts", function(data) {
+ return user.set('site_flagged_posts_count', data.total);
+ });
}
- }).property(),
+ return bus.subscribe("/notification/" + user.id, (function(data) {
+ user.set('unread_notifications', data.unread_notifications);
+ return user.set('unread_private_messages', data.unread_private_messages);
+ }), user.notification_channel_position);
+ }
+ }).observes('currentUser'),
+ notifyTitle: function() {
+ return this.set('notify', true);
+ },
- titleChanged: (function() {
- var title;
- title = "";
- if (this.get('title')) {
- title += "" + (this.get('title')) + " - ";
+ // Browser aware replaceState
+ replaceState: function(path) {
+ if (window.history &&
+ window.history.pushState &&
+ window.history.replaceState &&
+ !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)) {
+ if (window.location.pathname !== path) {
+ return history.replaceState({
+ path: path
+ }, null, path);
}
- title += Discourse.SiteSettings.title;
- jQuery('title').text(title);
- if (!this.get('hasFocus') && this.get('notify')) {
- title = "(*) " + title;
- }
- // chrome bug workaround see: http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome
- window.setTimeout((function() {
- document.title = ".";
- document.title = title;
- }), 200);
- }).observes('title', 'hasFocus', 'notify'),
+ }
+ },
- currentUserChanged: (function() {
- var bus, user;
- bus = Discourse.MessageBus;
+ openComposer: function(opts) {
+ // TODO, remove container link
+ var composer = Discourse.__container__.lookup('controller:composer');
+ if (composer) composer.open(opts);
+ },
- // We don't want to receive any previous user notifications
- bus.unsubscribe("/notification/*");
- bus.callbackInterval = Discourse.SiteSettings.anon_polling_interval;
- bus.enableLongPolling = false;
- user = this.get('currentUser');
- if (user) {
- bus.callbackInterval = Discourse.SiteSettings.polling_interval;
- bus.enableLongPolling = true;
- if (user.admin) {
- bus.subscribe("/flagged_counts", function(data) {
- return user.set('site_flagged_posts_count', data.total);
- });
- }
- return bus.subscribe("/notification/" + user.id, (function(data) {
- user.set('unread_notifications', data.unread_notifications);
- return user.set('unread_private_messages', data.unread_private_messages);
- }), user.notification_channel_position);
- }
- }).observes('currentUser'),
- notifyTitle: function() {
- return this.set('notify', true);
- },
+ // Like router.route, but allow full urls rather than relative one
+ // HERE BE HACKS - uses the ember container for now until we can do this nicer.
+ routeTo: function(path) {
+ var newMatches, newTopicId, oldMatches, oldTopicId, opts, router, topicController, topicRegexp;
+ path = path.replace(/https?\:\/\/[^\/]+/, '');
- // Browser aware replaceState
- replaceState: function(path) {
- if (window.history &&
- window.history.pushState &&
- window.history.replaceState &&
- !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)) {
- if (window.location.pathname !== path) {
- return history.replaceState({
- path: path
- }, null, path);
+ // If we're in the same topic, don't push the state
+ topicRegexp = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/;
+ newMatches = topicRegexp.exec(path);
+ newTopicId = newMatches ? newMatches[2] : null;
+ if (newTopicId) {
+ oldMatches = topicRegexp.exec(window.location.pathname);
+ if ((oldTopicId = oldMatches ? oldMatches[2] : void 0) && (oldTopicId === newTopicId)) {
+ Discourse.replaceState(path);
+ topicController = Discourse.__container__.lookup('controller:topic');
+ opts = {
+ trackVisit: false
+ };
+ if (newMatches[3]) {
+ opts.nearPost = newMatches[3];
}
- }
- },
-
- openComposer: function(opts) {
- // TODO, remove container link
- var composer = Discourse.__container__.lookup('controller:composer');
- if (composer) composer.open(opts);
- },
-
- // Like router.route, but allow full urls rather than relative one
- // HERE BE HACKS - uses the ember container for now until we can do this nicer.
- routeTo: function(path) {
- var newMatches, newTopicId, oldMatches, oldTopicId, opts, router, topicController, topicRegexp;
- path = path.replace(/https?\:\/\/[^\/]+/, '');
-
- // If we're in the same topic, don't push the state
- topicRegexp = /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/;
- newMatches = topicRegexp.exec(path);
- newTopicId = newMatches ? newMatches[2] : null;
- if (newTopicId) {
- oldMatches = topicRegexp.exec(window.location.pathname);
- if ((oldTopicId = oldMatches ? oldMatches[2] : void 0) && (oldTopicId === newTopicId)) {
- Discourse.replaceState(path);
- topicController = Discourse.__container__.lookup('controller:topic');
- opts = {
- trackVisit: false
- };
- if (newMatches[3]) {
- opts.nearPost = newMatches[3];
- }
- topicController.get('content').loadPosts(opts);
- return;
- }
- }
- // Be wary of looking up the router. In this case, we have links in our
- // HTML, say form compiled markdown posts, that need to be routed.
- router = Discourse.__container__.lookup('router:main');
- router.router.updateURL(path);
- return router.handleURL(path);
- },
-
- // The classes of buttons to show on a post
- postButtons: (function() {
- return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
- return "" + (i.replace(/\+/, '').capitalize());
- });
- }).property('Discourse.SiteSettings.post_menu'),
-
- bindDOMEvents: function() {
- var $html, hasTouch,
- _this = this;
- $html = jQuery('html');
-
- /* Add the discourse touch event */
- hasTouch = false;
- if ($html.hasClass('touch')) {
- hasTouch = true;
- }
- if (Modernizr.prefixed("MaxTouchPoints", navigator) > 1) {
- hasTouch = true;
- }
- if (hasTouch) {
- $html.addClass('discourse-touch');
- this.touch = true;
- this.hasTouch = true;
-
- $LAB.script(assetPath('defer/fastclick'))
- .wait(function(){
- // work around jshint hating side-effects
- // its just the way the FastClick api is
- var ignore = new FastClick(document.body);
- });
-
- } else {
- $html.addClass('discourse-no-touch');
- this.touch = false;
- }
- jQuery('#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) {
- var $currentTarget, href;
- if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
- return;
- }
- $currentTarget = jQuery(e.currentTarget);
- href = $currentTarget.attr('href');
- if (href === void 0) {
- return;
- }
- if (href === '#') {
- return;
- }
- if ($currentTarget.attr('target')) {
- return;
- }
- if ($currentTarget.data('auto-route')) {
- return;
- }
- if ($currentTarget.hasClass('lightbox')) {
- return;
- }
- if (href.indexOf("mailto:") === 0) {
- return;
- }
- if (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i"))) {
- return;
- }
- e.preventDefault();
- _this.routeTo(href);
- return false;
- });
- return jQuery(window).focus(function() {
- _this.set('hasFocus', true);
- return _this.set('notify', false);
- }).blur(function() {
- return _this.set('hasFocus', false);
- });
- },
- logout: function() {
- var username,
- _this = this;
- username = this.get('currentUser.username');
- Discourse.KeyValueStore.abandonLocal();
- return jQuery.ajax("/session/" + username, {
- type: 'DELETE',
- success: function(result) {
- /* To keep lots of our variables unbound, we can handle a redirect on logging out.
- */
- return window.location.reload();
- }
- });
- },
- /* fancy probes in ember
- */
-
- insertProbes: function() {
- var topLevel;
- if (typeof console === "undefined" || console === null) {
+ topicController.get('content').loadPosts(opts);
return;
}
- topLevel = function(fn, name) {
- return window.probes.measure(fn, {
- name: name,
- before: function(data, owner, args) {
- if (owner) {
- return window.probes.clear();
- }
- },
- after: function(data, owner, args) {
- var ary, f, n, v, _ref;
- if (owner && data.time > 10) {
- f = function(name, data) {
- if (data && data.count) {
- return "" + name + " - " + data.count + " calls " + ((data.time + 0.0).toFixed(2)) + "ms";
- }
- };
- if (console && console.group) {
- console.group(f(name, data));
- } else {
- console.log("");
- console.log(f(name, data));
+ }
+ // Be wary of looking up the router. In this case, we have links in our
+ // HTML, say form compiled markdown posts, that need to be routed.
+ router = Discourse.__container__.lookup('router:main');
+ router.router.updateURL(path);
+ return router.handleURL(path);
+ },
+
+ // The classes of buttons to show on a post
+ postButtons: (function() {
+ return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
+ return "" + (i.replace(/\+/, '').capitalize());
+ });
+ }).property('Discourse.SiteSettings.post_menu'),
+
+ bindDOMEvents: function() {
+ var $html, hasTouch,
+ _this = this;
+ $html = $('html');
+
+ /* Add the discourse touch event */
+ hasTouch = false;
+ if ($html.hasClass('touch')) {
+ hasTouch = true;
+ }
+ if (Modernizr.prefixed("MaxTouchPoints", navigator) > 1) {
+ hasTouch = true;
+ }
+ if (hasTouch) {
+ $html.addClass('discourse-touch');
+ this.touch = true;
+ this.hasTouch = true;
+
+ $LAB.script(assetPath('defer/fastclick'))
+ .wait(function(){
+ // work around jshint hating side-effects
+ // its just the way the FastClick api is
+ var ignore = new FastClick(document.body);
+ });
+
+ } else {
+ $html.addClass('discourse-no-touch');
+ this.touch = false;
+ }
+ $('#main').on('click.discourse', '[data-not-implemented=true]', function(e) {
+ e.preventDefault();
+ alert(Em.String.i18n('not_implemented'));
+ return false;
+ });
+ $('#main').on('click.discourse', 'a', function(e) {
+ var $currentTarget, href;
+ if (e.isDefaultPrevented() || e.metaKey || e.ctrlKey) {
+ return;
+ }
+ $currentTarget = $(e.currentTarget);
+ href = $currentTarget.attr('href');
+ if (href === void 0) {
+ return;
+ }
+ if (href === '#') {
+ return;
+ }
+ if ($currentTarget.attr('target')) {
+ return;
+ }
+ if ($currentTarget.data('auto-route')) {
+ return;
+ }
+ if ($currentTarget.hasClass('lightbox')) {
+ return;
+ }
+ if (href.indexOf("mailto:") === 0) {
+ return;
+ }
+ if (href.match(/^http[s]?:\/\//i) && !href.match(new RegExp("^http:\\/\\/" + window.location.hostname, "i"))) {
+ return;
+ }
+ e.preventDefault();
+ _this.routeTo(href);
+ return false;
+ });
+ return $(window).focus(function() {
+ _this.set('hasFocus', true);
+ return _this.set('notify', false);
+ }).blur(function() {
+ return _this.set('hasFocus', false);
+ });
+ },
+ logout: function() {
+ var username,
+ _this = this;
+ username = this.get('currentUser.username');
+ Discourse.KeyValueStore.abandonLocal();
+ return jQuery.ajax("/session/" + username, {
+ type: 'DELETE',
+ success: function(result) {
+ /* To keep lots of our variables unbound, we can handle a redirect on logging out.
+ */
+ return window.location.reload();
+ }
+ });
+ },
+ /* fancy probes in ember
+ */
+
+ insertProbes: function() {
+ var topLevel;
+ if (typeof console === "undefined" || console === null) {
+ return;
+ }
+ topLevel = function(fn, name) {
+ return window.probes.measure(fn, {
+ name: name,
+ before: function(data, owner, args) {
+ if (owner) {
+ return window.probes.clear();
+ }
+ },
+ after: function(data, owner, args) {
+ var ary, f, n, v, _ref;
+ if (owner && data.time > 10) {
+ f = function(name, data) {
+ if (data && data.count) {
+ return "" + name + " - " + data.count + " calls " + ((data.time + 0.0).toFixed(2)) + "ms";
}
- ary = [];
- _ref = window.probes;
- for (n in _ref) {
- v = _ref[n];
- if (n === name || v.time < 1) {
- continue;
- }
- ary.push({
- k: n,
- v: v
+ };
+ if (console && console.group) {
+ console.group(f(name, data));
+ } else {
+ console.log("");
+ console.log(f(name, data));
+ }
+ ary = [];
+ _ref = window.probes;
+ for (n in _ref) {
+ v = _ref[n];
+ if (n === name || v.time < 1) {
+ continue;
+ }
+ ary.push({
+ k: n,
+ v: v
+ });
+ }
+ ary.sortBy(function(item) {
+ if (item.v && item.v.time) {
+ return -item.v.time;
+ } else {
+ return 0;
+ }
+ }).each(function(item) {
+ var output = f("" + item.k, item.v);
+ if (output) {
+ return console.log(output);
+ }
+ });
+ if (typeof console !== "undefined" && console !== null) {
+ if (typeof console.groupEnd === "function") {
+ console.groupEnd();
+ }
+ }
+ return window.probes.clear();
+ }
+ }
+ });
+ };
+ Ember.View.prototype.renderToBuffer = window.probes.measure(Ember.View.prototype.renderToBuffer, "renderToBuffer");
+ Discourse.routeTo = topLevel(Discourse.routeTo, "Discourse.routeTo");
+ Ember.run.end = topLevel(Ember.run.end, "Ember.run.end");
+ },
+ authenticationComplete: function(options) {
+ // TODO, how to dispatch this to the view without the container?
+ var loginView;
+ loginView = Discourse.__container__.lookup('controller:modal').get('currentView');
+ return loginView.authenticationComplete(options);
+ },
+ buildRoutes: function(builder) {
+ var oldBuilder;
+ oldBuilder = Discourse.routeBuilder;
+ Discourse.routeBuilder = function() {
+ if (oldBuilder) {
+ oldBuilder.call(this);
+ }
+ return builder.call(this);
+ };
+ },
+ start: function() {
+ this.bindDOMEvents();
+ Discourse.SiteSettings = PreloadStore.getStatic('siteSettings');
+ Discourse.MessageBus.start();
+ Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus);
+ Discourse.insertProbes();
+
+ // subscribe to any site customizations that are loaded
+ $('link.custom-css').each(function() {
+ var id, split, stylesheet,
+ _this = this;
+ split = this.href.split("/");
+ id = split[split.length - 1].split(".css")[0];
+ stylesheet = this;
+ return Discourse.MessageBus.subscribe("/file-change/" + id, function(data) {
+ var orig, sp;
+ if (!$(stylesheet).data('orig')) {
+ $(stylesheet).data('orig', stylesheet.href);
+ }
+ orig = $(stylesheet).data('orig');
+ sp = orig.split(".css?");
+ stylesheet.href = sp[0] + ".css?" + data;
+ });
+ });
+ $('header.custom').each(function() {
+ var header;
+ header = $(this);
+ return Discourse.MessageBus.subscribe("/header-change/" + ($(this).data('key')), function(data) {
+ return header.html(data);
+ });
+ });
+
+ // possibly move this to dev only
+ return Discourse.MessageBus.subscribe("/file-change", function(data) {
+ Ember.TEMPLATES.empty = Handlebars.compile("");
+ return data.each(function(me) {
+ var js;
+ if (me === "refresh") {
+ return document.location.reload(true);
+ } else if (me.name.substr(-10) === "handlebars") {
+ js = me.name.replace(".handlebars", "").replace("app/assets/javascripts", "/assets");
+ return $LAB.script(js + "?hash=" + me.hash).wait(function() {
+ var templateName;
+ templateName = js.replace(".js", "").replace("/assets/", "");
+ return jQuery.each(Ember.View.views, function() {
+ var _this = this;
+ if (this.get('templateName') === templateName) {
+ this.set('templateName', 'empty');
+ this.rerender();
+ return Em.run.next(function() {
+ _this.set('templateName', templateName);
+ return _this.rerender();
});
}
- ary.sortBy(function(item) {
- if (item.v && item.v.time) {
- return -item.v.time;
- } else {
- return 0;
- }
- }).each(function(item) {
- var output = f("" + item.k, item.v);
- if (output) {
- return console.log(output);
- }
- });
- if (typeof console !== "undefined" && console !== null) {
- if (typeof console.groupEnd === "function") {
- console.groupEnd();
- }
+ });
+ });
+ } else {
+ return $('link').each(function() {
+ if (this.href.match(me.name) && me.hash) {
+ if (!$(this).data('orig')) {
+ $(this).data('orig', this.href);
}
- return window.probes.clear();
+ this.href = $(this).data('orig') + "&hash=" + me.hash;
}
- }
- });
- };
- Ember.View.prototype.renderToBuffer = window.probes.measure(Ember.View.prototype.renderToBuffer, "renderToBuffer");
- Discourse.routeTo = topLevel(Discourse.routeTo, "Discourse.routeTo");
- Ember.run.end = topLevel(Ember.run.end, "Ember.run.end");
- },
- authenticationComplete: function(options) {
- // TODO, how to dispatch this to the view without the container?
- var loginView;
- loginView = Discourse.__container__.lookup('controller:modal').get('currentView');
- return loginView.authenticationComplete(options);
- },
- buildRoutes: function(builder) {
- var oldBuilder;
- oldBuilder = Discourse.routeBuilder;
- Discourse.routeBuilder = function() {
- if (oldBuilder) {
- oldBuilder.call(this);
+ });
}
- return builder.call(this);
- };
- },
- start: function() {
- this.bindDOMEvents();
- Discourse.SiteSettings = PreloadStore.getStatic('siteSettings');
- Discourse.MessageBus.start();
- Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus);
- Discourse.insertProbes();
-
- // subscribe to any site customizations that are loaded
- jQuery('link.custom-css').each(function() {
- var id, split, stylesheet,
- _this = this;
- split = this.href.split("/");
- id = split[split.length - 1].split(".css")[0];
- 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);
- }
- orig = jQuery(stylesheet).data('orig');
- sp = orig.split(".css?");
- stylesheet.href = sp[0] + ".css?" + data;
- });
- });
- jQuery('header.custom').each(function() {
- var header;
- header = jQuery(this);
- return Discourse.MessageBus.subscribe("/header-change/" + (jQuery(this).data('key')), function(data) {
- return header.html(data);
- });
});
+ });
+ }
+});
- // possibly move this to dev only
- return Discourse.MessageBus.subscribe("/file-change", function(data) {
- Ember.TEMPLATES.empty = Handlebars.compile("");
- return data.each(function(me) {
- var js;
- if (me === "refresh") {
- return document.location.reload(true);
- } else if (me.name.substr(-10) === "handlebars") {
- js = me.name.replace(".handlebars", "").replace("app/assets/javascripts", "/assets");
- return $LAB.script(js + "?hash=" + me.hash).wait(function() {
- var templateName;
- templateName = js.replace(".js", "").replace("/assets/", "");
- return jQuery.each(Ember.View.views, function() {
- var _this = this;
- if (this.get('templateName') === templateName) {
- this.set('templateName', 'empty');
- this.rerender();
- return Em.run.next(function() {
- _this.set('templateName', templateName);
- return _this.rerender();
- });
- }
- });
- });
- } else {
- return jQuery('link').each(function() {
- if (this.href.match(me.name) && me.hash) {
- if (!jQuery(this).data('orig')) {
- jQuery(this).data('orig', this.href);
- }
- this.href = jQuery(this).data('orig') + "&hash=" + me.hash;
- }
- });
- }
- });
- });
- }
- });
+Discourse.Router = Discourse.Router.reopen({
+ location: 'discourse_location'
+});
- window.Discourse.Router = Discourse.Router.reopen({
- location: 'discourse_location'
- });
+// since we have no jquery-rails these days, hook up csrf token
+csrf_token = $('meta[name=csrf-token]').attr('content');
- // since we have no jquery-rails these days, hook up csrf token
- csrf_token = jQuery('meta[name=csrf-token]').attr('content');
+jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
+ if (!options.crossDomain) {
+ xhr.setRequestHeader('X-CSRF-Token', csrf_token);
+ }
+});
- jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
- if (!options.crossDomain) {
- xhr.setRequestHeader('X-CSRF-Token', csrf_token);
- }
- });
-}).call(this);
diff --git a/app/assets/javascripts/discourse/components/autocomplete.js b/app/assets/javascripts/discourse/components/autocomplete.js
index bd3a56054d0..70c8c29a0b2 100644
--- a/app/assets/javascripts/discourse/components/autocomplete.js
+++ b/app/assets/javascripts/discourse/components/autocomplete.js
@@ -1,313 +1,317 @@
-(function() {
+/**
+ This is a jQuery plugin to support autocompleting values in our text fields.
- (function($) {
- var template;
- template = null;
- $.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 (options && options.cancel && this.data("closeAutocomplete")) {
- this.data("closeAutocomplete")();
- return this;
- }
- if (this.length !== 1) {
- alert("only supporting one matcher at the moment");
- }
- autocompleteOptions = null;
- selectedOption = null;
- completeStart = null;
- completeEnd = null;
- me = this;
- div = null;
- /* input is handled differently
- */
+ @module $.fn.autocomplete
+**/
+$.fn.autocomplete = function(options) {
- isInput = this[0].tagName === "INPUT";
- inputSelectedItems = [];
- addInputSelectedItem = function(item) {
- var d, prev, transformed;
- if (options.transformComplete) {
- transformed = options.transformComplete(item);
+ 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 (options && options.cancel && this.data("closeAutocomplete")) {
+ this.data("closeAutocomplete")();
+ return this;
+ }
+ if (this.length !== 1) {
+ alert("only supporting one matcher at the moment");
+ }
+ autocompleteOptions = null;
+ selectedOption = null;
+ completeStart = null;
+ completeEnd = null;
+ me = this;
+ div = null;
+
+ // 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 = $("");
+ prev = me.parent().find('.item:last');
+ if (prev.length === 0) {
+ me.parent().prepend(d);
+ } else {
+ prev.after(d);
+ }
+ inputSelectedItems.push(item);
+ if (options.onChangeItems) {
+ options.onChangeItems(inputSelectedItems);
+ }
+ return d.find('a').click(function() {
+ closeAutocomplete();
+ inputSelectedItems.splice(jQuery.inArray(item), 1);
+ $(this).parent().parent().remove();
+ if (options.onChangeItems) {
+ return options.onChangeItems(inputSelectedItems);
+ }
+ });
+ };
+
+ if (isInput) {
+ width = this.width();
+ height = this.height();
+ wrap = this.wrap("").parent();
+ wrap.width(width);
+ this.width(80);
+ this.attr('name', this.attr('name') + "-renamed");
+ vals = this.val().split(",");
+ vals.each(function(x) {
+ if (x !== "") {
+ if (options.reverseTransform) {
+ x = options.reverseTransform(x);
}
- d = jQuery("");
- prev = me.parent().find('.item:last');
- if (prev.length === 0) {
- me.parent().prepend(d);
- } else {
- prev.after(d);
- }
- inputSelectedItems.push(item);
- if (options.onChangeItems) {
- options.onChangeItems(inputSelectedItems);
- }
- return d.find('a').click(function() {
- closeAutocomplete();
- inputSelectedItems.splice(jQuery.inArray(item), 1);
- jQuery(this).parent().parent().remove();
- if (options.onChangeItems) {
- return options.onChangeItems(inputSelectedItems);
- }
- });
+ return addInputSelectedItem(x);
+ }
+ });
+ this.val("");
+ completeStart = 0;
+ wrap.click(function() {
+ _this.focus();
+ return true;
+ });
+ }
+
+ markSelected = function() {
+ var links;
+ links = div.find('li a');
+ links.removeClass('selected');
+ return $(links[selectedOption]).addClass('selected');
+ };
+
+ renderAutocomplete = function() {
+ var borderTop, mePos, pos, ul;
+ if (div) {
+ div.hide().remove();
+ }
+ if (autocompleteOptions.length === 0) {
+ return;
+ }
+ div = $(options.template({
+ options: autocompleteOptions
+ }));
+ ul = div.find('ul');
+ selectedOption = 0;
+ markSelected();
+ ul.find('li').click(function() {
+ selectedOption = ul.find('li').index(this);
+ completeTerm(autocompleteOptions[selectedOption]);
+ return false;
+ });
+ pos = null;
+ if (isInput) {
+ pos = {
+ left: 0,
+ top: 0
};
+ } else {
+ pos = me.caretPosition({
+ pos: completeStart,
+ key: options.key
+ });
+ }
+ div.css({
+ left: "-1000px"
+ });
+ me.parent().append(div);
+ mePos = me.position();
+ borderTop = parseInt(me.css('border-top-width'), 10) || 0;
+ return div.css({
+ position: 'absolute',
+ top: (mePos.top + pos.top - div.height() + borderTop) + 'px',
+ left: (mePos.left + pos.left + 27) + 'px'
+ });
+ };
+
+ updateAutoComplete = function(r) {
+ if (completeStart === null) return;
+
+ autocompleteOptions = r;
+ if (!r || r.length === 0) {
+ return closeAutocomplete();
+ } else {
+ return renderAutocomplete();
+ }
+ };
+
+ closeAutocomplete = function() {
+ if (div) {
+ div.hide().remove();
+ }
+ div = null;
+ completeStart = null;
+ autocompleteOptions = null;
+ };
+
+ // chain to allow multiples
+ oldClose = me.data("closeAutocomplete");
+ me.data("closeAutocomplete", function() {
+ if (oldClose) {
+ oldClose();
+ }
+ return closeAutocomplete();
+ });
+
+ completeTerm = function(term) {
+ var text;
+ if (term) {
if (isInput) {
- width = this.width();
- height = this.height();
- wrap = this.wrap("").parent();
- wrap.width(width);
- this.width(80);
- this.attr('name', this.attr('name') + "-renamed");
- vals = this.val().split(",");
- vals.each(function(x) {
- if (x !== "") {
- if (options.reverseTransform) {
- x = options.reverseTransform(x);
- }
- return addInputSelectedItem(x);
- }
- });
- this.val("");
- completeStart = 0;
- wrap.click(function() {
- _this.focus();
- return true;
- });
+ me.val("");
+ addInputSelectedItem(term);
+ } else {
+ if (options.transformComplete) {
+ term = options.transformComplete(term);
+ }
+ text = me.val();
+ text = text.substring(0, completeStart) + (options.key || "") + term + ' ' + text.substring(completeEnd + 1, text.length);
+ me.val(text);
+ Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length);
}
- markSelected = function() {
- var links;
- links = div.find('li a');
- links.removeClass('selected');
- return jQuery(links[selectedOption]).addClass('selected');
- };
- renderAutocomplete = function() {
- var borderTop, mePos, pos, ul;
- if (div) {
- div.hide().remove();
+ }
+ return closeAutocomplete();
+ };
+
+ $(this).keypress(function(e) {
+ var caretPosition, prevChar, term;
+ if (!options.key) {
+ return;
+ }
+ /* keep hunting backwards till you hit a
+ */
+
+ if (e.which === options.key.charCodeAt(0)) {
+ caretPosition = Discourse.Utilities.caretPosition(me[0]);
+ prevChar = me.val().charAt(caretPosition - 1);
+ if (!prevChar || /\s/.test(prevChar)) {
+ completeStart = completeEnd = caretPosition;
+ term = "";
+ options.dataSource(term, updateAutoComplete);
+ }
+ }
+ });
+
+ return $(this).keydown(function(e) {
+ var c, caretPosition, i, initial, next, nextIsGood, prev, prevIsGood, stopFound, term, total, userToComplete;
+ if (!options.key) {
+ completeStart = 0;
+ }
+ if (e.which === 16) {
+ return;
+ }
+ if ((completeStart === null) && e.which === 8 && options.key) {
+ c = Discourse.Utilities.caretPosition(me[0]);
+ next = me[0].value[c];
+ nextIsGood = next === void 0 || /\s/.test(next);
+ c -= 1;
+ initial = c;
+ prevIsGood = true;
+ while (prevIsGood && c >= 0) {
+ c -= 1;
+ prev = me[0].value[c];
+ stopFound = prev === options.key;
+ if (stopFound) {
+ prev = me[0].value[c - 1];
+ if (!prev || /\s/.test(prev)) {
+ completeStart = c;
+ caretPosition = completeEnd = initial;
+ term = me[0].value.substring(c + 1, initial);
+ options.dataSource(term, updateAutoComplete);
+ return true;
+ }
}
- if (autocompleteOptions.length === 0) {
- return;
- }
- div = jQuery(options.template({
- options: autocompleteOptions
- }));
- ul = div.find('ul');
- selectedOption = 0;
- markSelected();
- ul.find('li').click(function() {
- selectedOption = ul.find('li').index(this);
- completeTerm(autocompleteOptions[selectedOption]);
- return false;
- });
- pos = null;
- if (isInput) {
- pos = {
- left: 0,
- top: 0
- };
- } else {
- pos = me.caretPosition({
- pos: completeStart,
- key: options.key
- });
- }
- div.css({
- left: "-1000px"
- });
- me.parent().append(div);
- mePos = me.position();
- borderTop = parseInt(me.css('border-top-width'), 10) || 0;
- return div.css({
- position: 'absolute',
- top: (mePos.top + pos.top - div.height() + borderTop) + 'px',
- left: (mePos.left + pos.left + 27) + 'px'
- });
- };
- updateAutoComplete = function(r) {
- if (completeStart === null) return;
-
- autocompleteOptions = r;
- if (!r || r.length === 0) {
- return closeAutocomplete();
- } else {
- return renderAutocomplete();
- }
- };
- closeAutocomplete = function() {
- if (div) {
- div.hide().remove();
- }
- div = null;
- completeStart = null;
- autocompleteOptions = null;
- };
- /* chain to allow multiples
+ prevIsGood = /[a-zA-Z\.]/.test(prev);
+ }
+ }
+ if (e.which === 27) {
+ if (completeStart !== null) {
+ closeAutocomplete();
+ return false;
+ }
+ return true;
+ }
+ if (completeStart !== null) {
+ caretPosition = Discourse.Utilities.caretPosition(me[0]);
+ /* If we've backspaced past the beginning, cancel unless no key
*/
- oldClose = me.data("closeAutocomplete");
- me.data("closeAutocomplete", function() {
- if (oldClose) {
- oldClose();
- }
- return closeAutocomplete();
- });
- completeTerm = function(term) {
- var text;
- if (term) {
- if (isInput) {
- me.val("");
- addInputSelectedItem(term);
+ if (caretPosition <= completeStart && options.key) {
+ closeAutocomplete();
+ return false;
+ }
+ /* Keyboard codes! So 80's.
+ */
+
+ switch (e.which) {
+ case 13:
+ case 39:
+ case 9:
+ if (!autocompleteOptions) {
+ return true;
+ }
+ if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) {
+ completeTerm(userToComplete);
} else {
- if (options.transformComplete) {
- term = options.transformComplete(term);
- }
- text = me.val();
- text = text.substring(0, completeStart) + (options.key || "") + term + ' ' + text.substring(completeEnd + 1, text.length);
- me.val(text);
- Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length);
- }
- }
- return closeAutocomplete();
- };
- jQuery(this).keypress(function(e) {
- var caretPosition, prevChar, term;
- if (!options.key) {
- return;
- }
- /* keep hunting backwards till you hit a
- */
+ /* We're cancelling it, really.
+ */
- if (e.which === options.key.charCodeAt(0)) {
- caretPosition = Discourse.Utilities.caretPosition(me[0]);
- prevChar = me.val().charAt(caretPosition - 1);
- if (!prevChar || /\s/.test(prevChar)) {
- completeStart = completeEnd = caretPosition;
- term = "";
- options.dataSource(term, updateAutoComplete);
+ return true;
}
- }
- });
- return jQuery(this).keydown(function(e) {
- var c, caretPosition, i, initial, next, nextIsGood, prev, prevIsGood, stopFound, term, total, userToComplete;
- if (!options.key) {
- completeStart = 0;
- }
- if (e.which === 16) {
- return;
- }
- if ((completeStart === null) && e.which === 8 && options.key) {
- c = Discourse.Utilities.caretPosition(me[0]);
- next = me[0].value[c];
- nextIsGood = next === void 0 || /\s/.test(next);
- c -= 1;
- initial = c;
- prevIsGood = true;
- while (prevIsGood && c >= 0) {
- c -= 1;
- prev = me[0].value[c];
- stopFound = prev === options.key;
- if (stopFound) {
- prev = me[0].value[c - 1];
- if (!prev || /\s/.test(prev)) {
- completeStart = c;
- caretPosition = completeEnd = initial;
- term = me[0].value.substring(c + 1, initial);
- options.dataSource(term, updateAutoComplete);
- return true;
+ closeAutocomplete();
+ return false;
+ case 38:
+ selectedOption = selectedOption - 1;
+ if (selectedOption < 0) {
+ selectedOption = 0;
+ }
+ markSelected();
+ return false;
+ case 40:
+ total = autocompleteOptions.length;
+ selectedOption = selectedOption + 1;
+ if (selectedOption >= total) {
+ selectedOption = total - 1;
+ }
+ if (selectedOption < 0) {
+ selectedOption = 0;
+ }
+ markSelected();
+ return false;
+ default:
+ /* otherwise they're typing - let's search for it!
+ */
+
+ completeEnd = caretPosition;
+ if (e.which === 8) {
+ caretPosition--;
+ }
+ if (caretPosition < 0) {
+ closeAutocomplete();
+ if (isInput) {
+ i = wrap.find('a:last');
+ if (i) {
+ i.click();
}
}
- prevIsGood = /[a-zA-Z\.]/.test(prev);
- }
- }
- if (e.which === 27) {
- if (completeStart !== null) {
- closeAutocomplete();
return false;
}
+ term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition);
+ if (e.which > 48 && e.which < 90) {
+ term += String.fromCharCode(e.which);
+ } else {
+ if (e.which !== 8) {
+ term += ",";
+ }
+ }
+ options.dataSource(term, updateAutoComplete);
return true;
- }
- if (completeStart !== null) {
- caretPosition = Discourse.Utilities.caretPosition(me[0]);
- /* If we've backspaced past the beginning, cancel unless no key
- */
-
- if (caretPosition <= completeStart && options.key) {
- closeAutocomplete();
- return false;
- }
- /* Keyboard codes! So 80's.
- */
-
- switch (e.which) {
- case 13:
- case 39:
- case 9:
- if (!autocompleteOptions) {
- return true;
- }
- if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) {
- completeTerm(userToComplete);
- } else {
- /* We're cancelling it, really.
- */
-
- return true;
- }
- closeAutocomplete();
- return false;
- case 38:
- selectedOption = selectedOption - 1;
- if (selectedOption < 0) {
- selectedOption = 0;
- }
- markSelected();
- return false;
- case 40:
- total = autocompleteOptions.length;
- selectedOption = selectedOption + 1;
- if (selectedOption >= total) {
- selectedOption = total - 1;
- }
- if (selectedOption < 0) {
- selectedOption = 0;
- }
- markSelected();
- return false;
- default:
- /* otherwise they're typing - let's search for it!
- */
-
- completeEnd = caretPosition;
- if (e.which === 8) {
- caretPosition--;
- }
- if (caretPosition < 0) {
- closeAutocomplete();
- if (isInput) {
- i = wrap.find('a:last');
- if (i) {
- i.click();
- }
- }
- return false;
- }
- term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition);
- if (e.which > 48 && e.which < 90) {
- term += String.fromCharCode(e.which);
- } else {
- if (e.which !== 8) {
- term += ",";
- }
- }
- options.dataSource(term, updateAutoComplete);
- return true;
- }
- }
- });
- };
- return $.fn.autocomplete;
- })(jQuery);
-
-}).call(this);
+ }
+ }
+ });
+};
diff --git a/app/assets/javascripts/discourse/components/bbcode.js b/app/assets/javascripts/discourse/components/bbcode.js
index eff4240707e..dd474985d01 100644
--- a/app/assets/javascripts/discourse/components/bbcode.js
+++ b/app/assets/javascripts/discourse/components/bbcode.js
@@ -1,222 +1,193 @@
/*global HANDLEBARS_TEMPLATES:true*/
-(function() {
+/**
+ Support for BBCode rendering
- Discourse.BBCode = {
- QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im,
- /* Define our replacers
- */
+ @class BBCode
+ @namespace Discourse
+ @module Discourse
+**/
+Discourse.BBCode = {
- replacers: {
- base: {
- withoutArgs: {
- "ol": function(_, content) {
- return "
" + content + "
";
- },
- "li": function(_, content) {
- return "" + content + "
";
- },
- "code": function(_, content) {
- return "" + content + "
";
- },
- "url": function(_, url) {
- return "" + url + "";
- },
- "email": function(_, address) {
- return "" + address + "";
- },
- "img": function(_, src) {
- return "";
- }
- },
- withArgs: {
- "url": function(_, href, title) {
- return "" + title + "";
- },
- "email": function(_, address, title) {
- return "" + title + "";
- },
- "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;
- }
- return "" + content + "";
+ QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im,
+
+ // Define our replacers
+ replacers: {
+ base: {
+ withoutArgs: {
+ "ol": function(_, content) { return "
" + content + "
"; },
+ "li": function(_, content) { return "" + content + "
"; },
+ "code": function(_, content) { return "" + content + "
"; },
+ "url": function(_, url) { return "" + url + ""; },
+ "email": function(_, address) { return "" + address + ""; },
+ "img": function(_, src) { return ""; }
+ },
+ withArgs: {
+ "url": function(_, href, title) { return "" + title + ""; },
+ "email": function(_, address, title) { return "" + title + ""; },
+ "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;
}
+ return "" + content + "";
+ }
+ }
+ },
+
+ // For HTML emails
+ email: {
+ withoutArgs: {
+ "b": function(_, content) { return "" + content + ""; },
+ "i": function(_, content) { return "" + content + ""; },
+ "u": function(_, content) { return "" + content + ""; },
+ "s": function(_, content) { return "
" + content + ""; },
+ "spoiler": function(_, content) { return "" + content + ""; }
+ },
+ withArgs: {
+ "size": function(_, size, content) {
+ return "" + content + "";
+ }
+ }
+ },
+
+ // For sane environments that support CSS
+ "default": {
+ withoutArgs: {
+ "b": function(_, content) { return "" + content + ""; },
+ "i": function(_, content) { return "" + content + ""; },
+ "u": function(_, content) { return "" + content + ""; },
+ "s": function(_, content) { return "" + content + ""; },
+ "spoiler": function(_, content) { return "" + content + "";
}
},
- /* For HTML emails
- */
-
- email: {
- withoutArgs: {
- "b": function(_, content) {
- return "" + content + "";
- },
- "i": function(_, content) {
- return "" + content + "";
- },
- "u": function(_, content) {
- return "" + content + "";
- },
- "s": function(_, content) {
- return "" + content + "";
- },
- "spoiler": function(_, content) {
- return "" + content + "";
- }
- },
- withArgs: {
- "size": function(_, size, content) {
- return "" + content + "";
- }
- }
- },
- /* For sane environments that support CSS
- */
-
- "default": {
- withoutArgs: {
- "b": function(_, content) {
- return "" + content + "";
- },
- "i": function(_, content) {
- return "" + content + "";
- },
- "u": function(_, content) {
- return "" + content + "";
- },
- "s": function(_, content) {
- return "" + content + "";
- },
- "spoiler": function(_, content) {
- return "" + content + "";
- }
- },
- withArgs: {
- "size": function(_, size, content) {
- return "" + content + "";
- }
+ withArgs: {
+ "size": function(_, size, content) {
+ return "" + content + "";
}
}
- },
-
- /* Apply a particular set of replacers */
- apply: function(text, environment) {
- var replacer;
- replacer = Discourse.BBCode.parsedReplacers()[environment];
-
- replacer.forEach(function(r) {
- text = text.replace(r.regexp, r.fn);
- });
- return text;
- },
-
- parsedReplacers: function() {
- var result;
- if (this.parsed) {
- return this.parsed;
- }
- result = {};
- Object.keys(Discourse.BBCode.replacers, function(name, rules) {
- var parsed;
- parsed = result[name] = [];
- Object.keys(Object.merge(Discourse.BBCode.replacers.base.withoutArgs, rules.withoutArgs), function(tag, val) {
- return parsed.push({
- regexp: new RegExp("\\[" + tag + "\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"),
- fn: val
- });
- });
- return Object.keys(Object.merge(Discourse.BBCode.replacers.base.withArgs, rules.withArgs), function(tag, val) {
- return parsed.push({
- regexp: new RegExp("\\[" + tag + "=?(.+?)\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"),
- fn: val
- });
- });
- });
- this.parsed = result;
- return this.parsed;
- },
-
- buildQuoteBBCode: function(post, contents) {
- var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp;
- if (!contents) contents = "";
-
- sansQuotes = contents.replace(this.QUOTE_REGEXP, '').trim();
- if (sansQuotes.length === 0) return "";
-
- /* Strip the HTML from cooked */
- tmp = document.createElement('div');
- tmp.innerHTML = post.get('cooked');
- stripped = tmp.textContent || tmp.innerText;
-
- /*
- Let's remove any non alphanumeric characters as a kind of hash. Yes it's
- not accurate but it should work almost every time we need it to. It would be unlikely
- that the user would quote another post that matches in exactly this way.
- */
- stripped_hashed = stripped.replace(/[^a-zA-Z0-9]/g, '');
- contents_hashed = contents.replace(/[^a-zA-Z0-9]/g, '');
- result = "[quote=\"" + (post.get('username')) + ", post:" + (post.get('post_number')) + ", topic:" + (post.get('topic_id'));
-
- /* If the quote is the full message, attribute it as such */
- if (stripped_hashed === contents_hashed) {
- result += ", full:true";
- }
- result += "\"]\n" + sansQuotes + "\n[/quote]\n\n";
- return result;
- },
-
- formatQuote: function(text, opts) {
-
- /* Replace quotes with appropriate markup */
- var args, matches, params, paramsSplit, paramsString, templateName, username;
- while (matches = this.QUOTE_REGEXP.exec(text)) {
- paramsString = matches[1];
- paramsString = paramsString.replace(/\"/g, '');
- paramsSplit = paramsString.split(/\, */);
- params = [];
- paramsSplit.each(function(p, i) {
- var assignment;
- if (i > 0) {
- assignment = p.split(':');
- if (assignment[0] && assignment[1]) {
- return params.push({
- key: assignment[0],
- value: assignment[1].trim()
- });
- }
- }
- });
- username = paramsSplit[0];
-
- /* Arguments for formatting */
- args = {
- username: username,
- params: params,
- quote: matches[2].trim(),
- avatarImg: opts.lookupAvatar ? opts.lookupAvatar(username) : void 0
- };
- templateName = 'quote';
- if (opts && opts.environment) {
- templateName = "quote_" + opts.environment;
- }
- text = text.replace(matches[0], "
"); - } - return text; - }, - format: function(text, opts) { - var environment; - if (opts && opts.environment) environment = opts.environment; - if (!environment) environment = 'default'; - - text = Discourse.BBCode.apply(text, environment); - // Add quotes - text = Discourse.BBCode.formatQuote(text, opts); - return text; } - }; + }, -}).call(this); + // Apply a particular set of replacers + apply: function(text, environment) { + var replacer; + replacer = Discourse.BBCode.parsedReplacers()[environment]; + + replacer.forEach(function(r) { + text = text.replace(r.regexp, r.fn); + }); + return text; + }, + + parsedReplacers: function() { + var result; + if (this.parsed) return this.parsed; + + result = {}; + Object.keys(Discourse.BBCode.replacers, function(name, rules) { + var parsed; + parsed = result[name] = []; + Object.keys(Object.merge(Discourse.BBCode.replacers.base.withoutArgs, rules.withoutArgs), function(tag, val) { + return parsed.push({ + regexp: new RegExp("\\[" + tag + "\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"), + fn: val + }); + }); + return Object.keys(Object.merge(Discourse.BBCode.replacers.base.withArgs, rules.withArgs), function(tag, val) { + return parsed.push({ + regexp: new RegExp("\\[" + tag + "=?(.+?)\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"), + fn: val + }); + }); + }); + this.parsed = result; + return this.parsed; + }, + + buildQuoteBBCode: function(post, contents) { + var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp; + if (!contents) contents = ""; + + sansQuotes = contents.replace(this.QUOTE_REGEXP, '').trim(); + if (sansQuotes.length === 0) return ""; + + /* Strip the HTML from cooked */ + tmp = document.createElement('div'); + tmp.innerHTML = post.get('cooked'); + stripped = tmp.textContent || tmp.innerText; + + /* + Let's remove any non alphanumeric characters as a kind of hash. Yes it's + not accurate but it should work almost every time we need it to. It would be unlikely + that the user would quote another post that matches in exactly this way. + */ + stripped_hashed = stripped.replace(/[^a-zA-Z0-9]/g, ''); + contents_hashed = contents.replace(/[^a-zA-Z0-9]/g, ''); + result = "[quote=\"" + (post.get('username')) + ", post:" + (post.get('post_number')) + ", topic:" + (post.get('topic_id')); + + /* If the quote is the full message, attribute it as such */ + if (stripped_hashed === contents_hashed) { + result += ", full:true"; + } + result += "\"]\n" + sansQuotes + "\n[/quote]\n\n"; + return result; + }, + + formatQuote: function(text, opts) { + + /* Replace quotes with appropriate markup */ + var args, matches, params, paramsSplit, paramsString, templateName, username; + while (matches = this.QUOTE_REGEXP.exec(text)) { + paramsString = matches[1]; + paramsString = paramsString.replace(/\"/g, ''); + paramsSplit = paramsString.split(/\, */); + params = []; + paramsSplit.each(function(p, i) { + var assignment; + if (i > 0) { + assignment = p.split(':'); + if (assignment[0] && assignment[1]) { + return params.push({ + key: assignment[0], + value: assignment[1].trim() + }); + } + } + }); + username = paramsSplit[0]; + + /* Arguments for formatting */ + args = { + username: username, + params: params, + quote: matches[2].trim(), + avatarImg: opts.lookupAvatar ? opts.lookupAvatar(username) : void 0 + }; + templateName = 'quote'; + if (opts && opts.environment) { + templateName = "quote_" + opts.environment; + } + text = text.replace(matches[0], "
" + HANDLEBARS_TEMPLATES[templateName](args) + ""); + } + 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; + if (!environment) environment = 'default'; + + text = Discourse.BBCode.apply(text, environment); + // Add quotes + text = Discourse.BBCode.formatQuote(text, opts); + return text; + } +}; \ No newline at end of file diff --git a/app/assets/javascripts/discourse/components/caret_position.js b/app/assets/javascripts/discourse/components/caret_position.js index 5081d1f30e5..4bdb9b316ab 100644 --- a/app/assets/javascripts/discourse/components/caret_position.js +++ b/app/assets/javascripts/discourse/components/caret_position.js @@ -1,135 +1,134 @@ +// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea +var clone, getCaret; +getCaret = function(el) { + var r, rc, re; + if (el.selectionStart) { + return el.selectionStart; + } else if (document.selection) { + el.focus(); + r = document.selection.createRange(); + if (!r) return 0; + re = el.createTextRange(); + rc = re.duplicate(); + re.moveToBookmark(r.getBookmark()); + rc.setEndPoint("EndToStart", re); + return rc.text.length; + } + return 0; +}; -/* caret position in textarea ... very hacky ... sorry -*/ +clone = null; +/** + This is a jQuery plugin to retrieve the caret position in a textarea -(function() { + @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 = $("#pos span"); + textarea = $(this); - (function($) { - /* http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea - */ + getStyles = function(el, prop) { + if (el.currentStyle) { + return el.currentStyle; + } else { + return document.defaultView.getComputedStyle(el, ""); + } + }; - var clone, getCaret; - getCaret = function(el) { - var r, rc, re; - if (el.selectionStart) { - return el.selectionStart; - } else if (document.selection) { - el.focus(); - r = document.selection.createRange(); - if (!r) return 0; - re = el.createTextRange(); - rc = re.duplicate(); - re.moveToBookmark(r.getBookmark()); - rc.setEndPoint("EndToStart", re); - return rc.text.length; - } - return 0; - }; - clone = null; - $.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); - getStyles = function(el, prop) { - if (el.currentStyle) { - return el.currentStyle; - } else { - return document.defaultView.getComputedStyle(el, ""); - } - }; - styles = getStyles(textarea[0]); - clone = jQuery("
" + escaped + "
";
- });
+ // 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;
+ escaped = Handlebars.Utils.escapeExpression(m2);
+ return "" + escaped + "
";
});
+ });
+
+ converter.hooks.chain("postConversion", function(text) {
+ if (!text) return "";
+
+ // don't to mention voodoo in pres
+ text = text.replace(/([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) { + return "" + (inner.replace(/@/g, '@')) + ""; + }); + + // 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 + "" + name + ""; + } else { + return "" + pre + "" + name + ""; + } + }); + + // a primitive attempt at oneboxing, this regex gives me much eye sores + text = text.replace(/(
|
)[\s\n\r]*)(]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|
))/gi, function() {
+ // We don't onebox items in a list
+ var onebox, url;
+ if (arguments[1]) {
+ return arguments[0];
+ }
+ url = arguments[5];
+ if (Discourse && Discourse.Onebox) {
+ onebox = Discourse.Onebox.lookupCache(url);
+ }
+ if (onebox && !onebox.isBlank()) {
+ return arguments[2] + onebox;
+ } else {
+ return arguments[2] + arguments[4] + " class=\"onebox\" target=\"_blank\">" + arguments[6];
+ }
+ });
+
+ return(text);
+ });
+
+ converter.hooks.chain("postConversion", function(text) {
+ return Discourse.BBCode.format(text, opts);
+ });
+
+ if (opts.sanitize) {
converter.hooks.chain("postConversion", function(text) {
- if (!text) {
+ if (!window.sanitizeHtml) {
return "";
}
- /* don't to mention voodoo in pres
- */
-
- text = text.replace(/([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
- return "
" + (inner.replace(/@/g, '@')) + "
";
- });
- /* 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 + "" + name + "";
- } else {
- return "" + pre + "" + name + "";
- }
- });
- /* a primitive attempt at oneboxing, this regex gives me much eye sores
- */
-
- text = text.replace(/(