From 6c4ae0545436e6bc1b9afdfa03e2712e315faed9 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 15 Feb 2013 13:36:19 -0800 Subject: [PATCH 01/35] Removes iconv dependency Fixes #100 --- lib/text_sentinel.rb | 11 +++-------- spec/components/text_sentinel_spec.rb | 3 +-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/text_sentinel.rb b/lib/text_sentinel.rb index 61515929a..b869a33d9 100644 --- a/lib/text_sentinel.rb +++ b/lib/text_sentinel.rb @@ -1,5 +1,3 @@ -require 'iconv' - # # Given a string, tell us whether or not is acceptable. Also, remove stuff we don't like # such as leading / trailing space. @@ -13,14 +11,11 @@ class TextSentinel end def initialize(text, opts=nil) - if text.present? - @text = Iconv.new('UTF-8//IGNORE', 'UTF-8').iconv(text.dup) - end - @opts = opts || {} - if @text.present? - @text.strip! + if text.present? + @text = text.encode('UTF-8', invalid: :replace, undef: :replace, replace: '') + @text.strip! @text.gsub!(/ +/m, ' ') if @opts[:remove_interior_spaces] end end diff --git a/spec/components/text_sentinel_spec.rb b/spec/components/text_sentinel_spec.rb index 2b50aef83..a257c8fff 100644 --- a/spec/components/text_sentinel_spec.rb +++ b/spec/components/text_sentinel_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' require 'text_sentinel' -require 'iconv' describe TextSentinel do @@ -100,4 +99,4 @@ describe TextSentinel do end -end \ No newline at end of file +end From 921c3f016ffb33c293a9314761101b0d0a5cab97 Mon Sep 17 00:00:00 2001 From: Shawn Holmes Date: Fri, 15 Feb 2013 22:06:14 -0700 Subject: [PATCH 02/35] Update docs/VAGRANT.md - reverted the Vagrant URL to http; using the https link generates a security warning in Chrome. --- docs/VAGRANT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/VAGRANT.md b/docs/VAGRANT.md index e2503c288..1d061229e 100644 --- a/docs/VAGRANT.md +++ b/docs/VAGRANT.md @@ -10,7 +10,7 @@ on Discourse with: ### Getting Started 1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads -2. Install Vagrant: https://www.vagrantup.com/ +2. Install Vagrant: http://www.vagrantup.com/ 3. Open a terminal 4. Clone the project: `git clone git@github.com:discourse/discourse.git` 5. Enter the project directory: `cd discourse` From 420614d50db8fae6a69e3b70d66c8123ee96aed9 Mon Sep 17 00:00:00 2001 From: Diego Barros Date: Sat, 16 Feb 2013 19:29:05 +1100 Subject: [PATCH 03/35] Clear 'Saved' message when clicking Save button in user preferences. --- .../discourse/controllers/preferences_controller.js.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/discourse/controllers/preferences_controller.js.coffee b/app/assets/javascripts/discourse/controllers/preferences_controller.js.coffee index 1c8264a2d..8eda0321b 100644 --- a/app/assets/javascripts/discourse/controllers/preferences_controller.js.coffee +++ b/app/assets/javascripts/discourse/controllers/preferences_controller.js.coffee @@ -42,6 +42,7 @@ Discourse.PreferencesController = Ember.ObjectController.extend Discourse.Presen save: -> @set('saving', true) + @set('saved', false) # Cook the bio for preview @get('content').save (result) => From 6dc088ca16aa82347ff7262ee901c898f94cc119 Mon Sep 17 00:00:00 2001 From: Dan Neumann Date: Sat, 16 Feb 2013 11:30:46 -0600 Subject: [PATCH 04/35] minor word change --- app/assets/javascripts/admin/templates/flags.js.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/templates/flags.js.handlebars b/app/assets/javascripts/admin/templates/flags.js.handlebars index 17670abee..c0b23f66c 100644 --- a/app/assets/javascripts/admin/templates/flags.js.handlebars +++ b/app/assets/javascripts/admin/templates/flags.js.handlebars @@ -13,7 +13,7 @@ - Flag by + Flagged by From 0d650972c260df9929880b57096edba165837b68 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Sat, 16 Feb 2013 12:27:16 -0800 Subject: [PATCH 05/35] add end nav footer to topic lists by category view --- .../javascripts/discourse/templates/list/list.js.handlebars | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/discourse/templates/list/list.js.handlebars b/app/assets/javascripts/discourse/templates/list/list.js.handlebars index c294ab930..099426789 100644 --- a/app/assets/javascripts/discourse/templates/list/list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/list.js.handlebars @@ -35,6 +35,8 @@ {{/if}} {{outlet listView}} + +

No more topics in this category. Browse all categories or view popular topics

From cecbede5dbe9a5b5187b41d3a978cd9c75ea3261 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Sat, 16 Feb 2013 13:07:07 -0800 Subject: [PATCH 06/35] add tooltip and better color to reply jump arrows --- .../discourse/templates/embedded_post.js.handlebars | 4 ++-- app/assets/stylesheets/application/topic-post.css.scss | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars b/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars index 20f5f30fb..62dbd1d8c 100644 --- a/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars +++ b/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars @@ -11,9 +11,9 @@
- {{#if view.previousPost}}{{/if}} + {{#if view.previousPost}}{{/if}} {{{unbound cooked}}} - {{#unless view.previousPost}}{{/unless}} + {{#unless view.previousPost}}{{/unless}}
{{/with}} diff --git a/app/assets/stylesheets/application/topic-post.css.scss b/app/assets/stylesheets/application/topic-post.css.scss index f9cb327ee..c972a2b82 100644 --- a/app/assets/stylesheets/application/topic-post.css.scss +++ b/app/assets/stylesheets/application/topic-post.css.scss @@ -431,6 +431,7 @@ a.arrow { float: right; margin: 3px 0 3px 0; + color: grey; } } From b91ac453597168d3f0b13ab58f5b8519d7cb9825 Mon Sep 17 00:00:00 2001 From: tms Date: Sat, 16 Feb 2013 16:33:51 -0500 Subject: [PATCH 07/35] Avoid grouping user stats by archetype (filter happens beforehand) --- app/models/user_action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 00cb87c0c..6d56f07f1 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -42,7 +42,7 @@ class UserAction < ActiveRecord::Base results = UserAction.select("action_type, COUNT(*) count, '' AS description") .joins(:target_topic) .where(user_id: user_id) - .group('action_type', 'topics.archetype') + .group('action_type') # should push this into the sql at some point, but its simple enough for now unless guardian.can_see_private_messages?(user_id) From b7e392c7a372a99781fd882ce22a81010500f287 Mon Sep 17 00:00:00 2001 From: tms Date: Sat, 16 Feb 2013 16:46:20 -0500 Subject: [PATCH 08/35] Don't count bookmark stats for users who can't see them --- app/models/user_action.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 6d56f07f1..1ae9a929e 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -44,10 +44,14 @@ class UserAction < ActiveRecord::Base .where(user_id: user_id) .group('action_type') - # should push this into the sql at some point, but its simple enough for now + # We apply similar filters in stream, might consider trying to consolidate somehow unless guardian.can_see_private_messages?(user_id) results = results.where('topics.archetype <> ?', Archetype::private_message) end + + unless guardian.user && guardian.user.id == user_id + results = results.where("action_type <> ?", BOOKMARK) + end results = results.to_a From fac75401ef3fcb7f4654eb5470d60f12e8dc096c Mon Sep 17 00:00:00 2001 From: Dan Neumann Date: Sat, 16 Feb 2013 14:57:16 -0600 Subject: [PATCH 09/35] category stats shouldn't include deleted topics. --- app/models/category.rb | 33 +++++++--------- app/models/topic.rb | 6 ++- spec/fabricators/topic_fabricator.rb | 4 ++ spec/models/category_spec.rb | 58 ++++++++++++++++++++-------- 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/app/models/category.rb b/app/models/category.rb index d180a34eb..b0e67c869 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -17,7 +17,6 @@ class Category < ActiveRecord::Base after_save :invalidate_site_cache after_destroy :invalidate_site_cache - def uncategorized_validator return errors.add(:name, I18n.t(:is_reserved)) if name == SiteSetting.uncategorized_name return errors.add(:slug, I18n.t(:is_reserved)) if slug == SiteSetting.uncategorized_name @@ -26,24 +25,22 @@ class Category < ActiveRecord::Base def self.popular order('topic_count desc') end - + + # Recalculates `topics_year`, `topics_month`, and `topics_week` + # for each Category. def self.update_stats - exec_sql "UPDATE categories - SET topics_week = (SELECT COUNT(*) - FROM topics as ft - WHERE ft.category_id = categories.id - AND ft.created_at > (CURRENT_TIMESTAMP - INTERVAL '1 WEEK') - AND ft.visible), - topics_month = (SELECT COUNT(*) - FROM topics as ft - WHERE ft.category_id = categories.id - AND ft.created_at > (CURRENT_TIMESTAMP - INTERVAL '1 MONTH') - AND ft.visible), - topics_year = (SELECT COUNT(*) - FROM topics as ft - WHERE ft.category_id = categories.id - AND ft.created_at > (CURRENT_TIMESTAMP - INTERVAL '1 YEAR') - AND ft.visible)" + topics = Topic + .select("COUNT(*)") + .where("topics.category_id = categories.id") + .visible + + topics_year = topics.created_since(1.year.ago).to_sql + topics_month = topics.created_since(1.month.ago).to_sql + topics_week = topics.created_since(1.week.ago).to_sql + + Category.update_all("topics_year = (#{topics_year}), + topics_month = (#{topics_month}), + topics_week = (#{topics_week})") end # Use the first paragraph of the topic's first post as the excerpt diff --git a/app/models/topic.rb b/app/models/topic.rb index 8c26deca3..5977f5bd7 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -167,7 +167,11 @@ class Topic < ActiveRecord::Base def self.visible where(visible: true) end - + + def self.created_since(time_ago) + where("created_at > ?", time_ago) + end + def private_message? self.archetype == Archetype.private_message end diff --git a/spec/fabricators/topic_fabricator.rb b/spec/fabricators/topic_fabricator.rb index 8647b0e17..bc4e60746 100644 --- a/spec/fabricators/topic_fabricator.rb +++ b/spec/fabricators/topic_fabricator.rb @@ -3,6 +3,10 @@ Fabricator(:topic) do title { sequence(:title) { |i| "Test topic #{i}" } } end +Fabricator(:deleted_topic, from: :topic) do + deleted_at Time.now +end + Fabricator(:topic_allowed_user) do end diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb index 96bf849e4..c79674283 100644 --- a/spec/models/category_spec.rb +++ b/spec/models/category_spec.rb @@ -157,31 +157,57 @@ describe Category do end describe 'update_stats' do - - # We're going to test with one topic. That's enough for stats! + before do @category = Fabricate(:category) - - # Create a non-invisible category to make sure count is 1 - @topic = Fabricate(:topic, user: @category.user, category: @category) - - Category.update_stats - @category.reload end + + context 'with regular topics' do - it 'updates topics_week' do - @category.topics_week.should == 1 + before do + @category.topics << Fabricate(:topic, + user: @category.user) + Category.update_stats + @category.reload + end + + it 'updates topics_week' do + @category.topics_week.should == 1 + end + + it 'updates topics_month' do + @category.topics_month.should == 1 + end + + it 'updates topics_year' do + @category.topics_year.should == 1 + end + end + + context 'with deleted topics' do - it 'updates topics_month' do - @category.topics_month.should == 1 - end + before do + @category.topics << Fabricate(:deleted_topic, + user: @category.user) + Category.update_stats + @category.reload + end + + it 'does not count deleted topics for topics_week' do + @category.topics_week.should == 0 + end + + it 'does not count deleted topics for topics_month' do + @category.topics_month.should == 0 + end + + it 'does not count deleted topics for topics_year' do + @category.topics_year.should == 0 + end - it 'updates topics_year' do - @category.topics_year.should == 1 end end end - From 72264e303484a82509adb47fdfc055477a3d8bdf Mon Sep 17 00:00:00 2001 From: Dan Neumann Date: Sat, 16 Feb 2013 21:40:24 -0600 Subject: [PATCH 10/35] added 'flagged_by' i18n. --- app/assets/javascripts/admin/templates/flags.js.handlebars | 2 +- config/locales/client.en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/templates/flags.js.handlebars b/app/assets/javascripts/admin/templates/flags.js.handlebars index c0b23f66c..ac4046257 100644 --- a/app/assets/javascripts/admin/templates/flags.js.handlebars +++ b/app/assets/javascripts/admin/templates/flags.js.handlebars @@ -13,7 +13,7 @@ - Flagged by + {{i18n admin.flags.flagged_by}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a564cc0db..6a2775477 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -597,6 +597,7 @@ en: clear_title: "dismiss all flags on this post (will unhide hidden posts)" delete: "Delete Post" delete_title: "delete post (if its the first post delete topic)" + flagged_by: "Flagged by" customize: title: "Customize" From 7df5366aed6b2240abed46e1c1bfdf4c01344aa8 Mon Sep 17 00:00:00 2001 From: Dan Neumann Date: Sat, 16 Feb 2013 22:55:43 -0600 Subject: [PATCH 11/35] fixed guide download link --- docs/DEVELOPER-ADVANCED.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/DEVELOPER-ADVANCED.md b/docs/DEVELOPER-ADVANCED.md index 9aba16d1e..906ecfe1b 100644 --- a/docs/DEVELOPER-ADVANCED.md +++ b/docs/DEVELOPER-ADVANCED.md @@ -11,7 +11,7 @@ to rails, you are likely much better off with our **[Discourse Vagrant Developer 3. Clone the project. 4. Create development and test databases in postgres. 5. Copy `config/database.yml.sample` and `config/redis.yml.sample` to `config/database.yml` and `config/redis.yml` and input the correct values to point to your postgres and redis instances. -6. We recommend starting with seed data to play around in your development environment. [Download Seed SQL Data](http://discourse.org/vms/dev-discourse-seed.sql). Install it into postgres using a command like this: `psql -d discourse_development < dev-discourse-seed.sql`. +6. We recommend starting with seed data to play around in your development environment. [Download Seed SQL Data][seed_download]. Install it into postgres using a command like this: `psql -d discourse_development < dev-discourse-seed.sql`. ## Before you start Rails @@ -107,7 +107,7 @@ Edit /etc/postgresql/9.1/main/pg_hba.conf to have this: host all all ::1/128 trust host all all 0.0.0.0/0 trust # wide-open -Download a database image from [http://discourse.org/vms/dev-discourse-seed.sql][1] +Download a database image from [http://discourse.org/vms/dev-discourse-seed.sql][seed_download]. Load it (as vagrant user): @@ -126,3 +126,5 @@ Load it (as vagrant user): ./install_server.sh # Press enter to accept all the defaults /etc/init.d/redis_6379 start + +[seed_download]: (http://discourse.org/vms/dev-discourse-seed.sql) \ No newline at end of file From fb5710b5d642059a6587823a9e51103949420fd8 Mon Sep 17 00:00:00 2001 From: tms Date: Sun, 17 Feb 2013 01:38:20 -0500 Subject: [PATCH 12/35] Improved action descriptions/links on user stream items --- .../discourse/models/user_action.js.coffee | 11 +++++++- .../templates/user/stream.js.handlebars | 26 +++++++++++++++++-- .../stylesheets/application/user.css.scss | 3 +++ app/models/user_action.rb | 1 + config/locales/client.en.yml | 8 +++++- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/discourse/models/user_action.js.coffee b/app/assets/javascripts/discourse/models/user_action.js.coffee index 88dc55073..1854434c2 100644 --- a/app/assets/javascripts/discourse/models/user_action.js.coffee +++ b/app/assets/javascripts/discourse/models/user_action.js.coffee @@ -2,10 +2,19 @@ window.Discourse.UserAction = Discourse.Model.extend postUrl:(-> Discourse.Utilities.postUrl(@get('slug'), @get('topic_id'), @get('post_number')) ).property() + + replyUrl: (-> + Discourse.Utilities.postUrl(@get('slug'), @get('topic_id'), @get('reply_to_post_number')) + ).property() isPM: (-> a = @get('action_type') - a == UserAction.NEW_PRIVATE_MESSAGE || UserAction.GOT_PRIVATE_MESSAGE + a == Discourse.UserAction.NEW_PRIVATE_MESSAGE || a == Discourse.UserAction.GOT_PRIVATE_MESSAGE + ).property() + + isPostAction: (-> + a = @get('action_type') + a == Discourse.UserAction.RESPONSE || a == Discourse.UserAction.POST || a == Discourse.UserAction.NEW_TOPIC ).property() addChild: (action)-> diff --git a/app/assets/javascripts/discourse/templates/user/stream.js.handlebars b/app/assets/javascripts/discourse/templates/user/stream.js.handlebars index 1890fb515..4a5d55f61 100644 --- a/app/assets/javascripts/discourse/templates/user/stream.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/stream.js.handlebars @@ -5,9 +5,31 @@
{{avatar this imageSize="large" extraClasses="actor" avatarTemplatePath="avatar_template" ignoreTitle="true"}}
{{date path="created_at" leaveAgo="true"}} {{unbound title}}
+ {{#unless description}} + + {{#if isPM}} + + {{i18n user.stream.sent_by}} + {{else}} + {{i18n user.stream.posted_by}} + {{/if}} + + {{/unless}} {{personalizedName name usernamePath="username"}} - {{unbound description}} - #{{unbound post_number}} + {{#if description}} + {{unbound description}} + {{#if isPostAction}} + + {{#if reply_to_post_number}} + #{{unbound reply_to_post_number}} + {{else}} + {{i18n user.stream.the_topic}} + {{/if}} + + {{else}} + {{personalizedName view.parentView.parentView.user.name usernamePath="view.parentView.parentView.user.username"}} + {{/if}} + {{/if}}

{{{unbound excerpt}}} diff --git a/app/assets/stylesheets/application/user.css.scss b/app/assets/stylesheets/application/user.css.scss index adf60b259..49a5b86d6 100644 --- a/app/assets/stylesheets/application/user.css.scss +++ b/app/assets/stylesheets/application/user.css.scss @@ -258,6 +258,9 @@ .type { color: lighten($black, 40%); } + span.name { + color: lighten($black, 40%); + } .time { display: block; float: right; diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 00cb87c0c..1bbb1e15e 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -79,6 +79,7 @@ class UserAction < ActiveRecord::Base SELECT t.title, a.action_type, a.created_at, t.id topic_id, coalesce(p.post_number, 1) post_number, + p.reply_to_post_number, pu.email ,pu.username, pu.name, pu.id user_id, u.email acting_email, u.username acting_username, u.name acting_name, u.id acting_user_id, coalesce(p.cooked, p2.cooked) cooked diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a564cc0db..6aef53d5b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -168,7 +168,13 @@ en: instructions: "We use Gravatar for avatars based on your email" filters: - all: "All" + all: "All" + + stream: + posted_by: "Posted by" + sent_by: "Sent by" + private_message: "private message" + the_topic: "the topic" loading: "Loading..." close: "Close" From 66d56a8b936f0165468414dec76adccc7d95bcd6 Mon Sep 17 00:00:00 2001 From: tms Date: Sun, 17 Feb 2013 02:21:49 -0500 Subject: [PATCH 13/35] Correctly set the typeFilter when the user clicks "show more" --- .../javascripts/discourse/views/search/search_view.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/views/search/search_view.js.coffee b/app/assets/javascripts/discourse/views/search/search_view.js.coffee index d4fdba0a2..eacc6c539 100644 --- a/app/assets/javascripts/discourse/views/search/search_view.js.coffee +++ b/app/assets/javascripts/discourse/views/search/search_view.js.coffee @@ -92,8 +92,8 @@ window.Discourse.SearchView = Ember.View.extend Discourse.Presence, count ).property('content') - moreOfType: (e) -> - @set('typeFilter', e.context) + moreOfType: (type) -> + @set('typeFilter', type) false cancelType: -> From eebc6c7e414d22dda63a24bd7d64cac54215cf30 Mon Sep 17 00:00:00 2001 From: tms Date: Sun, 17 Feb 2013 02:52:21 -0500 Subject: [PATCH 14/35] Request per_facet + 1 in search to know if we actually have more --- lib/search.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/search.rb b/lib/search.rb index b6ebf70de..84b3c0d1a 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -100,7 +100,7 @@ module Search db_result = [] [user_query_sql, category_query_sql, topic_query_sql].each do |sql| - sql << " LIMIT " << Search.per_facet.to_s + sql << " LIMIT " << (Search.per_facet + 1).to_s db_result += ActiveRecord::Base.exec_sql(sql , query: terms.join(" & ")).to_a end end @@ -157,9 +157,12 @@ module Search end result = grouped.map do |type, results| + more = type_filter.blank? && (results.size > Search.per_facet) + results = results[0..([results.length, Search.per_facet].min - 1)] if type_filter.blank? + {type: type, name: I18n.t("search.types.#{type}"), - more: type_filter.blank? && (results.size == Search.per_facet), + more: more, results: results} end result From fa9a2dda6d64df0d2ef036c6cfb0db1373f3e732 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Sun, 17 Feb 2013 01:09:54 -0800 Subject: [PATCH 15/35] nav for /tos /privacy /faq pages, h2 anchors --- .../stylesheets/application/faqs.css.scss | 44 +-- app/views/static/faq.html.erb | 272 +++++++++--------- app/views/static/privacy.html.erb | 46 ++- app/views/static/tos.html.erb | 111 +++---- 4 files changed, 230 insertions(+), 243 deletions(-) diff --git a/app/assets/stylesheets/application/faqs.css.scss b/app/assets/stylesheets/application/faqs.css.scss index bd005833d..07d6d16d7 100755 --- a/app/assets/stylesheets/application/faqs.css.scss +++ b/app/assets/stylesheets/application/faqs.css.scss @@ -36,7 +36,6 @@ margin-bottom: 8px; } - // Lists ul, ol { margin-left: 40px; @@ -44,9 +43,6 @@ } -// Content wrapper -// -------------------------------------------------- - .body-page { .container { @@ -54,44 +50,12 @@ width: 960px; margin: 0 auto; padding: 20px 10px; + margin-top: -60px; } } -// Navigation -// -------------------------------------------------- - -.body-page { - - nav { - width: 280px; - overflow: hidden; - position: fixed; - float: left; - border: 1px solid #b9b9b9; - background-color: #fafafa; - @include border-radius-all(4px); - @include box-shadow(0 1px 0 #fff); - > a { - display: block; - border-top: 1px solid #e6e6e6; - padding: 13px; - font-weight: bold; - font-size: 16px; - line-height: 20px; - text-shadow: 0 1px 0 rgba($white, 0.5); - &:first-child { - border-top: 0; - } - &:hover { - background-color: #eee; - } - &.active { - color: #f15b22; - background-color: #f9e7e0; - cursor: default; - } - } - } - +.nav-pills { + margin-left:0px !important; + font: 13px/18px "Helvetica Neue",Helvetica,Arial,sans-serif; } \ No newline at end of file diff --git a/app/views/static/faq.html.erb b/app/views/static/faq.html.erb index 3f63318aa..ab3ab53df 100644 --- a/app/views/static/faq.html.erb +++ b/app/views/static/faq.html.erb @@ -1,141 +1,133 @@ -

-
- -
-
-

This is a Civilized Place for Public Discussion

-

- Please treat this discussion forum with the same respect you would a public park. We, too, are a shared community resource — a place to share skills, knowledge and interests through ongoing conversation. -

-
-

- Use these guidelines to keep this a clean, well-lighted place for civilized public discourse. These are not hard and fast rules, merely aids to the human judgment of moderators and the overall community. -

-
-
-
-

Improve the Discussion

-

- Help us make this a great place for discussion by always working to improve the discussion in some way, however small. If you are not sure your post adds to the discussion or might detract from its usefulness, think over what you want to say and try again later. -

-
-

- The topics discussed here matter to us, and we want you to act as if they matter to you, too. Be respectful of the topics and the people discussing them, even if you disagree with some of what is being said. -

-

- One way to improve the discussion is by searching for ones that are already happening. Please use the search function before starting a new discussion, and you will have a better chance of meeting others who share your interests. -

-
-

-
-
-

Be Agreeable, Even When You Disagree

-

- You may wish to respond to something by disagreeing with it. That's fine. But, remember to criticize ideas, not people. - Please avoid: -

    -
  • Name-calling.
  • -
  • Ad hominem attacks.
  • -
  • Responding to a post's tone instead of its actual content.
  • -
  • Knee-jerk contradiction.
  • -
-

-
-

- Instead, provide reasoned counter-arguments that improve the conversation. -

-
-
- -
-

Your Participation Counts

-

- The conversations we have here set the tone for everyone. Help us influence the future of this community by choosing to engage in discussions that make this forum an interesting place to be — and avoiding those that do not. -

-
-

- Discourse provides tools that enable the community to collectively identify the best (and worst) contributions: favorites, bookmarks, likes, flags, replies, edits, and so forth. Use these tools to improve your own experience, and everyone else’s, too. -

-

- Let’s try to leave our park better than we found it. -

-
-
+ -
-

If You See a Problem, Flag It

-

- Moderators have special responsibility and authority; they are technically responsible for this forum. But so are you. With your help, moderators should be community facilitators more than janitors or police. -

-
-

- When you see bad behavior, don't reply. It encourages the bad behavior by acknowledging it, consumes your energy, and wastes everyone’s time. Just flag it. If enough flags accrue, action will be taken, either automatically or by moderator intervention. -

-

- In order to maintain our community, moderators reserve the right to remove any content and any user account for any reason at any time. Moderators do not preview new posts or take any preemptive action, therefore, the moderators and site operators take no responsibility for any content posted by the community. -

-
-
- -
-

Always Be Civil

-

- Nothing sabotages a healthy conversation like rudeness: -

    -
  • Be civil. Don’t post anything that a reasonable person would consider offensive, abusive, or hate speech.
  • -
  • Keep it clean. Don’t post anything obscene or sexually explicit.
  • -
  • Respect each other. Don’t harass or grief anyone, - impersonate people, or expose their private information.
  • -
  • Respect our forum. Don’t post spam or otherwise vandalize the forum.
  • -
-

-
-

- These are not concrete terms with precise definitions — avoid - even the appearance of any of these things. If you're unsure, ask yourself how you would feel if your post was featured on the front page of the New York Times. -

-

- This is a public forum, and search engines index these discussions. Keep the language, links, and images safe for family and friends. -

-
-
- -
-

Keep It Tidy

-

- Make the effort to put things in the right place, so that we can spend more time discussing and less cleaning up. So: -

    -
  • Don't start a topic in the wrong category.
  • -
  • Don't cross-post the same thing in multiple topics.
  • -
  • Don't post no-content replies.
  • -
  • Don't divert a topic by changing it midstream.
  • -
-

-
-

- Rather than posting "+1" or "Agreed," use the Like button. Rather than taking an existing topic in a radically different direction, use Reply as a New Topic. -

-

- Also, don't sign your posts — every post has your profile information attached to it. -

-
-
- -
-

Post Only Your Own Stuff

-

- You may not post anything digital that belongs to someone else without permission. You may not post descriptions of, links to, or methods for stealing someone's intellectual property (software, video, audio, images), or for breaking any other law. -

-
-
-
- -
-

Terms of Service

-

- Yes, legalese is boring, but we must protect ourselves – and by extension, you and your data – against unfriendly folks. We have a Terms of Service describing your (and our) behavior and rights related to content, privacy, and laws. To use this service, you must agree to abide by our TOS. -

-
-
-
-
\ No newline at end of file +
+

This is a Civilized Place for Public Discussion

+

+ Please treat this discussion forum with the same respect you would a public park. We, too, are a shared community resource — a place to share skills, knowledge and interests through ongoing conversation. +

+
+

+ Use these guidelines to keep this a clean, well-lighted place for civilized public discourse. These are not hard and fast rules, merely aids to the human judgment of moderators and the overall community. +

+
+ +
+

Improve the Discussion

+

+ Help us make this a great place for discussion by always working to improve the discussion in some way, however small. If you are not sure your post adds to the discussion or might detract from its usefulness, think over what you want to say and try again later. +

+
+

+ The topics discussed here matter to us, and we want you to act as if they matter to you, too. Be respectful of the topics and the people discussing them, even if you disagree with some of what is being said. +

+

+ One way to improve the discussion is by searching for ones that are already happening. Please use the search function before starting a new discussion, and you will have a better chance of meeting others who share your interests. +

+
+ +
+

Be Agreeable, Even When You Disagree

+

+ You may wish to respond to something by disagreeing with it. That's fine. But, remember to criticize ideas, not people. + Please avoid: +

    +
  • Name-calling.
  • +
  • Ad hominem attacks.
  • +
  • Responding to a post's tone instead of its actual content.
  • +
  • Knee-jerk contradiction.
  • +
+
+

+ Instead, provide reasoned counter-arguments that improve the conversation. +

+
+ +
+

Your Participation Counts

+

+ The conversations we have here set the tone for everyone. Help us influence the future of this community by choosing to engage in discussions that make this forum an interesting place to be — and avoiding those that do not. +

+
+

+ Discourse provides tools that enable the community to collectively identify the best (and worst) contributions: favorites, bookmarks, likes, flags, replies, edits, and so forth. Use these tools to improve your own experience, and everyone else’s, too. +

+

+ Let’s try to leave our park better than we found it. +

+
+ +
+

If You See a Problem, Flag It

+

+ Moderators have special responsibility and authority; they are technically responsible for this forum. But so are you. With your help, moderators should be community facilitators more than janitors or police. +

+
+

+ When you see bad behavior, don't reply. It encourages the bad behavior by acknowledging it, consumes your energy, and wastes everyone’s time. Just flag it. If enough flags accrue, action will be taken, either automatically or by moderator intervention. +

+

+ In order to maintain our community, moderators reserve the right to remove any content and any user account for any reason at any time. Moderators do not preview new posts or take any preemptive action, therefore, the moderators and site operators take no responsibility for any content posted by the community. +

+
+ +
+

Always Be Civil

+

+ Nothing sabotages a healthy conversation like rudeness: +

    +
  • Be civil. Don’t post anything that a reasonable person would consider offensive, abusive, or hate speech.
  • +
  • Keep it clean. Don’t post anything obscene or sexually explicit.
  • +
  • Respect each other. Don’t harass or grief anyone, + impersonate people, or expose their private information.
  • +
  • Respect our forum. Don’t post spam or otherwise vandalize the forum.
  • +
+

+
+

+ These are not concrete terms with precise definitions — avoid + even the appearance of any of these things. If you're unsure, ask yourself how you would feel if your post was featured on the front page of the New York Times. +

+

+ This is a public forum, and search engines index these discussions. Keep the language, links, and images safe for family and friends. +

+
+ +
+

Keep It Tidy

+

+ Make the effort to put things in the right place, so that we can spend more time discussing and less cleaning up. So: +

    +
  • Don't start a topic in the wrong category.
  • +
  • Don't cross-post the same thing in multiple topics.
  • +
  • Don't post no-content replies.
  • +
  • Don't divert a topic by changing it midstream.
  • +
+

+
+

+ Rather than posting "+1" or "Agreed," use the Like button. Rather than taking an existing topic in a radically different direction, use Reply as a New Topic. +

+

+ Also, don't sign your posts — every post has your profile information attached to it. +

+
+ +
+

Post Only Your Own Stuff

+

+ You may not post anything digital that belongs to someone else without permission. You may not post descriptions of, links to, or methods for stealing someone's intellectual property (software, video, audio, images), or for breaking any other law. +

+
+
+ +
+

Terms of Service

+

+ Yes, legalese is boring, but we must protect ourselves – and by extension, you and your data – against unfriendly folks. We have a Terms of Service describing your (and our) behavior and rights related to content, privacy, and laws. To use this service, you must agree to abide by our TOS. +

+
+
\ No newline at end of file diff --git a/app/views/static/privacy.html.erb b/app/views/static/privacy.html.erb index 3457f7825..620b67512 100644 --- a/app/views/static/privacy.html.erb +++ b/app/views/static/privacy.html.erb @@ -1,4 +1,11 @@ -

What information do we collect?

+ + +
+

What information do we collect?

We collect information from you when you register on our site and gather data when you participate in the forum by reading, writing, and evaluating the content shared here.

@@ -7,22 +14,25 @@ We collect information from you when you register on our site and gather data wh When participating on our site, you may be asked to enter your: name, age and or e-mail address. You may, however, visit our site anonymously.

-

What do we use your information for?

+
+

What do we use your information for?

Any of the information we collect from you may be used in one of the following ways:

    -
  • To personalize your experience - your information helps us to better respond to your individual needs.
  • -
  • To improve our site - we continually strive to improve our site offerings based on the information and feedback we receive from you.
  • -
  • To improve customer service - your information helps us to more effectively respond to your customer service requests and support needs.
  • -
  • To send periodic emails - The email address you provide may be used to send you information, notifications that you request about changes to topics or in response to your user name, respond to inquiries, and/or other requests or questions.
  • +
  • To personalize your experience — your information helps us to better respond to your individual needs.
  • +
  • To improve our site — we continually strive to improve our site offerings based on the information and feedback we receive from you.
  • +
  • To improve customer service — your information helps us to more effectively respond to your customer service requests and support needs.
  • +
  • To send periodic emails — The email address you provide may be used to send you information, notifications that you request about changes to topics or in response to your user name, respond to inquiries, and/or other requests or questions.
-

How do we protect your information?

+
+

How do we protect your information?

We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information.

-

Do we use cookies?

+
+

Do we use cookies?

Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow) that enables the sites or service providers systems to recognize your browser and capture and remember certain information.

@@ -31,32 +41,38 @@ information when you enter, submit, or access your personal information. We use cookies to understand and save your preferences for future visits and compile aggregate data about site traffic and site interaction so that we can offer better site experiences and tools in the future. We may contract with third-party service providers to assist us in better understanding our site visitors. These service providers are not permitted to use the information collected on our behalf except to help us conduct and improve our business.

-

Do we disclose any information to outside parties?

+
+

Do we disclose any information to outside parties?

We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety. However, non-personally identifiable visitor information may be provided to other parties for marketing, advertising, or other uses.

-

Third party links

+
+

Third party links

Occasionally, at our discretion, we may include or offer third party products or services on our site. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these sites.

-

Children's Online Privacy Protection Act Compliance

+
+

Children's Online Privacy Protection Act Compliance

-We are in compliance with the requirements of COPPA (Childrens Online Privacy Protection Act), we do not collect any information from anyone under 13 years of age. Our site, products and services are all directed to people who are at least 13 years old or older. +We are in compliance with the requirements of COPPA (Children's Online Privacy Protection Act), we do not collect any information from anyone under 13 years of age. Our site, products and services are all directed to people who are at least 13 years old or older.

-

Online Privacy Policy Only

+
+

Online Privacy Policy Only

This online privacy policy applies only to information collected through our site and not to information collected offline.

-

Your Consent

+ +

Your Consent

By using our site, you consent to our web site privacy policy.

-

Changes to our Privacy Policy

+
+

Changes to our Privacy Policy

If we decide to change our privacy policy, we will post those changes on this page.

diff --git a/app/views/static/tos.html.erb b/app/views/static/tos.html.erb index a2008b982..d7270a454 100644 --- a/app/views/static/tos.html.erb +++ b/app/views/static/tos.html.erb @@ -1,3 +1,9 @@ + +

The following terms and conditions govern all use of the <%= @company_domain %> website and all content, services and products available at or through the website, including, but not limited to, <%= @company_domain %> Forum Software, <%= @company_domain %> Support Forums and the <%= @company_domain %> Hosting service (“Hosting”), (taken together, the Website). The Website is owned and operated by <%= @company_fullname %> (“<%= @company_shortname %>”). The Website is offered subject to your acceptance without modification of all of the terms and conditions contained herein and all other operating rules, policies (including, without limitation, <%= @company_domain %>’s Privacy Policy and Community Guidelines) and procedures that may be published from time to time on this Site by <%= @company_shortname %> (collectively, the “Agreement”).

@@ -6,36 +12,30 @@ Please read this Agreement carefully before accessing or using the Website. By accessing or using any part of the web site, you agree to become bound by the terms and conditions of this agreement. If you do not agree to all the terms and conditions of this agreement, then you may not access the Website or use any services. If these terms and conditions are considered an offer by <%= @company_shortname %>, acceptance is expressly limited to these terms. The Website is available only to individuals who are at least 13 years old.

-

1. Your <%= @company_domain %> Account

+
+

1. Your <%= @company_domain %> Account

If you create an account on the Website, you are responsible for maintaining the security of your account and you are fully responsible for all activities that occur under the account. You must immediately notify <%= @company_shortname %> of any unauthorized uses of your account or any other breaches of security. <%= @company_shortname %> will not be liable for any acts or omissions by you, including any damages of any kind incurred as a result of such acts or omissions.

-

2. Responsibility of Contributors

+
+

2. Responsibility of Contributors

If you post material to the Website, post links on the Website, or otherwise make (or allow any third party to make) material available by means of the Website (any such material, “Content”), You are entirely responsible for the content of, and any harm resulting from, that Content. That is the case regardless of whether the Content in question constitutes text, graphics, an audio file, or computer software. By making Content available, you represent and warrant that:

    -
  • the downloading, copying and use of the Content will not infringe the proprietary rights, including but not limited to the copyright, patent, trademark or trade secret rights, of any third party;
  • -
  • if your employer has rights to intellectual property you create, you have either (i) received permission from your employer to post or make available the Content, including but not limited to any software, or (ii) secured from your employer a waiver as to all rights in or to the Content;
  • -
  • you have fully complied with any third-party licenses relating to the Content, and have done all things necessary to successfully pass through to end users any required terms;
  • -
  • the Content does not contain or install any viruses, worms, malware, Trojan horses or other harmful or destructive content;
  • -
  • the Content is not spam, is not machine- or randomly-generated, and does not contain unethical or unwanted commercial content designed to drive traffic to third party sites or boost the search engine rankings of third party sites, or to further unlawful acts (such as phishing) or mislead recipients as to the source of the material (such as spoofing);
  • -
  • the Content is not pornographic, does not contain threats or incite violence, and does not violate the privacy or publicity rights of any third party;
  • -
  • your content is not getting advertised via unwanted electronic messages such as spam links on newsgroups, email lists, blogs and web sites, and similar unsolicited promotional methods;
  • -
  • your content is not named in a manner that misleads your readers into thinking that you are another person or company; and
  • -
  • you have, in the case of Content that includes computer code, accurately categorized and/or described the type, nature, uses and effects of the materials, whether requested to do so by <%= @company_shortname %> or otherwise.
-

3. User Content License

+
+

3. User Content License

@@ -48,7 +48,8 @@ Without limiting any of those representations or warranties, <%= @company_shortname %> has the right (though not the obligation) to, in <%= @company_shortname %>’s sole discretion (i) refuse or remove any content that, in <%= @company_shortname %>’s reasonable opinion, violates any <%= @company_shortname %> policy or is in any way harmful or objectionable, or (ii) terminate or deny access to and use of the Website to any individual or entity for any reason, in <%= @company_shortname %>’s sole discretion. <%= @company_shortname %> will have no obligation to provide a refund of any amounts previously paid.

-

4. Payment and Renewal

+
+

4. Payment and Renewal

General Terms

Optional paid services such as extra storage, or domain purchases are available on the Website (any such services, an “Upgrade”). By selecting an Upgrade you agree to pay <%= @company_shortname %> the monthly or annual subscription fees indicated for that service. Payments will be charged on a pre-pay basis on the day you sign up for an Upgrade and will cover the use of that service for a monthly or annual subscription period as indicated. Upgrade fees are not refundable. @@ -57,67 +58,81 @@

Unless you notify <%= @company_shortname %> before the end of the applicable subscription period that you want to cancel an Upgrade, your Upgrade subscription will automatically renew and you authorize us to collect the then-applicable annual or monthly subscription fee for such Upgrade (as well as any taxes) using any credit card or other payment mechanism we have on record for you. Upgrades can be canceled at any time in the Upgrades section of your site’s dashboard.

-

5. Services

+
+

5. Services

Hosting, Support Services

Hosting and Support services are provided by <%= @company_shortname %> under the terms and conditions for each such service, which are located at /hosting-tos and /support-tos, respectively. By signing up for a Hosting/Support or Support services account, you agree to abide by such terms and conditions.

-

6. Responsibility of Website Visitors

+
+

6. Responsibility of Website Visitors

<%= @company_shortname %> has not reviewed, and cannot review, all of the material, including computer software, posted to the Website, and cannot therefore be responsible for that material’s content, use or effects. By operating the Website, <%= @company_shortname %> does not represent or imply that it endorses the material there posted, or that it believes such material to be accurate, useful or non-harmful. You are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content. The Website may contain content that is offensive, indecent, or otherwise objectionable, as well as content containing technical inaccuracies, typographical mistakes, and other errors. The Website may also contain material that violates the privacy or publicity rights, or infringes the intellectual property and other proprietary rights, of third parties, or the downloading, copying or use of which is subject to additional terms and conditions, stated or unstated. <%= @company_shortname %> disclaims any responsibility for any harm resulting from the use by visitors of the Website, or from any downloading by those visitors of content there posted.

-

7. Content Posted on Other Websites

+
+

7. Content Posted on Other Websites

We have not reviewed, and cannot review, all of the material, including computer software, made available through the websites and webpages to which <%= @company_domain %> links, and that link to <%= @company_domain %>. <%= @company_shortname %> does not have any control over those non-<%= @company_domain %> websites and webpages, and is not responsible for their contents or their use. By linking to a non-<%= @company_domain %> website or webpage, <%= @company_shortname %> does not represent or imply that it endorses such website or webpage. You are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content. <%= @company_shortname %> disclaims any responsibility for any harm resulting from your use of non-WordPress websites and webpages.

-

8. Copyright Infringement and DMCA Policy

+
+

8. Copyright Infringement and DMCA Policy

As <%= @company_shortname %> asks others to respect its intellectual property rights, it respects the intellectual property rights of others. If you believe that material located on or linked to by <%= @company_domain %> violates your copyright, you are encouraged to notify <%= @company_shortname %> in accordance with <%= @company_shortname %>’s Digital Millennium Copyright Act (“DMCA”) Policy. <%= @company_shortname %> will respond to all such notices, including as required or appropriate by removing the infringing material or disabling all links to the infringing material. <%= @company_shortname %> will terminate a visitor’s access to and use of the Website if, under appropriate circumstances, the visitor is determined to be a repeat infringer of the copyrights or other intellectual property rights of <%= @company_shortname %> or others. In the case of such termination, <%= @company_shortname %> will have no obligation to provide a refund of any amounts previously paid to <%= @company_shortname %>.

-

9. Intellectual Property

+
+

9. Intellectual Property

This Agreement does not transfer from <%= @company_shortname %> to you any <%= @company_shortname %> or third party intellectual property, and all right, title and interest in and to such property will remain (as between the parties) solely with <%= @company_shortname %>. <%= @company_shortname %>, <%= @company_domain %>, the <%= @company_domain %> logo, and all other trademarks, service marks, graphics and logos used in connection with <%= @company_domain %>, or the Website are trademarks or registered trademarks of <%= @company_shortname %> or <%= @company_shortname %>’s licensors. Other trademarks, service marks, graphics and logos used in connection with the Website may be the trademarks of other third parties. Your use of the Website grants you no right or license to reproduce or otherwise use any <%= @company_shortname %> or third-party trademarks.

-

10. Advertisements

+
+

10. Advertisements

<%= @company_shortname %> reserves the right to display advertisements on your content unless you have purchased an Ad-free Upgrade or a Services account.

-

11. Attribution

+
+

11. Attribution

<%= @company_shortname %> reserves the right to display attribution links such as ‘Powered by <%= @company_domain %>,’ theme author, and font attribution in your content footer or toolbar. Footer credits and the <%= @company_domain %> toolbar may not be removed regardless of upgrades purchased.

-

12. Changes

-

- <%= @company_shortname %> reserves the right, at its sole discretion, to modify or replace any part of this Agreement. It is your responsibility to check this Agreement periodically for changes. Your continued use of or access to the Website following the posting of any changes to this Agreement constitutes acceptance of those changes. <%= @company_shortname %> may also, in the future, offer new services and/or features through the Website (including, the release of new tools and resources). Such new features and/or services shall be subject to the terms and conditions of this Agreement. -

+
+

12. Changes

+

+<%= @company_shortname %> reserves the right, at its sole discretion, to modify or replace any part of this Agreement. It is your responsibility to check this Agreement periodically for changes. Your continued use of or access to the Website following the posting of any changes to this Agreement constitutes acceptance of those changes. <%= @company_shortname %> may also, in the future, offer new services and/or features through the Website (including, the release of new tools and resources). Such new features and/or services shall be subject to the terms and conditions of this Agreement. +

-

13. Termination

-

- <%= @company_shortname %> may terminate your access to all or any part of the Website at any time, with or without cause, with or without notice, effective immediately. If you wish to terminate this Agreement or your <%= @company_domain %> account (if you have one), you may simply discontinue using the Website. All provisions of this Agreement which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability. -

+
+

13. Termination

+

+<%= @company_shortname %> may terminate your access to all or any part of the Website at any time, with or without cause, with or without notice, effective immediately. If you wish to terminate this Agreement or your <%= @company_domain %> account (if you have one), you may simply discontinue using the Website. All provisions of this Agreement which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability. +

-

14.Disclaimer of Warranties.

-

- The Website is provided “as is”. <%= @company_shortname %> and its suppliers and licensors hereby disclaim all warranties of any kind, express or implied, including, without limitation, the warranties of merchantability, fitness for a particular purpose and non-infringement. Neither <%= @company_shortname %> nor its suppliers and licensors, makes any warranty that the Website will be error free or that cess thereto will be continuous or uninterrupted. If you’re actually reading this, here’s a treat. You understand that you download from, or otherwise obtain content or services through, the Website at your own discretion and risk. -

+
+

14. Disclaimer of Warranties

+

+The Website is provided “as is”. <%= @company_shortname %> and its suppliers and licensors hereby disclaim all warranties of any kind, express or implied, including, without limitation, the warranties of merchantability, fitness for a particular purpose and non-infringement. Neither <%= @company_shortname %> nor its suppliers and licensors, makes any warranty that the Website will be error free or that cess thereto will be continuous or uninterrupted. If you’re actually reading this, here’s a treat. You understand that you download from, or otherwise obtain content or services through, the Website at your own discretion and risk. +

-

15. Limitation of Liability.

-

- In no event will <%= @company_shortname %>, or its suppliers or licensors, be liable with respect to any subject matter of this agreement under any contract, negligence, strict liability or other legal or equitable theory for: (i) any special, incidental or consequential damages; (ii) the cost of procurement for substitute products or services; (iii) for interruption of use or loss or corruption of data; or (iv) for any amounts that exceed the fees paid by you to <%= @company_shortname %> under this agreement during the twelve (12) month period prior to the cause of action. <%= @company_shortname %> shall have no liability for any failure or delay due to matters beyond their reasonable control. The foregoing shall not apply to the extent prohibited by applicable law. -

+
+

15. Limitation of Liability

+

+In no event will <%= @company_shortname %>, or its suppliers or licensors, be liable with respect to any subject matter of this agreement under any contract, negligence, strict liability or other legal or equitable theory for: (i) any special, incidental or consequential damages; (ii) the cost of procurement for substitute products or services; (iii) for interruption of use or loss or corruption of data; or (iv) for any amounts that exceed the fees paid by you to <%= @company_shortname %> under this agreement during the twelve (12) month period prior to the cause of action. <%= @company_shortname %> shall have no liability for any failure or delay due to matters beyond their reasonable control. The foregoing shall not apply to the extent prohibited by applicable law. +

-

16. General Representation and Warranty.

-

- You represent and warrant that (i) your use of the Website will be in strict accordance with the <%= @company_shortname %> Privacy Policy, Community Guidelines, with this Agreement and with all applicable laws and regulations (including without limitation any local laws or regulations in your country, state, city, or other governmental area, regarding online conduct and acceptable content, and including all applicable laws regarding the transmission of technical data exported from the United States or the country in which you reside) and (ii) your use of the Website will not infringe or misappropriate the intellectual property rights of any third party. -

+
+

16. General Representation and Warranty

+

+You represent and warrant that (i) your use of the Website will be in strict accordance with the <%= @company_shortname %> Privacy Policy, Community Guidelines, with this Agreement and with all applicable laws and regulations (including without limitation any local laws or regulations in your country, state, city, or other governmental area, regarding online conduct and acceptable content, and including all applicable laws regarding the transmission of technical data exported from the United States or the country in which you reside) and (ii) your use of the Website will not infringe or misappropriate the intellectual property rights of any third party. +

-

17.Indemnification

-

- You agree to indemnify and hold harmless <%= @company_shortname %>, its contractors, and its licensors, and their respective directors, officers, employees and agents from and against any and all claims and expenses, including attorneys’ fees, arising out of your use of the Website, including but not limited to your violation of this Agreement. -

+
+

17.Indemnification

+

+You agree to indemnify and hold harmless <%= @company_shortname %>, its contractors, and its licensors, and their respective directors, officers, employees and agents from and against any and all claims and expenses, including attorneys’ fees, arising out of your use of the Website, including but not limited to your violation of this Agreement. +

-

18. Miscellaneous

-

- This Agreement constitutes the entire agreement between <%= @company_shortname %> and you concerning the subject matter hereof, and they may only be modified by a written amendment signed by an authorized executive of <%= @company_shortname %>, or by the posting by <%= @company_shortname %> of a revised version. Except to the extent applicable law, if any, provides otherwise, this Agreement, any access to or use of the Website will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions, and the proper venue for any disputes arising out of or relating to any of the same will be the state and federal courts located in San Francisco County, California. Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under this Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (“JAMS”) by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Francisco, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce this Agreement shall be entitled to costs and attorneys’ fees. If any part of this Agreement is held invalid or unenforceable, that part will be construed to reflect the parties’ original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof. You may assign your rights under this Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; <%= @company_shortname %> may assign its rights under this Agreement without condition. This Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns. -

\ No newline at end of file +
+

18. Miscellaneous

+

+ This Agreement constitutes the entire agreement between <%= @company_shortname %> and you concerning the subject matter hereof, and they may only be modified by a written amendment signed by an authorized executive of <%= @company_shortname %>, or by the posting by <%= @company_shortname %> of a revised version. Except to the extent applicable law, if any, provides otherwise, this Agreement, any access to or use of the Website will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions, and the proper venue for any disputes arising out of or relating to any of the same will be the state and federal courts located in San Francisco County, California. Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under this Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (“JAMS”) by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Francisco, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce this Agreement shall be entitled to costs and attorneys’ fees. If any part of this Agreement is held invalid or unenforceable, that part will be construed to reflect the parties’ original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof. You may assign your rights under this Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; <%= @company_shortname %> may assign its rights under this Agreement without condition. This Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns. +

\ No newline at end of file From 702fbcdfa8bdc5f1ff2c982e89e32d2ade630c31 Mon Sep 17 00:00:00 2001 From: tms Date: Sun, 17 Feb 2013 04:10:17 -0500 Subject: [PATCH 16/35] Oneboxes shouldn't explode when the remote causes an HTTPError --- lib/oneboxer.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/oneboxer.rb b/lib/oneboxer.rb index d8542e079..01b7da6ad 100644 --- a/lib/oneboxer.rb +++ b/lib/oneboxer.rb @@ -42,7 +42,7 @@ module Oneboxer end (doc/"link[@type='text/json+oembed']").each do |oembed| return OembedOnebox.new(oembed[:href]).onebox - end + end # Check for opengraph open_graph = Oneboxer.parse_open_graph(doc) @@ -50,7 +50,9 @@ module Oneboxer end end - nil + nil + rescue OpenURI::HTTPError + nil end # Parse URLs out of HTML, returning the document when finished. From 85b0c0afab16f118d0586f526596872f8d0f5985 Mon Sep 17 00:00:00 2001 From: tms Date: Sun, 17 Feb 2013 04:31:21 -0500 Subject: [PATCH 17/35] Have oneboxer default to page title when og:title is missing --- lib/oneboxer/base.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/oneboxer/base.rb b/lib/oneboxer/base.rb index fa0891e23..2d1a160c1 100644 --- a/lib/oneboxer/base.rb +++ b/lib/oneboxer/base.rb @@ -8,6 +8,11 @@ module Oneboxer node = doc.at("/html/head/meta[@property='og:#{prop}']") result[prop] = (node['content'] || node['value']) if node end + + # If there's no title, try using the page's title + if result['title'].blank? + result['title'] = doc.title + end # If there's no description, try and get one from the meta tags if result['description'].blank? From 20c723c4fbc39ceaae1bd58c0295760aa5ff991f Mon Sep 17 00:00:00 2001 From: tms Date: Sun, 17 Feb 2013 15:26:47 -0500 Subject: [PATCH 18/35] Fancy 404 should require access too --- app/controllers/exceptions_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/exceptions_controller.rb b/app/controllers/exceptions_controller.rb index 413807413..6014ebc9c 100644 --- a/app/controllers/exceptions_controller.rb +++ b/app/controllers/exceptions_controller.rb @@ -1,6 +1,5 @@ class ExceptionsController < ApplicationController skip_before_filter :check_xhr - skip_before_filter :check_restricted_access layout 'no_js' def not_found From 3c97cda8e44dac090f158268e5705a4550105483 Mon Sep 17 00:00:00 2001 From: Shawn Holmes Date: Sun, 17 Feb 2013 14:11:57 -0700 Subject: [PATCH 19/35] Update docs/VAGRANT.md - added a subsection for Windows users on what they need to do in lieu of 'vagrant ssh' not working on the Win platform. --- docs/VAGRANT.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/VAGRANT.md b/docs/VAGRANT.md index 1d061229e..9b758479e 100644 --- a/docs/VAGRANT.md +++ b/docs/VAGRANT.md @@ -32,6 +32,28 @@ Once the machine has booted up, you can shell into it by typing: vagrant ssh ``` +**Note to Windows users**: You cannot run ```vagrant ssh``` from a cmd prompt; you'll receive the error message: + +``` +`vagrant ssh` isn't available on the Windows platform. You are still able +to SSH into the virtual machine if you get a Windows SSH client (such as +PuTTY). The authentication information is shown below: + +Host: 127.0.0.1 +Port: 2222 +Username: vagrant +Private key: C:/Users/Your Name/.vagrant.d/insecure_private_key +``` + +At this point, you will want to get an SSH client, and use it to connect to your Vagrant VM instead. We recommend +PuTTY: + +**[PuTTY Download Link](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)** + +You may use this client to connect to the VM by using ```vagrant/vagrant``` as your username/password, or by [using +PuTTYGen to import the insecure_private_key file](http://jason.sharonandjason.com/key_based_putty_logins_mini_how_to.htm) +(mentioned above) into a PuTTY profile to quickly access your VM. + ### Keeping your VM up to date Now you're in a virtual machine is almost ready to start developing. It's a good idea to perform the following instructions From ff0d58e4ee9e43f8ee4333f88ed1c3084b62a9d5 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sat, 16 Feb 2013 12:14:52 +1100 Subject: [PATCH 20/35] message bus work in progress --- config/initializers/message_bus.rb | 1 + vendor/gems/message_bus/assets/application.js | 3 +- vendor/gems/message_bus/assets/message-bus.js | 36 ++++++++++--------- vendor/gems/message_bus/lib/message_bus.rb | 6 ++++ .../lib/message_bus/rack/diagnostics.rb | 5 +-- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/config/initializers/message_bus.rb b/config/initializers/message_bus.rb index a61d379e3..ad12cf1c7 100644 --- a/config/initializers/message_bus.rb +++ b/config/initializers/message_bus.rb @@ -31,3 +31,4 @@ MessageBus.is_admin_lookup do |env| end MessageBus.cache_assets = !Rails.env.development? +MessageBus.enable_diagnostics diff --git a/vendor/gems/message_bus/assets/application.js b/vendor/gems/message_bus/assets/application.js index 6f8f83022..6347f926c 100644 --- a/vendor/gems/message_bus/assets/application.js +++ b/vendor/gems/message_bus/assets/application.js @@ -1,5 +1,4 @@ - window.App = Ember.Application.createWithMixins({ start: function(){ MessageBus.start(); @@ -29,7 +28,7 @@ App.IndexModel = Ember.Object.extend({ this.set("discovering", true); Ember.run.later(function(){ _this.set("discovering", false); - }, 20 * 1000); + }, 1 * 1000); $.post("/message-bus/_diagnostics/discover"); diff --git a/vendor/gems/message_bus/assets/message-bus.js b/vendor/gems/message_bus/assets/message-bus.js index 0b0be0380..bec7c2344 100644 --- a/vendor/gems/message_bus/assets/message-bus.js +++ b/vendor/gems/message_bus/assets/message-bus.js @@ -30,6 +30,24 @@ window.MessageBus = (function() { } }; + var processMessages = function(messages) { + failCount = 0; + $.each(messages,function(idx,message) { + gotData = true; + $.each(callbacks,function(idx,callback) { + if (callback.channel === message.channel) { + callback.last_id = message.message_id; + callback.func(message.data); + } + if (message["channel"] === "/__status") { + if (message.data[callback.channel] !== void 0) { + callback.last_id = message.data[callback.channel]; + } + } + }); + }); + }; + return { enableLongPolling: true, @@ -52,7 +70,7 @@ window.MessageBus = (function() { return; } data = {}; - callbacks.each(function(c) { + $.each(callbacks, function(idx,c) { return data[c.channel] = c.last_id === void 0 ? -1 : c.last_id; }); gotData = false; @@ -65,21 +83,7 @@ window.MessageBus = (function() { 'X-SILENCE-LOGGER': 'true' }, success: function(messages) { - failCount = 0; - return messages.each(function(message) { - gotData = true; - return callbacks.each(function(callback) { - if (callback.channel === message.channel) { - callback.last_id = message.message_id; - callback.func(message.data); - } - if (message["channel"] === "/__status") { - if (message.data[callback.channel] !== void 0) { - callback.last_id = message.data[callback.channel]; - } - } - }); - }); + processMessages(messages); }, error: failCount += 1, complete: function() { diff --git a/vendor/gems/message_bus/lib/message_bus.rb b/vendor/gems/message_bus/lib/message_bus.rb index 72d859cd1..1948e74c7 100644 --- a/vendor/gems/message_bus/lib/message_bus.rb +++ b/vendor/gems/message_bus/lib/message_bus.rb @@ -125,6 +125,12 @@ module MessageBus::Implementation @reliable_pub_sub ||= MessageBus::ReliablePubSub.new redis_config end + def enable_diagnostics + subscribe('/discover') do |msg| + MessageBus.publish '/process-discovery', Process.pid, user_id: msg.data[:user_id] + end + end + def publish(channel, data, opts = nil) return if @off diff --git a/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb b/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb index 6b1e4e59d..a286a4d5e 100644 --- a/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb +++ b/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb @@ -71,8 +71,9 @@ HTML return index unless route - if route == 'discover' - MessageBus.publish('/discover', {user_id: MessageBus() }) + if route == '/discover' + MessageBus.publish('/discover', {user_id: MessageBus.user_id_lookup.call(env)}) + return [200, {}, ['ok']] end asset = route.split('/assets/')[1] From c81d07c44b04ac72399913535b7fcdedf50aff6d Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sat, 16 Feb 2013 12:15:15 +1100 Subject: [PATCH 21/35] clean up the topics footer so it does not flash and is localisable --- .../discourse/controllers/list_controller.js.coffee | 1 + .../discourse/routes/filtered_list_route.js.coffee | 3 +++ .../javascripts/discourse/templates/list/list.js.handlebars | 6 ++++-- config/locales/client.en.yml | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/list_controller.js.coffee b/app/assets/javascripts/discourse/controllers/list_controller.js.coffee index 4be14f1d9..987845cba 100644 --- a/app/assets/javascripts/discourse/controllers/list_controller.js.coffee +++ b/app/assets/javascripts/discourse/controllers/list_controller.js.coffee @@ -39,6 +39,7 @@ Discourse.ListController = Ember.Controller.extend Discourse.Presence, Discourse.TopicList.list(current).then (items) => @set('filterSummary', items.filter_summary) @set('filterMode', filterMode) + @set('allLoaded', true) unless items.more_topics_url @set('loading', false) deferred.resolve(items) diff --git a/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee b/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee index 660e54d9e..38da79204 100644 --- a/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee +++ b/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee @@ -5,6 +5,9 @@ window.Discourse.FilteredListRoute = Discourse.Route.extend listController = @controllerFor('list') listController.set('canCreateTopic', false) listController.set('filterMode', '') + listController.set('allLoaded', false) + + renderTemplate: -> @render 'listTopics', into: 'list', outlet: 'listView', controller: 'listTopics' setupController: -> diff --git a/app/assets/javascripts/discourse/templates/list/list.js.handlebars b/app/assets/javascripts/discourse/templates/list/list.js.handlebars index 099426789..b4a83d6f9 100644 --- a/app/assets/javascripts/discourse/templates/list/list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/list.js.handlebars @@ -35,8 +35,10 @@ {{/if}} {{outlet listView}} - -

No more topics in this category. Browse all categories or view popular topics

+ + {{#if controller.allLoaded}} + {{{i18n topics.footer}}} + {{/if}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d7a26c1ec..1692cde10 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -314,6 +314,7 @@ en: no_read: "You haven't read any topics yet." no_posted: "You haven't posted in any topics yet." no_popular: "There are no popular topics. That's sad." + footer: "No more topics in this category. Browse all categories or view popular topics" topic: create_in: 'Create {{categoryName}} Topic' From c95e1b644cea2e33f8f6885bc777b0e01ae61c38 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sat, 16 Feb 2013 12:26:10 +1100 Subject: [PATCH 22/35] fix tests --- vendor/gems/message_bus/spec/lib/middleware_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/gems/message_bus/spec/lib/middleware_spec.rb b/vendor/gems/message_bus/spec/lib/middleware_spec.rb index 08380b0d5..7e1422843 100644 --- a/vendor/gems/message_bus/spec/lib/middleware_spec.rb +++ b/vendor/gems/message_bus/spec/lib/middleware_spec.rb @@ -118,13 +118,13 @@ describe MessageBus::Rack::Middleware do end it "should get a 200 with html for an authorized user" do - MessageBus.stub(:is_admin_lookup).and_return(lambda{ true }) + MessageBus.stub(:is_admin_lookup).and_return(lambda{|env| true }) get "/message-bus/_diagnostics" last_response.status.should == 200 end it "should get the script it asks for" do - MessageBus.stub(:is_admin_lookup).and_return(lambda{ true }) + MessageBus.stub(:is_admin_lookup).and_return(lambda{|env| true }) get "/message-bus/_diagnostics/assets/message-bus.js" last_response.status.should == 200 last_response.content_type.should == "text/javascript;" From 985b5c59c3c934f4c663260fbbbadfb91c4c15cb Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sat, 16 Feb 2013 12:27:30 +1100 Subject: [PATCH 23/35] missing h3 --- .../javascripts/discourse/templates/list/list.js.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/list/list.js.handlebars b/app/assets/javascripts/discourse/templates/list/list.js.handlebars index b4a83d6f9..75ccca731 100644 --- a/app/assets/javascripts/discourse/templates/list/list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/list.js.handlebars @@ -37,7 +37,7 @@ {{outlet listView}} {{#if controller.allLoaded}} - {{{i18n topics.footer}}} +

{{{i18n topics.footer}}}

{{/if}} From 1228e351a84186db74c93ac3f674ede5e632e485 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sat, 16 Feb 2013 13:54:23 +1100 Subject: [PATCH 24/35] a much more basic but robust restart watcher, removed the pid stuff cause it needs to be a lot more careful not to think pids from other machines restarted. robust working code is better than complex graceful broken code. --- config/initializers/watch_for_restart.rb | 63 ++++++------------------ 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/config/initializers/watch_for_restart.rb b/config/initializers/watch_for_restart.rb index 46261d7c8..e1fb4403e 100644 --- a/config/initializers/watch_for_restart.rb +++ b/config/initializers/watch_for_restart.rb @@ -1,61 +1,30 @@ +# this is a trivial graceful restart on touch of tmp/restart. +# +# It simply drains all the requests (waits up to 4 seconds) and issues a HUP +# if you need a more sophisticated cycling restart for multiple thins it will need to be written +# +# This works fine for Discourse.org cause we host our app accross multiple machines, if you hosting +# on a single machine you have a trickier problem at hand as you need to cycle the processes in order + Thread.new do file = "#{Rails.root}/tmp/restart" - did_exist = nil - old_time = nil + old_time = File.ctime(file).to_i if File.exists? file + wait_seconds = 4 return if $PROGRAM_NAME !~ /thin/ - processes = {} - got_new = false - MessageBus.subscribe "/processes" do |msg| - filetime = msg.data["filetime"] - pid = msg.data["pid"] - got_new = processes[pid].nil? || (processes[pid][:filetime] != filetime) - # puts "#{got_new} #{pid}" - processes[pid] = {time: Time.now.to_i, filetime: filetime} - end - while true - exists = File.exists? file - time = File.ctime(file).to_i if exists - - if (did_exist != nil && did_exist != exists) || - (old_time != nil && time != nil && old_time != time) + time = File.ctime(file).to_i if File.exists? file - got_new = false - probably_restarted = [] - - give_up_time = Time.now.to_i + 60 - - while Time.now.to_i < give_up_time - candidates = [] - processes.each do |pid,data| - if data[:filetime] == old_time && data[:time] > Time.now.to_i - 40 - candidates << pid - end - end - - candidates = candidates - probably_restarted - - break if (candidates.min || $$) >= $$ - sleep 1 - probably_restarted << candidates.min if got_new - got_new = false - end - - - Rails.logger.info "attempting to reload #{$$} #{$PROGRAM_NAME} in 3 seconds restarted #{probably_restarted.inspect}" + if old_time != time + Rails.logger.info "attempting to reload #{$$} #{$PROGRAM_NAME} in #{wait_seconds} seconds" $shutdown = true - sleep 4 + sleep wait_seconds Rails.logger.info "restarting #{$$}" Process.kill("HUP", $$) - - break + return end - MessageBus.publish "/processes", {pid: $$, filetime: time} - did_exist = exists - old_time = time - sleep 10 + sleep 1 end end From cb0e53e701e37d5bb6241a5e4b26e90254968896 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 18 Feb 2013 15:27:44 +1100 Subject: [PATCH 25/35] more work in progress, message bus diags --- vendor/gems/message_bus/assets/application.js | 48 +- vendor/gems/message_bus/assets/ember.js | 1021 +++++++++++------ .../{handlebars-1.0.rc.2.js => handlebars.js} | 388 +++++-- .../gems/message_bus/assets/index.handlebars | 24 + vendor/gems/message_bus/assets/message-bus.js | 4 +- vendor/gems/message_bus/lib/message_bus.rb | 5 +- .../lib/message_bus/diagnostics.rb | 44 + .../lib/message_bus/rack/diagnostics.rb | 11 +- .../lib/message_bus/rack/middleware.rb | 2 + 9 files changed, 1111 insertions(+), 436 deletions(-) rename vendor/gems/message_bus/assets/{handlebars-1.0.rc.2.js => handlebars.js} (84%) create mode 100644 vendor/gems/message_bus/lib/message_bus/diagnostics.rb diff --git a/vendor/gems/message_bus/assets/application.js b/vendor/gems/message_bus/assets/application.js index 6347f926c..884cff24d 100644 --- a/vendor/gems/message_bus/assets/application.js +++ b/vendor/gems/message_bus/assets/application.js @@ -9,12 +9,22 @@ window.App.start(); App.IndexRoute = Ember.Route.extend({ setupController: function(controller) { - controller.set('content', App.IndexModel.create()); + model = App.IndexModel.create(); + model.ensureSubscribed(); + controller.set('content', model); } }); -App.IndexView = Ember.View.extend({ - +App.IndexView = Ember.View.extend({}); + +App.Process = Ember.View.extend({ + uniqueId: function(){ + return this.get('hostname') + this.get('pid'); + }.property('hostname', 'pid'), + + hup: function(){ + $.post("/message-bus/_diagnostics/hup/" + this.get('hostname') + "/" + this.get('pid')); + } }); App.IndexModel = Ember.Object.extend({ @@ -22,8 +32,31 @@ App.IndexModel = Ember.Object.extend({ return this.get("discovering") ? "disabled" : null; }.property("discovering"), + ensureSubscribed: function() { + var processes; + var _this = this; + if(this.get("subscribed")) { return; } + + MessageBus.callbackInterval = 500; + MessageBus.subscribe("/_diagnostics/process-discovery", function(data){ + processes = _this.get('processes'); + processes.pushObject(App.Process.create(data)); + processes = processes.sort(function(a,b){ + return a.get('uniqueId') < b.get('uniqueId') ? -1 : 1; + }); + // somewhat odd ... + _this.set('processes', null); + _this.set('processes', processes); + }); + + this.set("subscribed", true); + }, + discover: function(){ var _this = this; + this.set('processes', Em.A()); + + this.ensureSubscribed(); this.set("discovering", true); Ember.run.later(function(){ @@ -31,11 +64,6 @@ App.IndexModel = Ember.Object.extend({ }, 1 * 1000); $.post("/message-bus/_diagnostics/discover"); - - MessageBus.subscribe("/process-discovery", function(data){ - console.log(data); - }); - } }); @@ -43,5 +71,9 @@ App.IndexModel = Ember.Object.extend({ App.IndexController = Ember.ObjectController.extend({ discover: function(){ this.get("content").discover(); + }, + + hup: function(process) { + process.hup(); } }); diff --git a/vendor/gems/message_bus/assets/ember.js b/vendor/gems/message_bus/assets/ember.js index f16bfd4bc..e566b10f9 100644 --- a/vendor/gems/message_bus/assets/ember.js +++ b/vendor/gems/message_bus/assets/ember.js @@ -1,3 +1,159 @@ +// Version: v1.0.0-rc.1 +// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800) + + +(function() { +/*global __fail__*/ + +/** +Ember Debug + +@module ember +@submodule ember-debug +*/ + +/** +@class Ember +*/ + +if ('undefined' === typeof Ember) { + Ember = {}; + + if ('undefined' !== typeof window) { + window.Em = window.Ember = Em = Ember; + } +} + +Ember.ENV = 'undefined' === typeof ENV ? {} : ENV; + +if (!('MANDATORY_SETTER' in Ember.ENV)) { + Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist +} + +/** + Define an assertion that will throw an exception if the condition is not + met. Ember build tools will remove any calls to `Ember.assert()` when + doing a production build. Example: + + ```javascript + // Test for truthiness + Ember.assert('Must pass a valid object', obj); + // Fail unconditionally + Ember.assert('This code path should never be run') + ``` + + @method assert + @param {String} desc A description of the assertion. This will become + the text of the Error thrown if the assertion fails. + @param {Boolean} test Must be truthy for the assertion to pass. If + falsy, an exception will be thrown. +*/ +Ember.assert = function(desc, test) { + if (!test) throw new Error("assertion failed: "+desc); +}; + + +/** + Display a warning with the provided message. Ember build tools will + remove any calls to `Ember.warn()` when doing a production build. + + @method warn + @param {String} message A warning to display. + @param {Boolean} test An optional boolean. If falsy, the warning + will be displayed. +*/ +Ember.warn = function(message, test) { + if (!test) { + Ember.Logger.warn("WARNING: "+message); + if ('trace' in Ember.Logger) Ember.Logger.trace(); + } +}; + +/** + Display a debug notice. Ember build tools will remove any calls to + `Ember.debug()` when doing a production build. + + ```javascript + Ember.debug("I'm a debug notice!"); + ``` + + @method debug + @param {String} message A debug message to display. +*/ +Ember.debug = function(message) { + Ember.Logger.debug("DEBUG: "+message); +}; + +/** + Display a deprecation warning with the provided message and a stack trace + (Chrome and Firefox only). Ember build tools will remove any calls to + `Ember.deprecate()` when doing a production build. + + @method deprecate + @param {String} message A description of the deprecation. + @param {Boolean} test An optional boolean. If falsy, the deprecation + will be displayed. +*/ +Ember.deprecate = function(message, test) { + if (Ember && Ember.TESTING_DEPRECATION) { return; } + + if (arguments.length === 1) { test = false; } + if (test) { return; } + + if (Ember && Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); } + + var error; + + // When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome + try { __fail__.fail(); } catch (e) { error = e; } + + if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) { + var stack, stackStr = ''; + if (error['arguments']) { + // Chrome + stack = error.stack.replace(/^\s+at\s+/gm, ''). + replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2'). + replace(/^Object.\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n'); + stack.shift(); + } else { + // Firefox + stack = error.stack.replace(/(?:\n@:0)?\s+$/m, ''). + replace(/^\(/gm, '{anonymous}(').split('\n'); + } + + stackStr = "\n " + stack.slice(2).join("\n "); + message = message + stackStr; + } + + Ember.Logger.warn("DEPRECATION: "+message); +}; + + + +/** + Display a deprecation warning with the provided message and a stack trace + (Chrome and Firefox only) when the wrapped method is called. + + Ember build tools will not remove calls to `Ember.deprecateFunc()`, though + no warnings will be shown in production. + + @method deprecateFunc + @param {String} message A description of the deprecation. + @param {Function} func The function to be deprecated. +*/ +Ember.deprecateFunc = function(message, func) { + return function() { + Ember.deprecate(message); + return func.apply(this, arguments); + }; +}; + +})(); + +// Version: v1.0.0-rc.1 +// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800) + + (function() { var define, requireModule; @@ -55,7 +211,7 @@ var define, requireModule; @class Ember @static - @version 1.0.0-pre.4 + @version 1.0.0-rc.1 */ if ('undefined' === typeof Ember) { @@ -82,10 +238,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0-pre.4' + @default '1.0.0-rc.1' @final */ -Ember.VERSION = '1.0.0-pre.4'; +Ember.VERSION = '1.0.0-rc.1'; /** Standard environmental variables. You can define these in a global `ENV` @@ -1699,10 +1855,11 @@ get = function get(obj, keyName) { } if (!obj || keyName.indexOf('.') !== -1) { - + Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined); return getPath(obj, keyName); } + Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName); var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret; if (desc) { @@ -1748,7 +1905,7 @@ get = function get(obj, keyName) { */ set = function set(obj, keyName, value, tolerant) { if (typeof obj === 'string') { - + Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj)); value = keyName; keyName = obj; obj = null; @@ -1758,7 +1915,8 @@ set = function set(obj, keyName, value, tolerant) { return setPath(obj, keyName, value, tolerant); } - + Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined); + Ember.assert('calling set on destroyed object', !obj.isDestroyed); var meta = obj[META_KEY], desc = meta && meta.descs[keyName], isUnknown, currentValue; @@ -1992,7 +2150,7 @@ var Descriptor = Ember.Descriptor = function() {}; // var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) { - + Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false); }; var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) { @@ -2495,6 +2653,7 @@ function flushPendingChains() { forEach.call(queue, function(q) { q[0].add(q[1]); }); + Ember.warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0); } function isProto(pvalue) { @@ -3026,6 +3185,7 @@ Ember.destroy = function (obj) { @module ember-metal */ +Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false); var get = Ember.get, @@ -3255,7 +3415,7 @@ ComputedPropertyPrototype.meta = function(meta) { ComputedPropertyPrototype.willWatch = function(obj, keyName) { // watch already creates meta for this instance var meta = obj[META_KEY]; - + Ember.assert('watch should have setup meta to be writable', meta.source === obj); if (!(keyName in meta.cache)) { addDependentKeys(this, obj, keyName, meta); } @@ -3263,7 +3423,7 @@ ComputedPropertyPrototype.willWatch = function(obj, keyName) { ComputedPropertyPrototype.didUnwatch = function(obj, keyName) { var meta = obj[META_KEY]; - + Ember.assert('unwatch should have setup meta to be writable', meta.source === obj); if (!(keyName in meta.cache)) { // unwatch already creates meta for this instance removeDependentKeys(this, obj, keyName, meta); @@ -3599,7 +3759,7 @@ function actionsDiff(obj, eventName, otherActions) { @param {Function|String} method A function or the name of a function to be called on `target` */ function addListener(obj, eventName, target, method, once) { - + Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName); if (!method && 'function' === typeof target) { method = target; @@ -3631,7 +3791,7 @@ function addListener(obj, eventName, target, method, once) { @param {Function|String} method A function or the name of a function to be called on `target` */ function removeListener(obj, eventName, target, method) { - + Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName); if (!method && 'function' === typeof target) { method = target; @@ -4132,7 +4292,7 @@ Ember.run.begin = function() { @return {void} */ Ember.run.end = function() { - + Ember.assert('must have a current run loop', run.currentRunLoop); function tryable() { run.currentRunLoop.end(); } function finalizer() { run.currentRunLoop = run.currentRunLoop.prev(); } @@ -4234,7 +4394,7 @@ Ember.run.cancelTimers = function () { */ Ember.run.autorun = function() { if (!run.currentRunLoop) { - + Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing); run.begin(); @@ -4628,7 +4788,7 @@ Binding.prototype = { @return {Ember.Binding} `this` */ connect: function(obj) { - + Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj); var fromPath = this._from, toPath = this._to; Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath)); @@ -4653,7 +4813,7 @@ Binding.prototype = { @return {Ember.Binding} `this` */ disconnect: function(obj) { - + Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj); var twoWay = !this._oneWay; @@ -5119,7 +5279,7 @@ function mergeMixins(mixins, m, descs, values, base) { for(var i=0, l=mixins.length; i=0) return copies[loc]; + Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj))); // IMPORTANT: this specific test will detect a native array only. Any other // object will need to implement Copyable. @@ -9372,7 +9567,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { @return {Object} The property value or undefined. */ getPath: function(path) { - + Ember.deprecate("getPath is deprecated since get now supports paths"); return this.get(path); }, @@ -9384,7 +9579,7 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ { @return {Ember.Observable} */ setPath: function(path, value) { - + Ember.deprecate("setPath is deprecated since set now supports paths"); return this.set(path, value); }, @@ -9628,7 +9823,7 @@ Ember.Evented = Ember.Mixin.create({ event. ```javascript - person.on('didEat', food) { + person.on('didEat', function(food) { console.log('person ate some ' + food); }); @@ -9649,7 +9844,7 @@ Ember.Evented = Ember.Mixin.create({ }, fire: function(name) { - + Ember.deprecate("Ember.Evented#fire() has been deprecated in favor of trigger() for compatibility with jQuery. It will be removed in 1.0. Please update your code to call trigger() instead."); this.trigger.apply(this, arguments); }, @@ -9842,7 +10037,8 @@ function makeCtor() { var desc = m.descs[keyName]; - + Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty)); + Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1)); if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) { var baseValue = this[keyName]; @@ -10192,6 +10388,7 @@ var ClassMixin = Mixin.create({ metaForProperty: function(key) { var desc = meta(this.proto(), false).descs[key]; + Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty); return desc._meta || {}; }, @@ -10774,16 +10971,28 @@ var Namespace = Ember.Namespace = Ember.Object.extend({ Namespace.reopenClass({ NAMESPACES: [Ember], + NAMESPACES_BY_ID: {}, PROCESSED: false, - processAll: processAllNamespaces + processAll: processAllNamespaces, + byName: function(name) { + if (!Ember.BOOTED) { + processAllNamespaces(); + } + + return NAMESPACES_BY_ID[name]; + } }); +var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID; + var hasOwnProp = ({}).hasOwnProperty, guidFor = Ember.guidFor; function processNamespace(paths, root, seen) { var idx = paths.length; + NAMESPACES_BY_ID[paths.join('.')] = root; + // Loop over all of the keys in the namespace, looking for classes for(var key in root) { if (!hasOwnProp.call(root, key)) { continue; } @@ -10842,7 +11051,7 @@ function findNamespaces() { } if (isNamespace) { - + Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop)); obj[NAME_KEY] = prop; } } @@ -10883,12 +11092,15 @@ function classToString() { } function processAllNamespaces() { - if (!Namespace.PROCESSED) { + var unprocessedNamespaces = !Namespace.PROCESSED, + unprocessedMixins = Ember.anyUnprocessedMixins; + + if (unprocessedNamespaces) { findNamespaces(); Namespace.PROCESSED = true; } - if (Ember.anyUnprocessedMixins) { + if (unprocessedNamespaces || unprocessedMixins) { var namespaces = Namespace.NAMESPACES, namespace; for (var i=0, l=namespaces.length; i= 0 && idx < length) { + var controllerClass = this.lookupItemController(object); + if (controllerClass) { + return this.controllerAt(idx, object, controllerClass); + } } + + // When `controllerClass` is falsy, we have not opted in to using item + // controllers, so return the object directly. + + // When the index is out of range, we want to return the "out of range" + // value, whatever that might be. Rather than make assumptions + // (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`. + return object; }, arrangedContentDidChange: function() { @@ -12259,6 +12478,7 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, init: function() { this._super(); + if (!this.get('content')) { this.set('content', Ember.A()); } this._resetSubContainers(); }, @@ -12352,7 +12572,7 @@ Ember Runtime */ var jQuery = Ember.imports.jQuery; - +Ember.assert("Ember Views require jQuery 1.8 or 1.9", jQuery && (jQuery().jquery.match(/^1\.(8|9)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY)); /** Alias for jQuery @@ -12492,7 +12712,7 @@ var setInnerHTML = function(element, html) { if (canSetInnerHTML(tagName)) { setInnerHTMLWithoutFix(element, html); } else { - + Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", element.outerHTML); var startTag = element.outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0], endTag = ''; @@ -12632,17 +12852,19 @@ Ember._RenderBuffer.prototype = elementAttributes: null, /** - The value for this attribute. Values cannot be set via attr after - jQuery 1.9, they need to be set with val() instead. + A hash keyed on the name of the properties and whose value will be + applied to that property. For example, if you wanted to apply a + `checked=true` property to an element, you would set the + elementProperties hash to `{'checked':true}`. - You should not maintain this value yourself, rather, you should use - the `val()` method of `Ember.RenderBuffer`. + You should not maintain this hash yourself, rather, you should use + the `prop()` method of `Ember.RenderBuffer`. - @property elementValue - @type String - @default null + @property elementProperties + @type Hash + @default {} */ - elementValue: null, + elementProperties: null, /** The tagname of the element an instance of `Ember.RenderBuffer` represents. @@ -12752,26 +12974,6 @@ Ember._RenderBuffer.prototype = return this; }, - /** - Adds an value which will be rendered to the element. - - @method val - @param {String} value The value to set - @chainable - @return {Ember.RenderBuffer|String} this or the current value - */ - val: function(value) { - var elementValue = this.elementValue; - - if (arguments.length === 0) { - return elementValue; - } else { - this.elementValue = value; - } - - return this; - }, - /** Remove an attribute from the list of attributes to render. @@ -12786,6 +12988,41 @@ Ember._RenderBuffer.prototype = return this; }, + /** + Adds an property which will be rendered to the element. + + @method prop + @param {String} name The name of the property + @param {String} value The value to add to the property + @chainable + @return {Ember.RenderBuffer|String} this or the current property value + */ + prop: function(name, value) { + var properties = this.elementProperties = (this.elementProperties || {}); + + if (arguments.length === 1) { + return properties[name]; + } else { + properties[name] = value; + } + + return this; + }, + + /** + Remove an property from the list of properties to render. + + @method removeProp + @param {String} name The name of the property + @chainable + */ + removeProp: function(name) { + var properties = this.elementProperties; + if (properties) { delete properties[name]; } + + return this; + }, + /** Adds a style to the style attribute which will be rendered to the element. @@ -12819,9 +13056,9 @@ Ember._RenderBuffer.prototype = id = this.elementId, classes = this.classes, attrs = this.elementAttributes, - value = this.elementValue, + props = this.elementProperties, style = this.elementStyle, - prop; + attr, prop; buffer.push('<' + tagName); @@ -12849,19 +13086,30 @@ Ember._RenderBuffer.prototype = } if (attrs) { - for (prop in attrs) { - if (attrs.hasOwnProperty(prop)) { - buffer.push(' ' + prop + '="' + this._escapeAttribute(attrs[prop]) + '"'); + for (attr in attrs) { + if (attrs.hasOwnProperty(attr)) { + buffer.push(' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"'); } } this.elementAttributes = null; } - if (value) { - buffer.push(' value="' + this._escapeAttribute(value) + '"'); + if (props) { + for (prop in props) { + if (props.hasOwnProperty(prop)) { + var value = props[prop]; + if (value || typeof(value) === 'number') { + if (value === true) { + buffer.push(' ' + prop + '="' + prop + '"'); + } else { + buffer.push(' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"'); + } + } + } + } - this.elementValue = null; + this.elementProperties = null; } buffer.push('>'); @@ -12883,9 +13131,9 @@ Ember._RenderBuffer.prototype = id = this.elementId, classes = this.classes, attrs = this.elementAttributes, - value = this.elementValue, + props = this.elementProperties, style = this.elementStyle, - styleBuffer = '', prop; + styleBuffer = '', attr, prop; if (id) { $element.attr('id', id); @@ -12909,19 +13157,23 @@ Ember._RenderBuffer.prototype = } if (attrs) { - for (prop in attrs) { - if (attrs.hasOwnProperty(prop)) { - $element.attr(prop, attrs[prop]); + for (attr in attrs) { + if (attrs.hasOwnProperty(attr)) { + $element.attr(attr, attrs[attr]); } } this.elementAttributes = null; } - if (value) { - $element.val(value); + if (props) { + for (prop in props) { + if (props.hasOwnProperty(prop)) { + $element.prop(prop, props[prop]); + } + } - this.elementValue = null; + this.elementProperties = null; } return element; @@ -13077,11 +13329,13 @@ Ember.EventDispatcher = Ember.Object.extend( var rootElement = Ember.$(get(this, 'rootElement')); - - + Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application')); + Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length); + Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length); rootElement.addClass('ember-application'); + Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application')); for (event in events) { if (events.hasOwnProperty(event)) { @@ -13290,7 +13544,7 @@ var childViewsProperty = Ember.computed(function() { ret.replace = function (idx, removedCount, addedViews) { if (view instanceof Ember.ContainerView) { - + Ember.deprecate("Manipulating a Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray."); return view.replace(idx, removedCount, addedViews); } throw new Error("childViews is immutable"); @@ -13299,6 +13553,7 @@ var childViewsProperty = Ember.computed(function() { return ret; }); +Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false); /** Global hash of shared templates. This will automatically be populated @@ -13322,7 +13577,7 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, { // Register the view for event handling. This hash is used by // Ember.EventDispatcher to dispatch incoming events. if (!this.isVirtual) { - + Ember.assert("Attempted to register a view with an id already in use: "+this.elementId, !Ember.View.views[this.elementId]); Ember.View.views[this.elementId] = this; } @@ -14091,6 +14346,7 @@ Ember.View = Ember.CoreView.extend( var templateName = get(this, 'templateName'), template = this.templateForName(templateName, 'template'); + Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template); return template || get(this, 'defaultTemplate'); }).property('templateName'), @@ -14133,6 +14389,7 @@ Ember.View = Ember.CoreView.extend( var layoutName = get(this, 'layoutName'), layout = this.templateForName(layoutName, 'layout'); + Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || layout); return layout || get(this, 'defaultLayout'); }).property('layoutName'), @@ -14140,6 +14397,7 @@ Ember.View = Ember.CoreView.extend( templateForName: function(name, type) { if (!name) { return; } + Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1); var container = get(this, 'container'); @@ -14266,7 +14524,7 @@ Ember.View = Ember.CoreView.extend( @deprecated */ nearestInstanceOf: function(klass) { - + Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType."); var view = get(this, 'parentView'); while (view) { @@ -14400,6 +14658,7 @@ Ember.View = Ember.CoreView.extend( // is the view's controller by default. A hash of data is also passed that provides // the template with access to the view and render buffer. + Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function'); // The template should write directly to the render buffer instead // of returning a string. output = template(context, { data: data }); @@ -14671,7 +14930,7 @@ Ember.View = Ember.CoreView.extend( // Schedule the DOM element to be created and appended to the given // element after bindings have synchronized. this._insertElementLater(function() { - + Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view')); this.$().appendTo(target); }); @@ -14692,7 +14951,7 @@ Ember.View = Ember.CoreView.extend( @return {Ember.View} received */ replaceIn: function(target) { - + Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view')); this._insertElementLater(function() { Ember.$(target).empty(); @@ -15144,8 +15403,10 @@ Ember.View = Ember.CoreView.extend( // setup child views. be sure to clone the child views array first this._childViews = this._childViews.slice(); + Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array'); this.classNameBindings = Ember.A(this.classNameBindings.slice()); + Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array'); this.classNames = Ember.A(this.classNames.slice()); var viewController = get(this, 'viewController'); @@ -15296,7 +15557,7 @@ Ember.View = Ember.CoreView.extend( // consumers of the view API if (view.viewName) { set(get(this, 'concreteView'), view.viewName, view); } } else { - + Ember.assert('You must pass instance or subclass of View', view.isView); if (attrs) { view.setProperties(attrs); @@ -15604,56 +15865,20 @@ Ember.View.views = {}; Ember.View.childViewsProperty = childViewsProperty; Ember.View.applyAttributeBindings = function(elem, name, value) { - if (name === 'value') { - Ember.View.applyValueBinding(elem, value); - } else { - Ember.View.applyAttributeBinding(elem, name, value); - } -}; - -Ember.View.applyAttributeBinding = function(elem, name, value) { var type = Ember.typeOf(value); - var currentValue = elem.attr(name); // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js - if ( - ( - ( type === 'string' ) || - ( type === 'number' && !isNaN(value) ) || - ( type === 'boolean' && value ) - ) && ( - value !== currentValue - ) - ) { - elem.attr(name, value); - } else if (!value) { - elem.removeAttr(name); - } -}; - -Ember.View.applyValueBinding = function(elem, value) { - var type = Ember.typeOf(value); - var currentValue = elem.val(); - - // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js - if ( - ( - ( type === 'string' ) || - ( type === 'number' && !isNaN(value) ) || - ( type === 'boolean' && value ) - ) && ( - value !== currentValue - ) - ) { - if (elem.caretPosition) { - var caretPosition = elem.caretPosition(); - elem.val(value); - elem.setCaretPosition(caretPosition); - } else { - elem.val(value); + if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) { + if (value !== elem.attr(name)) { + elem.attr(name, value); + } + } else if (name === 'value' || type === 'boolean') { + if (value !== elem.prop(name)) { + // value and booleans should always be properties + elem.prop(name, value); } } else if (!value) { - elem.val(''); + elem.removeAttr(name); } }; @@ -15802,7 +16027,7 @@ Ember.merge(inBuffer, { }, empty: function() { - + Ember.assert("Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications."); }, renderToBufferIfNeeded: function (view) { @@ -16617,7 +16842,7 @@ Ember.CollectionView = Ember.ContainerView.extend( var content = get(this, 'content'); if (content) { - + Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content)); content.addArrayObserver(this); } @@ -16685,6 +16910,7 @@ Ember.CollectionView = Ember.ContainerView.extend( itemViewClass = get(itemViewClass); } + Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), Ember.View.detect(itemViewClass)); len = content ? get(content, 'length') : 0; if (len) { @@ -17245,7 +17471,7 @@ var objectCreate = Object.create || function(parent) { }; var Handlebars = this.Handlebars || Ember.imports.Handlebars; - +Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/)); /** Prepares the Handlebars templating library for use inside Ember's view @@ -17659,7 +17885,7 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) { Ember.Handlebars.registerBoundHelper = function(name, fn) { var dependentKeys = slice.call(arguments, 2); - Ember.Handlebars.registerHelper(name, function() { + function helper() { var properties = slice.call(arguments, 0, -1), numProperties = properties.length, options = arguments[arguments.length - 1], @@ -17698,6 +17924,7 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { return evaluateMultiPropertyBoundHelper(currentContext, fn, normalizedProperties, options); } + Ember.assert("Dependent keys can only be used with single-property helpers.", properties.length === 1); normalized = normalizedProperties[0]; @@ -17720,7 +17947,10 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { for (var i=0, l=dependentKeys.length; i= 1.0.0-rc.3']; helpers = helpers || Ember.Handlebars.helpers; data = data || {}; var buffer = '', stack1, hashTypes, escapeExpression=this.escapeExpression, self=this; function program1(depth0,data) { - var buffer = '', stack1, hashTypes; + var buffer = '', hashTypes; data.buffer.push(""); - return buffer;} + data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}))); + data.buffer.push(""); + return buffer; + } function program3(depth0,data) { - var stack1, hashTypes; - stack1 = {}; - hashTypes = {}; - hashTypes['contentBinding'] = "STRING"; - stack1['contentBinding'] = "this"; - stack1 = helpers.view.call(depth0, "Ember.SelectOption", {hash:stack1,contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); - data.buffer.push(escapeExpression(stack1));} + var hashTypes; + hashTypes = {'contentBinding': "STRING"}; + data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.SelectOption", {hash:{ + 'contentBinding': ("this") + },contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}))); + } - stack1 = {}; hashTypes = {}; - stack1 = helpers['if'].call(depth0, "view.prompt", {hash:stack1,inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); + stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); if(stack1 || stack1 === 0) { data.buffer.push(stack1); } - stack1 = {}; hashTypes = {}; - stack1 = helpers.each.call(depth0, "view.content", {hash:stack1,inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); + stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); if(stack1 || stack1 === 0) { data.buffer.push(stack1); } return buffer; + }), attributeBindings: ['multiple', 'disabled', 'tabindex'], @@ -22413,7 +22648,7 @@ DSL.prototype = { }, route: function(name, options) { - + Ember.assert("You must use `this.resource` to nest", typeof options !== 'function'); options = options || {}; @@ -22536,6 +22771,10 @@ Ember.Router = Ember.Object.extend({ setupLocation(this); }, + url: Ember.computed(function() { + return get(this, 'location').getURL(); + }), + startRouting: function() { this.router = this.router || this.constructor.map(Ember.K); @@ -22549,16 +22788,17 @@ Ember.Router = Ember.Object.extend({ container.register('view', 'default', DefaultView); container.register('view', 'toplevel', Ember.View.extend()); - this.handleURL(location.getURL()); location.onUpdateURL(function(url) { self.handleURL(url); }); + + this.handleURL(location.getURL()); }, didTransition: function(infos) { // Don't do any further action here if we redirected for (var i=0, l=infos.length; i= 2); var container, router, controller, view, context; if (arguments.length === 2) { @@ -23482,6 +23786,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { container = options.data.keywords.controller.container; router = container.lookup('router:main'); + Ember.assert("This view is already rendered", !router || !router._lookupActiveView(name)); view = container.lookup('view:' + name) || container.lookup('view:default'); @@ -23575,12 +23880,14 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { target = target.root; } - if (target.send) { - return target.send.apply(target, args(options.parameters, actionName)); - } else { - - return target[actionName].apply(target, args(options.parameters)); - } + Ember.run(function() { + if (target.send) { + target.send.apply(target, args(options.parameters, actionName)); + } else { + Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function'); + target[actionName].apply(target, args(options.parameters)); + } + }); } }; @@ -23760,7 +24067,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @method action @for Ember.Handlebars.helpers @param {String} actionName - @param {Object...} contexts + @param {Object} [context]* @param {Hash} options */ EmberHandlebars.registerHelper('action', function(actionName) { @@ -23807,61 +24114,80 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { (function() { -var get = Ember.get, set = Ember.set; +/** +@module ember +@submodule ember-routing +*/ -Ember.Handlebars.registerHelper('control', function(path, modelPath, options) { - if (arguments.length === 2) { - options = modelPath; - modelPath = undefined; - } +if (Ember.ENV.EXPERIMENTAL_CONTROL_HELPER) { + var get = Ember.get, set = Ember.set; - var model; + /** + The control helper is currently under development and is considered experimental. + To enable it, set `ENV.EXPERIMENTAL_CONTROL_HELPER = true` before requiring Ember. - if (modelPath) { - model = Ember.Handlebars.get(this, modelPath, options); - } + @method control + @for Ember.Handlebars.helpers + @param {String} path + @param {String} modelPath + @param {Hash} options + @return {String} HTML string + */ + Ember.Handlebars.registerHelper('control', function(path, modelPath, options) { + if (arguments.length === 2) { + options = modelPath; + modelPath = undefined; + } - var controller = options.data.keywords.controller, - view = options.data.keywords.view, - children = get(controller, '_childContainers'), - controlID = options.hash.controlID, - container, subContainer; + var model; - if (children.hasOwnProperty(controlID)) { - subContainer = children[controlID]; - } else { - container = get(controller, 'container'), - subContainer = container.child(); - children[controlID] = subContainer; - } + if (modelPath) { + model = Ember.Handlebars.get(this, modelPath, options); + } - var normalizedPath = path.replace(/\//g, '.'); + var controller = options.data.keywords.controller, + view = options.data.keywords.view, + children = get(controller, '_childContainers'), + controlID = options.hash.controlID, + container, subContainer; - var childView = subContainer.lookup('view:' + normalizedPath) || subContainer.lookup('view:default'), - childController = subContainer.lookup('controller:' + normalizedPath), - childTemplate = subContainer.lookup('template:' + path); + if (children.hasOwnProperty(controlID)) { + subContainer = children[controlID]; + } else { + container = get(controller, 'container'), + subContainer = container.child(); + children[controlID] = subContainer; + } + var normalizedPath = path.replace(/\//g, '.'); + var childView = subContainer.lookup('view:' + normalizedPath) || subContainer.lookup('view:default'), + childController = subContainer.lookup('controller:' + normalizedPath), + childTemplate = subContainer.lookup('template:' + path); - set(childController, 'target', controller); - set(childController, 'model', model); + Ember.assert("Could not find controller for path: " + normalizedPath, childController); + Ember.assert("Could not find view for path: " + normalizedPath, childView); - options.hash.template = childTemplate; - options.hash.controller = childController; - - function observer() { - var model = Ember.Handlebars.get(this, modelPath, options); + set(childController, 'target', controller); set(childController, 'model', model); - childView.rerender(); - } - Ember.addObserver(this, modelPath, observer); - childView.one('willDestroyElement', this, function() { - Ember.removeObserver(this, modelPath, observer); + options.hash.template = childTemplate; + options.hash.controller = childController; + + function observer() { + var model = Ember.Handlebars.get(this, modelPath, options); + set(childController, 'model', model); + childView.rerender(); + } + + Ember.addObserver(this, modelPath, observer); + childView.one('willDestroyElement', this, function() { + Ember.removeObserver(this, modelPath, observer); + }); + + Ember.Handlebars.helpers.view.call(this, childView, options); }); - - Ember.Handlebars.helpers.view.call(this, childView, options); -}); +} })(); @@ -23883,24 +24209,26 @@ var get = Ember.get, set = Ember.set; Ember.ControllerMixin.reopen({ transitionToRoute: function() { - var target = get(this, 'target'); - - return target.transitionTo.apply(target, arguments); + // target may be either another controller or a router + var target = get(this, 'target'), + method = target.transitionToRoute || target.transitionTo; + return method.apply(target, arguments); }, - // TODO: Deprecate this, see https://github.com/emberjs/ember.js/issues/1785 transitionTo: function() { + Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute."); return this.transitionToRoute.apply(this, arguments); }, replaceRoute: function() { - var target = get(this, 'target'); - - return target.replaceWith.apply(target, arguments); + // target may be either another controller or a router + var target = get(this, 'target'), + method = target.replaceRoute || target.replaceWith; + return method.apply(target, arguments); }, - // TODO: Deprecate this, see https://github.com/emberjs/ember.js/issues/1785 replaceWith: function() { + Ember.deprecate("replaceWith is deprecated. Please use replaceRoute."); return this.replaceRoute.apply(this, arguments); } }); @@ -23991,10 +24319,10 @@ var get = Ember.get, set = Ember.set; Ember.Location = { create: function(options) { var implementation = options && options.implementation; - + Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation); var implementationClass = this.implementations[implementation]; - + Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass); return implementationClass.create.apply(implementationClass, arguments); }, @@ -24040,7 +24368,12 @@ Ember.NoneLocation = Ember.Object.extend({ }, onUpdateURL: function(callback) { - // We are not wired up to the browser, so we'll never trigger the callback. + this.updateCallback = callback; + }, + + handleURL: function(url) { + set(this, 'path', url); + this.updateCallback(url); }, formatURL: function(url) { @@ -24121,12 +24454,14 @@ Ember.HashLocation = Ember.Object.extend({ var guid = Ember.guidFor(this); Ember.$(window).bind('hashchange.ember-location-'+guid, function() { - var path = location.hash.substr(1); - if (get(self, 'lastSetURL') === path) { return; } + Ember.run(function() { + var path = location.hash.substr(1); + if (get(self, 'lastSetURL') === path) { return; } - set(self, 'lastSetURL', null); + set(self, 'lastSetURL', null); - callback(location.hash.substr(1)); + callback(location.hash.substr(1)); + }); }); }, @@ -24586,7 +24921,14 @@ var get = Ember.get, set = Ember.set, ### Routing In addition to creating your application's router, `Ember.Application` is - also responsible for telling the router when to start routing. + also responsible for telling the router when to start routing. Transitions + between routes can be logged with the LOG_TRANSITIONS flag: + + ```javascript + window.App = Ember.Application.create({ + LOG_TRANSITIONS: true + }); + ``` By default, the router will begin trying to translate the current URL into application state once the browser emits the `DOMContentReady` event. If you @@ -24595,14 +24937,7 @@ var get = Ember.get, set = Ember.set, If there is any setup required before routing begins, you can implement a `ready()` method on your app that will be invoked immediately before routing - begins: - - ```javascript - window.App = Ember.Application.create({ - ready: function() { - this.set('router.enableLogging', true); - } - }); + begins. To begin routing, you must have at a minimum a top-level controller and view. You define these as `App.ApplicationController` and `App.ApplicationView`, @@ -24620,8 +24955,7 @@ var get = Ember.get, set = Ember.set, @namespace Ember @extends Ember.Namespace */ -var Application = Ember.Application = Ember.Namespace.extend( -/** @scope Ember.Application.prototype */{ +var Application = Ember.Application = Ember.Namespace.extend({ /** The root DOM element of the Application. This can be specified as an @@ -24701,10 +25035,11 @@ var Application = Ember.Application = Ember.Namespace.extend( this.deferUntilDOMReady(); this.scheduleInitialize(); - - - - + Ember.debug('-------------------------------'); + Ember.debug('Ember.VERSION : ' + Ember.VERSION); + Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION); + Ember.debug('jQuery.VERSION : ' + Ember.$().jquery); + Ember.debug('-------------------------------'); }, /** @@ -24820,7 +25155,7 @@ var Application = Ember.Application = Ember.Namespace.extend( @method deferReadiness */ deferReadiness: function() { - + Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0); this._readinessDeferrals++; }, @@ -24896,8 +25231,8 @@ var Application = Ember.Application = Ember.Namespace.extend( @method initialize */ initialize: function() { - - + Ember.assert("Application initialize may only be called once", !this.isInitialized); + Ember.assert("Cannot initialize a destroyed application", !this.isDestroyed); this.isInitialized = true; // At this point, the App.Router must already be assigned @@ -24914,6 +25249,15 @@ var Application = Ember.Application = Ember.Namespace.extend( return this; }, + reset: function() { + get(this, '__container__').destroy(); + this.buildContainer(); + + this.isInitialized = false; + this.initialize(); + this.startRouting(); + }, + /** @private @method runInitializers @@ -25001,6 +25345,12 @@ var Application = Ember.Application = Ember.Namespace.extend( router.startRouting(); }, + handleURL: function(url) { + var router = this.__container__.lookup('router:main'); + + router.handleURL(url); + }, + /** Called when the Application has become ready. The call will be delayed until the DOM has become ready. @@ -25015,7 +25365,7 @@ var Application = Ember.Application = Ember.Namespace.extend( var eventDispatcher = get(this, 'eventDispatcher'); if (eventDispatcher) { eventDispatcher.destroy(); } - this.__container__.destroy(); + get(this, '__container__').destroy(); }, initializer: function(options) { @@ -25029,8 +25379,9 @@ Ember.Application.reopenClass({ initializer: function(initializer) { var initializers = get(this, 'initializers'); - - + Ember.assert("The initializer '" + initializer.name + "' has already been registered", !initializers.findProperty('name', initializers.name)); + Ember.assert("An injection cannot be registered with both a before and an after", !(initializer.before && initializer.after)); + Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(initializer, 'initialize')); initializers.push(initializer); }, @@ -25063,7 +25414,7 @@ Ember.Application.reopenClass({ */ buildContainer: function(namespace) { var container = new Ember.Container(); - Ember.Container.defaultContainer = container; + Ember.Container.defaultContainer = Ember.Container.defaultContainer || container; container.set = Ember.set; container.resolver = resolverFor(namespace); @@ -25176,7 +25527,7 @@ function verifyDependencies(controller) { if (!container.has(dependency)) { satisfied = false; - + Ember.assert(controller + " needs " + dependency + " but it does not exist", false); } } @@ -25192,12 +25543,12 @@ Ember.ControllerMixin.reopen({ // Structure asserts to still do verification but not string concat in production if(!verifyDependencies(this)) { - + Ember.assert("Missing dependencies", false); } }, controllerFor: function(controllerName) { - + Ember.deprecate("Controller#controllerFor is depcrecated, please use Controller#needs instead"); var container = get(this, 'container'); return container.lookup('controller:' + controllerName); }, @@ -26145,7 +26496,7 @@ Ember.StateManager = Ember.State.extend({ if (initialState) { this.transitionTo(initialState); - + Ember.assert('Failed to transition to initial state "' + initialState + '"', !!get(this, 'currentState')); } }, @@ -26209,7 +26560,7 @@ Ember.StateManager = Ember.State.extend({ send: function(event) { var contexts = [].slice.call(arguments, 1); - + Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState')); return sendEvent.call(this, event, contexts, false); }, unhandledEvent: function(manager, event) { @@ -26367,7 +26718,7 @@ Ember.StateManager = Ember.State.extend({ if (!resolveState) { enterStates = this.getStatesInPath(this, path); if (!enterStates) { - + Ember.assert('Could not find state for path: "'+path+'"'); return; } } @@ -26421,6 +26772,7 @@ Ember.StateManager = Ember.State.extend({ enterStates = transitions.enterStates, transitionEvent = get(this, 'transitionEvent'); + Ember.assert("More contexts provided than states", offset >= 0); arrayForEach.call(enterStates, function(state, idx) { state.trigger(transitionEvent, this, contexts[idx-offset]); @@ -26472,9 +26824,16 @@ Ember States })(); +// Version: v1.0.0-rc.1 +// Last commit: 8b061b4 (2013-02-15 12:10:22 -0800) -if (typeof location !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) { - console.warn("You are running a production build of Ember on localhost and won't receive detailed error messages. "+ - "If you want full error messages please use the non-minified build provided on the Ember website."); -} +(function() { +/** +Ember + +@module ember +*/ + +})(); + diff --git a/vendor/gems/message_bus/assets/handlebars-1.0.rc.2.js b/vendor/gems/message_bus/assets/handlebars.js similarity index 84% rename from vendor/gems/message_bus/assets/handlebars-1.0.rc.2.js rename to vendor/gems/message_bus/assets/handlebars.js index aeea92605..9c653ee72 100644 --- a/vendor/gems/message_bus/assets/handlebars-1.0.rc.2.js +++ b/vendor/gems/message_bus/assets/handlebars.js @@ -1,3 +1,27 @@ +/* + +Copyright (C) 2011 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + // lib/handlebars/base.js /*jshint eqnull:true*/ @@ -5,7 +29,13 @@ this.Handlebars = {}; (function(Handlebars) { -Handlebars.VERSION = "1.0.rc.2"; +Handlebars.VERSION = "1.0.0-rc.3"; +Handlebars.COMPILER_REVISION = 2; + +Handlebars.REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '>= 1.0.0-rc.3' +}; Handlebars.helpers = {}; Handlebars.partials = {}; @@ -618,9 +648,13 @@ return new Parser; // lib/handlebars/compiler/base.js Handlebars.Parser = handlebars; -Handlebars.parse = function(string) { +Handlebars.parse = function(input) { + + // Just return if an already-compile AST was passed in. + if(input.constructor === Handlebars.AST.ProgramNode) { return input; } + Handlebars.Parser.yy = Handlebars.AST; - return Handlebars.Parser.parse(string); + return Handlebars.Parser.parse(input); }; Handlebars.print = function(ast) { @@ -702,8 +736,11 @@ Handlebars.print = function(ast) { for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } + } else { dig.push(part); } } @@ -853,6 +890,26 @@ Handlebars.JavaScriptCompiler = function() {}; return out.join("\n"); }, + equals: function(other) { + var len = this.opcodes.length; + if (other.opcodes.length !== len) { + return false; + } + + for (var i = 0; i < len; i++) { + var opcode = this.opcodes[i], + otherOpcode = other.opcodes[i]; + if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) { + return false; + } + for (var j = 0; j < opcode.args.length; j++) { + if (opcode.args[j] !== otherOpcode.args[j]) { + return false; + } + } + } + return true; + }, guid: 0, @@ -944,7 +1001,7 @@ Handlebars.JavaScriptCompiler = function() {}; // evaluate it by executing `blockHelperMissing` this.opcode('pushProgram', program); this.opcode('pushProgram', inverse); - this.opcode('pushHash'); + this.opcode('emptyHash'); this.opcode('blockValue'); } else { this.ambiguousMustache(mustache, program, inverse); @@ -953,7 +1010,7 @@ Handlebars.JavaScriptCompiler = function() {}; // evaluate it by executing `blockHelperMissing` this.opcode('pushProgram', program); this.opcode('pushProgram', inverse); - this.opcode('pushHash'); + this.opcode('emptyHash'); this.opcode('ambiguousBlockValue'); } @@ -977,6 +1034,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.opcode('assignToHash', pair[0]); } + this.opcode('popHash'); }, partial: function(partial) { @@ -1017,17 +1075,19 @@ Handlebars.JavaScriptCompiler = function() {}; }, ambiguousMustache: function(mustache, program, inverse) { - var id = mustache.id, name = id.parts[0]; + var id = mustache.id, + name = id.parts[0], + isBlock = program != null || inverse != null; this.opcode('getContext', id.depth); this.opcode('pushProgram', program); this.opcode('pushProgram', inverse); - this.opcode('invokeAmbiguous', name); + this.opcode('invokeAmbiguous', name, isBlock); }, - simpleMustache: function(mustache, program, inverse) { + simpleMustache: function(mustache) { var id = mustache.id; if (id.type === 'DATA') { @@ -1158,7 +1218,7 @@ Handlebars.JavaScriptCompiler = function() {}; if(mustache.hash) { this.hash(mustache.hash); } else { - this.opcode('pushHash'); + this.opcode('emptyHash'); } return params; @@ -1175,7 +1235,7 @@ Handlebars.JavaScriptCompiler = function() {}; if(mustache.hash) { this.hash(mustache.hash); } else { - this.opcode('pushHash'); + this.opcode('emptyHash'); } return params; @@ -1189,7 +1249,7 @@ Handlebars.JavaScriptCompiler = function() {}; JavaScriptCompiler.prototype = { // PUBLIC API: You can override these methods in a subclass to provide // alternative compiled forms for name lookup and buffering semantics - nameLookup: function(parent, name, type) { + nameLookup: function(parent, name /* , type*/) { if (/^[0-9]+$/.test(name)) { return parent + "[" + name + "]"; } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { @@ -1204,7 +1264,11 @@ Handlebars.JavaScriptCompiler = function() {}; if (this.environment.isSimple) { return "return " + string + ";"; } else { - return "buffer += " + string + ";"; + return { + appendToBuffer: true, + content: string, + toString: function() { return "buffer += " + string + ";"; } + }; } }, @@ -1225,6 +1289,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.isChild = !!context; this.context = context || { programs: [], + environments: [], aliases: { } }; @@ -1234,6 +1299,7 @@ Handlebars.JavaScriptCompiler = function() {}; this.stackVars = []; this.registers = { list: [] }; this.compileStack = []; + this.inlineStack = []; this.compileChildren(environment, options); @@ -1255,11 +1321,11 @@ Handlebars.JavaScriptCompiler = function() {}; }, nextOpcode: function() { - var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1]; + var opcodes = this.environment.opcodes; return opcodes[this.i + 1]; }, - eat: function(opcode) { + eat: function() { this.i = this.i + 1; }, @@ -1297,7 +1363,6 @@ Handlebars.JavaScriptCompiler = function() {}; // Generate minimizer alias mappings if (!this.isChild) { - var aliases = []; for (var alias in this.context.aliases) { this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; } @@ -1322,16 +1387,48 @@ Handlebars.JavaScriptCompiler = function() {}; params.push("depth" + this.environment.depths.list[i]); } + // Perform a second pass over the output to merge content when possible + var source = this.mergeSource(); + + if (!this.isChild) { + var revision = Handlebars.COMPILER_REVISION, + versions = Handlebars.REVISION_CHANGES[revision]; + source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source; + } + if (asObject) { - params.push(this.source.join("\n ")); + params.push(source); return Function.apply(this, params); } else { - var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}'; + var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}'; Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); return functionSource; } }, + mergeSource: function() { + // WARN: We are not handling the case where buffer is still populated as the source should + // not have buffer append operations as their final action. + var source = '', + buffer; + for (var i = 0, len = this.source.length; i < len; i++) { + var line = this.source[i]; + if (line.appendToBuffer) { + if (buffer) { + buffer = buffer + '\n + ' + line.content; + } else { + buffer = line.content; + } + } else { + if (buffer) { + source += 'buffer += ' + buffer + ';\n '; + buffer = undefined; + } + source += line + '\n '; + } + } + return source; + }, // [blockValue] // @@ -1369,6 +1466,9 @@ Handlebars.JavaScriptCompiler = function() {}; var current = this.topStack(); params.splice(1, 0, current); + // Use the options value generated from the invocation + params[params.length-1] = 'options'; + this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); }, @@ -1392,6 +1492,9 @@ Handlebars.JavaScriptCompiler = function() {}; // If `value` is truthy, or 0, it is coerced into a string and appended // Otherwise, the empty string is appended append: function() { + // Force anything that is inlined onto the stack so we don't have duplication + // when we examine local + this.flushInline(); var local = this.popStack(); this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); if (this.environment.isSimple) { @@ -1406,15 +1509,9 @@ Handlebars.JavaScriptCompiler = function() {}; // // Escape `value` and append it to the buffer appendEscaped: function() { - var opcode = this.nextOpcode(), extra = ""; this.context.aliases.escapeExpression = 'this.escapeExpression'; - if(opcode && opcode.opcode === 'appendContent') { - extra = " + " + this.quotedString(opcode.args[0]); - this.eat(opcode); - } - - this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra)); + this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); }, // [getContext] @@ -1438,7 +1535,7 @@ Handlebars.JavaScriptCompiler = function() {}; // Looks up the value of `name` on the current context and pushes // it onto the stack. lookupOnContext: function(name) { - this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context')); + this.push(this.nameLookup('depth' + this.lastContext, name, 'context')); }, // [pushContext] @@ -1486,7 +1583,7 @@ Handlebars.JavaScriptCompiler = function() {}; // // Push the result of looking up `id` on the current data lookupData: function(id) { - this.pushStack(this.nameLookup('data', id, 'data')); + this.push(this.nameLookup('data', id, 'data')); }, // [pushStringParam] @@ -1509,13 +1606,25 @@ Handlebars.JavaScriptCompiler = function() {}; } }, - pushHash: function() { - this.push('{}'); + emptyHash: function() { + this.pushStackLiteral('{}'); if (this.options.stringParams) { this.register('hashTypes', '{}'); } }, + pushHash: function() { + this.hash = {values: [], types: []}; + }, + popHash: function() { + var hash = this.hash; + this.hash = undefined; + + if (this.options.stringParams) { + this.register('hashTypes', '{' + hash.types.join(',') + '}'); + } + this.push('{\n ' + hash.values.join(',\n ') + '\n }'); + }, // [pushString] // @@ -1534,7 +1643,8 @@ Handlebars.JavaScriptCompiler = function() {}; // // Push an expression onto the stack push: function(expr) { - this.pushStack(expr); + this.inlineStack.push(expr); + return expr; }, // [pushLiteral] @@ -1577,12 +1687,14 @@ Handlebars.JavaScriptCompiler = function() {}; invokeHelper: function(paramSize, name) { this.context.aliases.helperMissing = 'helpers.helperMissing'; - var helper = this.lastHelper = this.setupHelper(paramSize, name); - this.register('foundHelper', helper.name); + var helper = this.lastHelper = this.setupHelper(paramSize, name, true); - this.pushStack("foundHelper ? foundHelper.call(" + - helper.callParams + ") " + ": helperMissing.call(" + - helper.helperMissingParams + ")"); + this.push(helper.name); + this.replaceStack(function(name) { + return name + ' ? ' + name + '.call(' + + helper.callParams + ") " + ": helperMissing.call(" + + helper.helperMissingParams + ")"; + }); }, // [invokeKnownHelper] @@ -1594,7 +1706,7 @@ Handlebars.JavaScriptCompiler = function() {}; // so a `helperMissing` fallback is not required. invokeKnownHelper: function(paramSize, name) { var helper = this.setupHelper(paramSize, name); - this.pushStack(helper.name + ".call(" + helper.callParams + ")"); + this.push(helper.name + ".call(" + helper.callParams + ")"); }, // [invokeAmbiguous] @@ -1609,19 +1721,18 @@ Handlebars.JavaScriptCompiler = function() {}; // This operation emits more code than the other options, // and can be avoided by passing the `knownHelpers` and // `knownHelpersOnly` flags at compile-time. - invokeAmbiguous: function(name) { + invokeAmbiguous: function(name, helperCall) { this.context.aliases.functionType = '"function"'; - this.pushStackLiteral('{}'); - var helper = this.setupHelper(0, name); + this.pushStackLiteral('{}'); // Hash value + var helper = this.setupHelper(0, name, helperCall); var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); - this.register('foundHelper', helperName); var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); var nextStack = this.nextStack(); - this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }'); + this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }'); this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }'); }, @@ -1640,7 +1751,7 @@ Handlebars.JavaScriptCompiler = function() {}; } this.context.aliases.self = "this"; - this.pushStack("self.invokePartial(" + params.join(", ") + ")"); + this.push("self.invokePartial(" + params.join(", ") + ")"); }, // [assignToHash] @@ -1651,17 +1762,19 @@ Handlebars.JavaScriptCompiler = function() {}; // Pops a value and hash off the stack, assigns `hash[key] = value` // and pushes the hash back onto the stack. assignToHash: function(key) { - var value = this.popStack(); + var value = this.popStack(), + type; if (this.options.stringParams) { - var type = this.popStack(); + type = this.popStack(); this.popStack(); - this.source.push("hashTypes['" + key + "'] = " + type + ";"); } - var hash = this.topStack(); - - this.source.push(hash + "['" + key + "'] = " + value + ";"); + var hash = this.hash; + if (type) { + hash.types.push("'" + key + "': " + type); + } + hash.values.push("'" + key + "': (" + value + ")"); }, // HELPERS @@ -1675,11 +1788,27 @@ Handlebars.JavaScriptCompiler = function() {}; child = children[i]; compiler = new this.compiler(); - this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children - var index = this.context.programs.length; - child.index = index; - child.name = 'program' + index; - this.context.programs[index] = compiler.compile(child, options, this.context); + var index = this.matchExistingProgram(child); + + if (index == null) { + this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children + index = this.context.programs.length; + child.index = index; + child.name = 'program' + index; + this.context.programs[index] = compiler.compile(child, options, this.context); + this.context.environments[index] = child; + } else { + child.index = index; + child.name = 'program' + index; + } + } + }, + matchExistingProgram: function(child) { + for (var i = 0, len = this.context.environments.length; i < len; i++) { + var environment = this.context.environments[i]; + if (environment && environment.equals(child)) { + return i; + } } }, @@ -1723,57 +1852,111 @@ Handlebars.JavaScriptCompiler = function() {}; }, pushStackLiteral: function(item) { - this.compileStack.push(new Literal(item)); - return item; + return this.push(new Literal(item)); }, pushStack: function(item) { + this.flushInline(); + var stack = this.incrStack(); - this.source.push(stack + " = " + item + ";"); + if (item) { + this.source.push(stack + " = " + item + ";"); + } this.compileStack.push(stack); return stack; }, replaceStack: function(callback) { - var stack = this.topStack(), - item = callback.call(this, stack); + var prefix = '', + inline = this.isInline(), + stack; - // Prevent modification of the context depth variable. Through replaceStack - if (/^depth/.test(stack)) { - stack = this.nextStack(); + // If we are currently inline then we want to merge the inline statement into the + // replacement statement via ',' + if (inline) { + var top = this.popStack(true); + + if (top instanceof Literal) { + // Literals do not need to be inlined + stack = top.value; + } else { + // Get or create the current stack name for use by the inline + var name = this.stackSlot ? this.topStackName() : this.incrStack(); + + prefix = '(' + this.push(name) + ' = ' + top + '),'; + stack = this.topStack(); + } + } else { + stack = this.topStack(); } - this.source.push(stack + " = " + item + ";"); + var item = callback.call(this, stack); + + if (inline) { + if (this.inlineStack.length || this.compileStack.length) { + this.popStack(); + } + this.push('(' + prefix + item + ')'); + } else { + // Prevent modification of the context depth variable. Through replaceStack + if (!/^stack/.test(stack)) { + stack = this.nextStack(); + } + + this.source.push(stack + " = (" + prefix + item + ");"); + } return stack; }, - nextStack: function(skipCompileStack) { - var name = this.incrStack(); - this.compileStack.push(name); - return name; + nextStack: function() { + return this.pushStack(); }, incrStack: function() { this.stackSlot++; if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { return "stack" + this.stackSlot; }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, - popStack: function() { - var item = this.compileStack.pop(); + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); - if (item instanceof Literal) { + if (!wrapped && (item instanceof Literal)) { return item.value; } else { - this.stackSlot--; + if (!inline) { + this.stackSlot--; + } return item; } }, - topStack: function() { - var item = this.compileStack[this.compileStack.length - 1]; + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; - if (item instanceof Literal) { + if (!wrapped && (item instanceof Literal)) { return item.value; } else { return item; @@ -1788,22 +1971,22 @@ Handlebars.JavaScriptCompiler = function() {}; .replace(/\r/g, '\\r') + '"'; }, - setupHelper: function(paramSize, name) { + setupHelper: function(paramSize, name, missingParams) { var params = []; - this.setupParams(paramSize, params); + this.setupParams(paramSize, params, missingParams); var foundHelper = this.nameLookup('helpers', name, 'helper'); return { params: params, name: foundHelper, callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ") + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") }; }, // the params and contexts arguments are passed in arrays // to fill in - setupParams: function(paramSize, params) { + setupParams: function(paramSize, params, useRegister) { var options = [], contexts = [], types = [], param, inverse, program; options.push("hash:" + this.popStack()); @@ -1848,7 +2031,13 @@ Handlebars.JavaScriptCompiler = function() {}; options.push("data:data"); } - params.push("{" + options.join(",") + "}"); + options = "{" + options.join(",") + "}"; + if (useRegister) { + this.register('options', options); + params.push('options'); + } else { + params.push(options); + } return params.join(", "); } }; @@ -1886,23 +2075,23 @@ Handlebars.JavaScriptCompiler = function() {}; })(Handlebars.Compiler, Handlebars.JavaScriptCompiler); -Handlebars.precompile = function(string, options) { - if (typeof string !== 'string') { - throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string); +Handlebars.precompile = function(input, options) { + if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { + throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); } options = options || {}; if (!('data' in options)) { options.data = true; } - var ast = Handlebars.parse(string); + var ast = Handlebars.parse(input); var environment = new Handlebars.Compiler().compile(ast, options); return new Handlebars.JavaScriptCompiler().compile(environment, options); }; -Handlebars.compile = function(string, options) { - if (typeof string !== 'string') { - throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string); +Handlebars.compile = function(input, options) { + if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { + throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); } options = options || {}; @@ -1911,7 +2100,7 @@ Handlebars.compile = function(string, options) { } var compiled; function compile() { - var ast = Handlebars.parse(string); + var ast = Handlebars.parse(input); var environment = new Handlebars.Compiler().compile(ast, options); var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); return Handlebars.template(templateSpec); @@ -1946,12 +2135,32 @@ Handlebars.VM = { } }, programWithDepth: Handlebars.VM.programWithDepth, - noop: Handlebars.VM.noop + noop: Handlebars.VM.noop, + compilerInfo: null }; return function(context, options) { options = options || {}; - return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); + var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); + + var compilerInfo = container.compilerInfo || [], + compilerRevision = compilerInfo[0] || 1, + currentRevision = Handlebars.COMPILER_REVISION; + + if (compilerRevision !== currentRevision) { + if (compilerRevision < currentRevision) { + var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], + compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; + throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ + "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ + "Please update your runtime to a newer version ("+compilerInfo[1]+")."; + } + } + + return result; }; }, @@ -1990,4 +2199,3 @@ Handlebars.VM = { Handlebars.template = Handlebars.VM.template; ; - diff --git a/vendor/gems/message_bus/assets/index.handlebars b/vendor/gems/message_bus/assets/index.handlebars index 340249ec6..6c2b053d3 100644 --- a/vendor/gems/message_bus/assets/index.handlebars +++ b/vendor/gems/message_bus/assets/index.handlebars @@ -1 +1,25 @@ + + + + + + + + + + + + + + {{#each processes}} + + + + + + + + {{/each}} + +
pidfull_pathhostnameuptime
{{pid}}{{full_path}}{{hostname}}{{uptime}} secs
diff --git a/vendor/gems/message_bus/assets/message-bus.js b/vendor/gems/message_bus/assets/message-bus.js index bec7c2344..1a19d1577 100644 --- a/vendor/gems/message_bus/assets/message-bus.js +++ b/vendor/gems/message_bus/assets/message-bus.js @@ -39,7 +39,7 @@ window.MessageBus = (function() { callback.last_id = message.message_id; callback.func(message.data); } - if (message["channel"] === "/__status") { + if (message.channel === "/__status") { if (message.data[callback.channel] !== void 0) { callback.last_id = message.data[callback.channel]; } @@ -60,7 +60,7 @@ window.MessageBus = (function() { start: function(opts) { var poll, _this = this; - if (opts == null) { + if (opts === null) { opts = {}; } poll = function() { diff --git a/vendor/gems/message_bus/lib/message_bus.rb b/vendor/gems/message_bus/lib/message_bus.rb index 1948e74c7..3d144892a 100644 --- a/vendor/gems/message_bus/lib/message_bus.rb +++ b/vendor/gems/message_bus/lib/message_bus.rb @@ -9,6 +9,7 @@ require "message_bus/reliable_pub_sub" require "message_bus/client" require "message_bus/connection_manager" require "message_bus/message_handler" +require "message_bus/diagnostics" require "message_bus/rack/middleware" require "message_bus/rack/diagnostics" @@ -126,9 +127,7 @@ module MessageBus::Implementation end def enable_diagnostics - subscribe('/discover') do |msg| - MessageBus.publish '/process-discovery', Process.pid, user_id: msg.data[:user_id] - end + MessageBus::Diagnostics.enable end def publish(channel, data, opts = nil) diff --git a/vendor/gems/message_bus/lib/message_bus/diagnostics.rb b/vendor/gems/message_bus/lib/message_bus/diagnostics.rb new file mode 100644 index 000000000..8d8d34f8d --- /dev/null +++ b/vendor/gems/message_bus/lib/message_bus/diagnostics.rb @@ -0,0 +1,44 @@ +class MessageBus::Diagnostics + def self.full_process_path + begin + info = `ps -eo "%p|$|%a" | grep '^\\s*#{Process.pid}'` + info.strip.split('|$|')[1] + rescue + # skip it ... not linux or something weird + end + end + + def self.hostname + begin + `hostname`.strip + rescue + # skip it + end + end + + def self.enable + full_path = full_process_path + start_time = Time.now.to_f + hostname = self.hostname + + # it may make sense to add a channel per machine/host to streamline + # process to process comms + MessageBus.subscribe('/_diagnostics/hup') do |msg| + if Process.pid == msg.data["pid"] && hostname == msg.data["hostname"] + $shutdown = true + sleep 4 + Process.kill("HUP", $$) + end + end + + MessageBus.subscribe('/_diagnostics/discover') do |msg| + MessageBus.publish '/_diagnostics/process-discovery', { + pid: Process.pid, + process_name: $0, + full_path: full_path, + uptime: (Time.now.to_f - start_time).to_i, + hostname: hostname + }, user_ids: [msg.data["user_id"]] + end + end +end diff --git a/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb b/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb index a286a4d5e..85f7fbeeb 100644 --- a/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb +++ b/vendor/gems/message_bus/lib/message_bus/rack/diagnostics.rb @@ -38,7 +38,7 @@ class MessageBus::Rack::Diagnostics
#{js_asset "jquery-1.8.2.js"} - #{js_asset "handlebars-1.0.rc.2.js"} + #{js_asset "handlebars.js"} #{js_asset "ember.js"} #{js_asset "message-bus.js"} #{js_asset "application.handlebars"} @@ -72,7 +72,14 @@ HTML return index unless route if route == '/discover' - MessageBus.publish('/discover', {user_id: MessageBus.user_id_lookup.call(env)}) + user_id = MessageBus.user_id_lookup.call(env) + MessageBus.publish('/_diagnostics/discover', user_id: user_id) + return [200, {}, ['ok']] + end + + if route =~ /^\/hup\// + hostname, pid = route.split('/hup/')[1].split('/') + MessageBus.publish('/_diagnostics/hup', {hostname: hostname, pid: pid.to_i}) return [200, {}, ['ok']] end diff --git a/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb b/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb index f240266a4..121d8144a 100644 --- a/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb +++ b/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb @@ -9,6 +9,8 @@ class MessageBus::Rack::Middleware def self.start_listener unless @started_listener MessageBus.subscribe do |msg| + p msg.channel + p msg.message_id EM.next_tick do @@connection_manager.notify_clients(msg) if @@connection_manager end From 65d71f87a616f6acebafc9f08f182ab7a4de72d2 Mon Sep 17 00:00:00 2001 From: tms Date: Mon, 18 Feb 2013 00:28:19 -0500 Subject: [PATCH 26/35] Use consistent date formatting in date helpers --- .../discourse/helpers/application_helpers.js.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/helpers/application_helpers.js.coffee b/app/assets/javascripts/discourse/helpers/application_helpers.js.coffee index 269278afa..460df3aa2 100644 --- a/app/assets/javascripts/discourse/helpers/application_helpers.js.coffee +++ b/app/assets/javascripts/discourse/helpers/application_helpers.js.coffee @@ -72,8 +72,7 @@ Handlebars.registerHelper 'avatar', (user, options) -> Handlebars.registerHelper 'unboundDate', (property, options) -> dt = new Date(Ember.Handlebars.get(this, property, options)) - month = Date.SugarMethods.getLocale.method().months[12 + dt.getMonth()] - "#{dt.getDate()} #{month}, #{dt.getFullYear()} #{dt.getHours()}:#{dt.getMinutes()}" + dt.format("{d} {Mon}, {yyyy} {hh}:{mm}") Handlebars.registerHelper 'editDate', (property, options) -> dt = Date.create(Ember.Handlebars.get(this, property, options)) From 87b929eac6174ee514c0478f0b1c79d69fc84274 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 18 Feb 2013 17:34:43 +1100 Subject: [PATCH 27/35] added secret token warning in prd added task to stamp builds --- .gitignore | 3 +++ config/application.rb | 6 ++++-- config/initializers/secret_token.rb | 15 ++++++++++++++- lib/tasks/build.rake | 8 ++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 lib/tasks/build.rake diff --git a/.gitignore b/.gitignore index db2900f71..19d075c15 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ chef/tmp/* # .procfile .procfile + +# exclude our git version file for now +config/version.rb diff --git a/config/application.rb b/config/application.rb index 85723bf21..99591b514 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,7 +1,9 @@ require File.expand_path('../boot', __FILE__) +# our version info can be missing, we will do our best to figure it out +require File.expand_path('../version', __FILE__) rescue nil require 'rails/all' -require "redis-store" # HACK +require 'redis-store' # HACK # Plugin related stuff require './lib/discourse_plugin_registry' @@ -50,7 +52,7 @@ module Discourse # config.i18n.default_locale = :de # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = "utf-8" + config.encoding = 'utf-8' # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 61d320aa4..9635256f3 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -1,3 +1,16 @@ # Definitely change this when you deploy to production. Ours is replaced by jenkins. -Discourse::Application.config.secret_token = "47f5390004bf6d25bb97083fb98e7cc133ab450ba814dd19638a78282b4ca291" +# This token is used to secure sessions, we don't mind shipping with one to ease test and debug, +# however, the stock one should never be used in production, people will be able to crack +# session cookies. +# + +# Discourse::Application.config.secret_token = "SET_SECRET_HERE" + +# delete all lines below in production +if Rails.env.test? || Rails.env.development? + Discourse::Application.config.secret_token = "47f5390004bf6d25bb97083fb98e7cc133ab450ba814dd19638a78282b4ca291" +else + raise "You must set a secret token in config/initializers/secret_token.rb" +end + diff --git a/lib/tasks/build.rake b/lib/tasks/build.rake new file mode 100644 index 000000000..2ee206d4a --- /dev/null +++ b/lib/tasks/build.rake @@ -0,0 +1,8 @@ +desc "stamp the current build with the git hash placed in version.rb" +task "build:stamp" => :environment do + git_version = `git rev-parse HEAD`.strip + File.open(Rails.root.to_s + '/config/version.rb', 'w') do |f| + f.write("$git_version = #{git_version.inspect}\n") + end + puts "Stamped current build with #{git_version}" +end From d9a84ddd019282511e07120896c9737c7299133e Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 18 Feb 2013 17:39:54 +1100 Subject: [PATCH 28/35] chuck git version at the bottom of the page --- app/views/layouts/application.html.erb | 1 + lib/discourse.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 295a7d509..437c36e46 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -58,5 +58,6 @@ <%= render :partial => "common/discourse_javascript" %> <%= render_google_analytics_code %> + diff --git a/lib/discourse.rb b/lib/discourse.rb index 1893fa56a..4d0d119b7 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -44,6 +44,14 @@ module Discourse !!$redis.get( maintenance_mode_key ) end + def self.git_version + begin + $git_version ||= `git rev-parse HEAD` + rescue + $git_version = "unknown" + end + end + private From 0e48e1c3466371fb31a5ad012741d26aa6c3dda1 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 18 Feb 2013 18:00:49 +1100 Subject: [PATCH 29/35] clean up git version stuff a tad --- app/views/layouts/application.html.erb | 2 +- config/application.rb | 3 --- lib/discourse.rb | 6 +++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 437c36e46..38669d745 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -58,6 +58,6 @@ <%= render :partial => "common/discourse_javascript" %> <%= render_google_analytics_code %> - + diff --git a/config/application.rb b/config/application.rb index 99591b514..c9fb85768 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,7 +1,4 @@ require File.expand_path('../boot', __FILE__) -# our version info can be missing, we will do our best to figure it out -require File.expand_path('../version', __FILE__) rescue nil - require 'rails/all' require 'redis-store' # HACK diff --git a/lib/discourse.rb b/lib/discourse.rb index 4d0d119b7..58349b039 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -45,8 +45,12 @@ module Discourse end def self.git_version + return $git_version if $git_version + f = Rails.root.to_s + "/config/version" + require f if File.exists?("#{f}.rb") + begin - $git_version ||= `git rev-parse HEAD` + $git_version ||= `git rev-parse HEAD`.strip rescue $git_version = "unknown" end From b9698e8d2f3e897670adc475d1801336cf4406f0 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 18 Feb 2013 19:37:12 +1100 Subject: [PATCH 30/35] fix loading message am message bus stuff --- .../discourse/controllers/list_controller.js.coffee | 1 - app/assets/javascripts/discourse/models/topic_list.js.coffee | 1 + .../discourse/routes/filtered_list_route.js.coffee | 2 -- .../javascripts/discourse/templates/list/list.js.handlebars | 4 ---- .../javascripts/discourse/templates/list/topics.js.handlebars | 4 ++++ .../discourse/views/list/list_topics_view.js.coffee | 4 ++++ vendor/gems/message_bus/lib/message_bus/diagnostics.rb | 2 ++ vendor/gems/message_bus/lib/message_bus/rack/middleware.rb | 2 -- 8 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/list_controller.js.coffee b/app/assets/javascripts/discourse/controllers/list_controller.js.coffee index 987845cba..4be14f1d9 100644 --- a/app/assets/javascripts/discourse/controllers/list_controller.js.coffee +++ b/app/assets/javascripts/discourse/controllers/list_controller.js.coffee @@ -39,7 +39,6 @@ Discourse.ListController = Ember.Controller.extend Discourse.Presence, Discourse.TopicList.list(current).then (items) => @set('filterSummary', items.filter_summary) @set('filterMode', filterMode) - @set('allLoaded', true) unless items.more_topics_url @set('loading', false) deferred.resolve(items) diff --git a/app/assets/javascripts/discourse/models/topic_list.js.coffee b/app/assets/javascripts/discourse/models/topic_list.js.coffee index 9e0685dc3..6e9edbefa 100644 --- a/app/assets/javascripts/discourse/models/topic_list.js.coffee +++ b/app/assets/javascripts/discourse/models/topic_list.js.coffee @@ -9,6 +9,7 @@ window.Discourse.TopicList = Discourse.Model.extend Em.String.i18n('topics.no_' + @get('filter')) ).property('topics', 'topics@each', 'filter', 'loaded') + loadMoreTopics: -> promise = new RSVP.Promise() if moreUrl = @get('more_topics_url') diff --git a/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee b/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee index 38da79204..2b6273190 100644 --- a/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee +++ b/app/assets/javascripts/discourse/routes/filtered_list_route.js.coffee @@ -5,8 +5,6 @@ window.Discourse.FilteredListRoute = Discourse.Route.extend listController = @controllerFor('list') listController.set('canCreateTopic', false) listController.set('filterMode', '') - listController.set('allLoaded', false) - renderTemplate: -> @render 'listTopics', into: 'list', outlet: 'listView', controller: 'listTopics' diff --git a/app/assets/javascripts/discourse/templates/list/list.js.handlebars b/app/assets/javascripts/discourse/templates/list/list.js.handlebars index 75ccca731..c294ab930 100644 --- a/app/assets/javascripts/discourse/templates/list/list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/list.js.handlebars @@ -35,10 +35,6 @@ {{/if}} {{outlet listView}} - - {{#if controller.allLoaded}} -

{{{i18n topics.footer}}}

- {{/if}} diff --git a/app/assets/javascripts/discourse/templates/list/topics.js.handlebars b/app/assets/javascripts/discourse/templates/list/topics.js.handlebars index 04c16f888..49aaccef1 100644 --- a/app/assets/javascripts/discourse/templates/list/topics.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/topics.js.handlebars @@ -55,5 +55,9 @@ {{/if}} + {{#if view.allLoaded}} +

{{{i18n topics.footer}}}

+ {{/if}} + {{/if}} {{/unless}} diff --git a/app/assets/javascripts/discourse/views/list/list_topics_view.js.coffee b/app/assets/javascripts/discourse/views/list/list_topics_view.js.coffee index e5d97650a..cd50ddebf 100644 --- a/app/assets/javascripts/discourse/views/list/list_topics_view.js.coffee +++ b/app/assets/javascripts/discourse/views/list/list_topics_view.js.coffee @@ -18,6 +18,10 @@ window.Discourse.ListTopicsView = Ember.View.extend Discourse.Scrolling, Discour willDestroyElement: -> @unbindScrolling() + allLoaded: (-> + !@get('loading') && !@get('controller.content.more_topics_url') + ).property('loading', 'controller.content.more_topics_url') + didInsertElement: -> @bindScrolling() eyeline = new Discourse.Eyeline('.topic-list-item') diff --git a/vendor/gems/message_bus/lib/message_bus/diagnostics.rb b/vendor/gems/message_bus/lib/message_bus/diagnostics.rb index 8d8d34f8d..97883c8a1 100644 --- a/vendor/gems/message_bus/lib/message_bus/diagnostics.rb +++ b/vendor/gems/message_bus/lib/message_bus/diagnostics.rb @@ -32,6 +32,7 @@ class MessageBus::Diagnostics end MessageBus.subscribe('/_diagnostics/discover') do |msg| + MessageBus.on_connect.call msg.site_id if MessageBus.on_connect MessageBus.publish '/_diagnostics/process-discovery', { pid: Process.pid, process_name: $0, @@ -39,6 +40,7 @@ class MessageBus::Diagnostics uptime: (Time.now.to_f - start_time).to_i, hostname: hostname }, user_ids: [msg.data["user_id"]] + MessageBus.on_disconnect.call msg.site_id if MessageBus.on_disconnect end end end diff --git a/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb b/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb index 121d8144a..f240266a4 100644 --- a/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb +++ b/vendor/gems/message_bus/lib/message_bus/rack/middleware.rb @@ -9,8 +9,6 @@ class MessageBus::Rack::Middleware def self.start_listener unless @started_listener MessageBus.subscribe do |msg| - p msg.channel - p msg.message_id EM.next_tick do @@connection_manager.notify_clients(msg) if @@connection_manager end From dee014210e1c0e367b7ce59ebfc9bb00802fefc3 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Mon, 18 Feb 2013 19:59:15 +1100 Subject: [PATCH 31/35] trigger a build, travis segfaulted --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index f884e0966..b1c61b588 100644 --- a/Gemfile +++ b/Gemfile @@ -47,7 +47,7 @@ gem 'slim' # required for sidekiq-web gem 'therubyracer', require: 'v8' gem 'thin' -# Gem that enables support for plugins. It is required +# Gem that enables support for plugins. It is required. gem 'discourse_plugin', path: 'vendor/gems/discourse_plugin' # Discourse Plugins (optional) From 1a81c90076d41e036d50ee42883f8507b2eb2f35 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 18 Feb 2013 11:20:07 +0100 Subject: [PATCH 32/35] convert plain text in composer template to i18n --- .../templates/composer.js.handlebars | 12 +-- config/locales/client.en.yml | 87 ++++++++++--------- 2 files changed, 50 insertions(+), 49 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/composer.js.handlebars b/app/assets/javascripts/discourse/templates/composer.js.handlebars index e1c6d5417..55180cf3d 100644 --- a/app/assets/javascripts/discourse/templates/composer.js.handlebars +++ b/app/assets/javascripts/discourse/templates/composer.js.handlebars @@ -9,12 +9,12 @@
- + {{#if content.viewOpen}}
{{{content.actionTitle}}}:
- + {{#if content.editTitle}}
@@ -28,7 +28,7 @@ {{/if}} {{/unless}} -
+
{{/if}} @@ -41,7 +41,7 @@
{{#if Discourse.currentUser}} - {{{content.toggleText}}} + {{{content.toggleText}}}
{{/if}} @@ -54,7 +54,7 @@ {{#if view.loadingImage}}
- Uploading image {{view.uploadProgress}}% cancel + {{i18n image_selector.uploading_image}} {{view.uploadProgress}}% {{i18n cancel}}
{{/if}} @@ -69,7 +69,7 @@ {{i18n composer.saved}} {{else}} {{i18n composer.saving}} - {{/if}} + {{/if}}
{{i18n composer.saved_draft}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 1692cde10..46e7ee461 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3,9 +3,9 @@ en: js: - share: - topic: 'share a link to this topic' - post: 'share a link to this post' + share: + topic: 'share a link to this topic' + post: 'share a link to this post' edit: 'edit the title and category of this topic' not_implemented: "That feature hasn't been implemented yet, sorry!" @@ -61,7 +61,7 @@ en: invited_by: "Invited By" trust_level: "Trust Level" - change_username: + change_username: action: "change" title: "Change Username" confirm: "There could be consequences to changing your username. Are you absolutely sure you want to?" @@ -75,7 +75,7 @@ en: error: "There was an error changing your email. Perhaps that address is already in use?" success: "We've sent an email to that address. Please follow the confirmation instructions." - email: + email: title: "Email" instructions: "Your email will never be shown to the public." ok: "Looks good. We will email you to confirm." @@ -83,12 +83,12 @@ en: authenticated: "Your email has been authenticated by {{provider}}." frequency: "We'll only email you if we haven't seen you recently and you haven't already seen the thing we're emailing you about." - name: + name: title: "Name" instructions: "The longer version of your name; does not need to be unique." too_short: "Your name is too short." ok: "Your name looks good." - username: + username: title: "Username" #instructions: "People can mention you as @{{username}}. This is an unregistered nickname. You can register it at discourse.org." instructions: "People can mention you as @{{username}}." @@ -108,7 +108,7 @@ en: log_out: "Log Out" website: "Web Site" email_settings: "Email" - email_digests: + email_digests: title: "When I don't visit the site, send me an email digest of what's new" daily: "daily" weekly: "weekly" @@ -124,11 +124,11 @@ en: not_viewed: "I haven't viewed them yet" last_here: "they were posted since I was here last" after_n_days: - one: "they were posted in the last day" - other: "they were posted in the last {{count}} days" + one: "they were posted in the last day" + other: "they were posted in the last {{count}} days" after_n_weeks: - one: "they were posted in the last week" - other: "they were posted in the last {{count}} week" + one: "they were posted in the last week" + other: "they were posted in the last {{count}} week" auto_track_topics: "Automatically track topics I enter" auto_track_options: @@ -141,7 +141,7 @@ en: one: "after 1 minute" other: "after {{count}} minutes" - invited: + invited: title: "Invites" user: "Invited User" none: "{{username}} hasn't invited any users to the site." @@ -156,14 +156,14 @@ en: days_visited: "Days Visited" account_age_days: "Account age in days" - password: + password: title: "Password" too_short: "Your password is too short." ok: "Your password looks good." - ip_address: + ip_address: title: "Last IP Address" - avatar: + avatar: title: "Avatar" instructions: "We use Gravatar for avatars based on your email" @@ -213,7 +213,7 @@ en: invite: "Don't have an account yet?" failed: "Something went wrong, perhaps this email is already registered, try the forgot password link" - forgot_password: + forgot_password: title: "Forgot Password" action: "I forgot my password" invite: "Enter your username or email address, and we'll send you a password reset email." @@ -246,15 +246,15 @@ en: message: "Authenticating with Yahoo (make sure pop up blockers are not enabled)" composer: - saving_draft_tip: "saving" - saved_draft_tip: "saved" - saved_local_draft_tip: "saved locally" + saving_draft_tip: "saving" + saved_draft_tip: "saved" + saved_local_draft_tip: "saved locally" min_length: at_least: "enter at least {{n}} characters" more: "{{n}} to go..." - - save_edit: "Save Edit" + + save_edit: "Save Edit" reply: "Reply" create_topic: "Create Topic" create_pm: "Create Private Message" @@ -292,8 +292,9 @@ en: remote_tip: "enter address of an image in the form http://example.com/image.jpg" local_tip: "click to select an image from your device." upload: "Upload" + uploading_image: "Uploading image" - search: + search: title: "search for topics, posts, users, or categories" placeholder: "type your search terms here" no_results: "No results found." @@ -307,7 +308,7 @@ en: title: 'Favorite' help: 'add this topic to your favorites list' - topics: + topics: no_favorited: "You haven't favorited any topics yet. To favorite a topic, click or tap the star next to the title." no_unread: "You have no unread topics to read." no_new: "You have no new topics to read." @@ -316,7 +317,7 @@ en: no_popular: "There are no popular topics. That's sad." footer: "No more topics in this category. Browse all categories or view popular topics" - topic: + topic: create_in: 'Create {{categoryName}} Topic' create: 'Create Topic' create_long: 'Create a new Topic' @@ -361,7 +362,7 @@ en: "1_2": 'You will be notified only if someone mentions your @name or replies to your post.' "0": 'You are ignoring all notifications on this topic.' "0_2": 'You are ignoring all notifications on this topic.' - watching: + watching: title: "Watching" description: "same as Tracking, plus you will be notified of all new posts." tracking: @@ -388,11 +389,11 @@ en: multi_select: "Toggle Multi-Select" convert_to_topic: "Convert to Regular Topic" - reply: + reply: title: 'Reply' help: 'begin composing a reply to this topic' - share: + share: title: 'Share' help: 'share a link to this topic' @@ -406,11 +407,11 @@ en: success: "Thanks! We've invited that user to participate in this private conversation." error: "Sorry there was an error inviting that user." - invite_reply: + invite_reply: title: 'Invite Friends to Reply' help: 'send invitations to friends so they can reply to this topic with a single click' email: "We'll send your friend a brief email allowing them to reply to this topic by clicking a link." - email_placeholder: 'email address' + email_placeholder: 'email address' success: "Thanks! We mailed out an invitation to {{email}}. We'll let you know when they redeem your invitation. Check the invitations tab on your user page to keep track of who you've invited." error: "Sorry we couldn't invite that person. Perhaps they are already a user?" @@ -425,7 +426,7 @@ en: title: "Move Selected Posts" topic_name: "New Topic Name:" error: "Sorry, there was an error moving those posts." - instructions: + instructions: one: "You are about to create a new topic and populate it with the post you've selected." other: "You are about to create a new topic and populate it with the {{count}} posts you've selected." @@ -440,7 +441,7 @@ en: other: "You have selected {{count}} posts." post: - reply: "Replying to {{link}} by {{replyAvatar}} {{username}}" + reply: "Replying to {{link}} by {{replyAvatar}} {{username}}" reply_topic: "Reply to {{link}}" edit: "Edit {{link}}" in_reply_to: "in reply to" @@ -489,13 +490,13 @@ en: one: "1 person {{long_form}}" other: "{{count}} people {{long_form}}" - edits: + edits: one: 1 edit other: "{{count}} edits" zero: no edits delete: - confirm: + confirm: one: "Are you sure you want to delete that post?" other: "Are you sure you want to delete all those posts?" @@ -553,27 +554,27 @@ en: categories_list: "Categories List" filters: - popular: + popular: title: "Popular" help: "the most popular recent topics" - favorited: + favorited: title: "Favorited" help: "topics you marked as favorites" - read: + read: title: "Read" help: "topics you've read" - categories: + categories: title: "Categories" title_in: "Category - {{categoryName}}" help: "all topics grouped by category" unread: - title: + title: zero: "Unread" one: "Unread (1)" other: "Unread ({{count}})" help: "tracked topics with unread posts" - new: - title: + new: + title: zero: "New" one: "New (1)" other: "New ({{count}})" @@ -592,7 +593,7 @@ en: admin_js: type_to_filter: "type to filter..." - admin: + admin: title: 'Discourse Admin' dashboard: 'Admin Dashboard' @@ -606,7 +607,7 @@ en: delete_title: "delete post (if its the first post delete topic)" flagged_by: "Flagged by" - customize: + customize: title: "Customize" header: "Header" css: "Stylesheet" From 859a6e18b59a7026ef6a61a4049834e737881641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 18 Feb 2013 19:44:11 +0100 Subject: [PATCH 33/35] changed links to localhost to clickable ones --- docs/VAGRANT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/VAGRANT.md b/docs/VAGRANT.md index 9b758479e..f4f49aef6 100644 --- a/docs/VAGRANT.md +++ b/docs/VAGRANT.md @@ -73,7 +73,7 @@ Once your VM is up to date, you can start a rails instance using the following c bundle exec rails server ``` -In a few seconds, rails will start serving pages. To access them, open a web browser to http://localhost:4000 - if it all worked you should see discourse! Congratulations, you are ready to start working! +In a few seconds, rails will start serving pages. To access them, open a web browser to [http://localhost:4000](http://localhost:4000) - if it all worked you should see discourse! Congratulations, you are ready to start working! You can now edit files on your local file system, using your favorite text editor or IDE. When you reload your web browser, it should have the latest changes. @@ -109,7 +109,7 @@ To start mailcatcher, run the following command in the vagrant image: mailcatcher --http-ip 0.0.0.0 ``` -Then in a browser, go to http://localhost:4080 +Then in a browser, go to [http://localhost:4080](http://localhost:4080) Sent emails will be received by mailcatcher and shown in its web ui. From 3be7bde40125c490bb8b799a6dc92a5d1f9213c7 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Tue, 19 Feb 2013 10:33:07 +1100 Subject: [PATCH 34/35] stop onebox rapid fire --- .../discourse/models/onebox.js.coffee | 17 +++++++++++++---- spec/javascripts/onebox.js.coffee | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 spec/javascripts/onebox.js.coffee diff --git a/app/assets/javascripts/discourse/models/onebox.js.coffee b/app/assets/javascripts/discourse/models/onebox.js.coffee index 60e16699d..f82d1d1a6 100644 --- a/app/assets/javascripts/discourse/models/onebox.js.coffee +++ b/app/assets/javascripts/discourse/models/onebox.js.coffee @@ -13,17 +13,26 @@ Discourse.Onebox = (-> null lookupCache = (url) -> - localCache[url] + cached = localCache[url] + if cached && cached.then # its a promise + null + else + cached lookup = (url, refresh, callback) -> - cached = lookupCache(url) unless refresh + cached = localCache[url] + cached = null if refresh && cached && !cached.then if cached - callback(cached) + if cached.then + cached.then(callback(lookupCache(url))) + else + callback(cached) return false else - $.get "/onebox", url: url, refresh: refresh, (html) -> + cache(url, $.get "/onebox", url: url, refresh: refresh, (html) -> cache(url,html) callback(html) + ) return true load = (e, refresh=false) -> diff --git a/spec/javascripts/onebox.js.coffee b/spec/javascripts/onebox.js.coffee new file mode 100644 index 000000000..dfd28eae8 --- /dev/null +++ b/spec/javascripts/onebox.js.coffee @@ -0,0 +1,14 @@ +describe "Discourse.Onebox", -> + + beforeEach -> + spyOn($, 'ajax').andCallThrough() + + it "Stops rapid calls with cache true", -> + Discourse.Onebox.lookup('http://bla.com', true, (c) -> c) + Discourse.Onebox.lookup('http://bla.com', true, (c) -> c) + expect($.ajax.calls.length).toBe(1) + + it "Stops rapid calls with cache false", -> + Discourse.Onebox.lookup('http://bla.com/a', false, (c) -> c) + Discourse.Onebox.lookup('http://bla.com/a', false, (c) -> c) + expect($.ajax.calls.length).toBe(1) From d520771c736fe7da47180af2d4d0ebcd13e7af63 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Tue, 19 Feb 2013 11:02:00 +1100 Subject: [PATCH 35/35] move stylesheet cache into the upload dir so its less crazy to sync across servers --- app/assets/javascripts/discourse/models/onebox.js.coffee | 3 --- app/models/site_customization.rb | 3 ++- spec/models/site_customization_spec.rb | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/discourse/models/onebox.js.coffee b/app/assets/javascripts/discourse/models/onebox.js.coffee index f82d1d1a6..815d8b25e 100644 --- a/app/assets/javascripts/discourse/models/onebox.js.coffee +++ b/app/assets/javascripts/discourse/models/onebox.js.coffee @@ -7,9 +7,6 @@ Discourse.Onebox = (-> cache = (url, contents) -> localCache[url] = contents - - #if localStorage && localStorage.setItem - # localStorage.setItme null lookupCache = (url) -> diff --git a/app/models/site_customization.rb b/app/models/site_customization.rb index ed19ada58..fe338c8ff 100644 --- a/app/models/site_customization.rb +++ b/app/models/site_customization.rb @@ -1,7 +1,8 @@ class SiteCustomization < ActiveRecord::Base ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd' - CACHE_PATH = 'stylesheet-cache' + # placing this in uploads to ease deployment rules + CACHE_PATH = 'uploads/stylesheet-cache' @lock = Mutex.new before_create do diff --git a/spec/models/site_customization_spec.rb b/spec/models/site_customization_spec.rb index 004e71e4d..0b4b50a50 100644 --- a/spec/models/site_customization_spec.rb +++ b/spec/models/site_customization_spec.rb @@ -58,7 +58,7 @@ describe SiteCustomization do it 'should allow me to lookup a filename containing my preview stylesheet' do SiteCustomization.custom_stylesheet(customization.key).should == - "" + "" end it 'should fix stylesheet files after changing the stylesheet' do