diff --git a/Gemfile b/Gemfile index 571e4098..120e0895 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,11 @@ gem 'paranoia' gem 'coffee-rails' gem 'rails-jquery-autocomplete' +# Form enhancements +gem 'redcarpet' #markdown formatting +gem 'acts_as_list' #sortables +gem 'tribute' # @mentions + # SEO gem 'meta-tags' @@ -50,10 +55,6 @@ gem 'medium-editor-rails' gem 'chartkick' gem 'slack-notifier' -# Form enhancements -gem 'redcarpet' #markdown formatting -gem 'acts_as_list' #sortables - # Analytics gem 'mixpanel-ruby' gem 'barnes' diff --git a/Gemfile.lock b/Gemfile.lock index 8f9da3b6..daa92c7c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,45 +34,45 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.2.2) - actionpack (= 5.2.2) + actioncable (5.2.2.1) + actionpack (= 5.2.2.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.2) - actionpack (= 5.2.2) - actionview (= 5.2.2) - activejob (= 5.2.2) + actionmailer (5.2.2.1) + actionpack (= 5.2.2.1) + actionview (= 5.2.2.1) + activejob (= 5.2.2.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.2) - actionview (= 5.2.2) - activesupport (= 5.2.2) + actionpack (5.2.2.1) + actionview (= 5.2.2.1) + activesupport (= 5.2.2.1) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.2) - activesupport (= 5.2.2) + actionview (5.2.2.1) + activesupport (= 5.2.2.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) active_record_union (1.3.0) activerecord (>= 4.0) - activejob (5.2.2) - activesupport (= 5.2.2) + activejob (5.2.2.1) + activesupport (= 5.2.2.1) globalid (>= 0.3.6) - activemodel (5.2.2) - activesupport (= 5.2.2) - activerecord (5.2.2) - activemodel (= 5.2.2) - activesupport (= 5.2.2) + activemodel (5.2.2.1) + activesupport (= 5.2.2.1) + activerecord (5.2.2.1) + activemodel (= 5.2.2.1) + activesupport (= 5.2.2.1) arel (>= 9.0) - activestorage (5.2.2) - actionpack (= 5.2.2) - activerecord (= 5.2.2) + activestorage (5.2.2.1) + actionpack (= 5.2.2.1) + activerecord (= 5.2.2.1) marcel (~> 0.3.1) - activesupport (5.2.2) + activesupport (5.2.2.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -85,7 +85,7 @@ GEM ast (2.4.0) authority (3.3.0) activesupport (>= 3.0.0) - autoprefixer-rails (9.4.8) + autoprefixer-rails (9.4.10.2) execjs aws-eventstream (1.0.1) aws-partitions (1.131.0) @@ -150,7 +150,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.5) connection_pool (2.2.2) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -184,7 +184,7 @@ GEM db_text_search (0.3.0) activerecord (>= 4.1.15, < 6.0) debug_inspector (0.0.3) - devise (4.5.0) + devise (4.6.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0, < 6.0) @@ -201,7 +201,7 @@ GEM railties (>= 3.0.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) - ffi (1.9.25) + ffi (1.10.0) filesize (0.2.0) flamegraph (0.9.5) font-awesome-rails (4.7.0.4) @@ -236,7 +236,7 @@ GEM activesupport (>= 2) nokogiri (>= 1.4) htmlentities (4.3.4) - i18n (1.5.2) + i18n (1.6.0) concurrent-ruby (~> 1.0) inline_svg (1.3.1) activesupport (>= 3.0) @@ -314,7 +314,7 @@ GEM notiffany (0.1.1) nenv (~> 0.1) shellany (~> 0.0) - onebox (1.8.78) + onebox (1.8.82) htmlentities (~> 4.3) moneta (~> 1.0) multi_json (~> 1.11) @@ -328,16 +328,17 @@ GEM mime-types mimemagic (~> 0.3.0) terrapin (~> 0.6.0) - parallel (1.12.1) + parallel (1.14.0) paranoia (2.4.1) activerecord (>= 4.0, < 5.3) - parser (2.5.3.0) + parser (2.6.0.0) ast (~> 2.4.0) pg (0.21.0) powerpack (0.1.2) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) + psych (3.1.0) public_suffix (3.0.3) puma (3.12.0) puma-heroku (1.0.0) @@ -354,18 +355,18 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.2) - actioncable (= 5.2.2) - actionmailer (= 5.2.2) - actionpack (= 5.2.2) - actionview (= 5.2.2) - activejob (= 5.2.2) - activemodel (= 5.2.2) - activerecord (= 5.2.2) - activestorage (= 5.2.2) - activesupport (= 5.2.2) + rails (5.2.2.1) + actioncable (= 5.2.2.1) + actionmailer (= 5.2.2.1) + actionpack (= 5.2.2.1) + actionview (= 5.2.2.1) + activejob (= 5.2.2.1) + activemodel (= 5.2.2.1) + activerecord (= 5.2.2.1) + activestorage (= 5.2.2.1) + activesupport (= 5.2.2.1) bundler (>= 1.3.0) - railties (= 5.2.2) + railties (= 5.2.2.1) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.4) actionpack (>= 5.0.1.x) @@ -399,9 +400,9 @@ GEM sass-rails (>= 4.0, < 6) rails_serve_static_assets (0.0.5) rails_stdout_logging (0.0.5) - railties (5.2.2) - actionpack (= 5.2.2) - activesupport (= 5.2.2) + railties (5.2.2.1) + actionpack (= 5.2.2.1) + activesupport (= 5.2.2.1) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -415,9 +416,9 @@ GEM redis (4.1.0) regexp_parser (1.3.0) remotipart (1.4.2) - responders (2.4.0) - actionpack (>= 4.2.0, < 5.3) - railties (>= 4.2.0, < 5.3) + responders (2.4.1) + actionpack (>= 4.2.0, < 6.0) + railties (>= 4.2.0, < 6.0) rinku (2.0.5) rmagick (2.16.0) rspec (3.8.0) @@ -444,11 +445,12 @@ GEM rspec-mocks (~> 3.8.0) rspec-support (~> 3.8.0) rspec-support (3.8.0) - rubocop (0.62.0) + rubocop (0.65.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.5, != 2.5.1.1) powerpack (~> 0.1) + psych (>= 3.1.0) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.4.0) @@ -472,8 +474,8 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - sassc (2.0.0) - ffi (~> 1.9.6) + sassc (2.0.1) + ffi (~> 1.9) rake sassc-rails (2.1.0) railties (>= 4.0.0) @@ -523,8 +525,30 @@ GEM climate_control (>= 0.0.3, < 1.0) thor (0.20.3) thread_safe (0.3.6) + thredded (0.16.9) + active_record_union (>= 1.3.0) + autoprefixer-rails + db_text_search (~> 0.3.0) + friendly_id + html-pipeline + htmlentities + inline_svg + kaminari + kramdown (>= 2.0.0) + kramdown-parser-gfm + nokogiri + onebox (~> 1.8, >= 1.8.48) + pundit (>= 1.1.0) + rails (>= 4.2.10) + rb-gravatar + rinku + sanitize + sassc-rails (>= 2.0.0) + sprockets-es6 + timeago_js tilt (2.0.9) timeago_js (3.0.2) + tribute (3.6.0.0) tzinfo (1.2.5) thread_safe (~> 0.1) tzinfo-data (1.2018.9) @@ -618,7 +642,8 @@ DEPENDENCIES stackprof stripe stripe_event - thredded! + thredded + tribute tzinfo-data uglifier (>= 1.3.0) web-console diff --git a/README.rdoc b/README.rdoc index 0b13b7d9..3051b7d1 100644 --- a/README.rdoc +++ b/README.rdoc @@ -80,7 +80,7 @@ You should now see a copy of the site running locally at http://localhost:3000/! == Deployment to notebook.ai -Deployment to the live stage will only be done by approved developers, and consists of a deployment of +Deployment to the live production servers at www.Notebook.ai will only be done by approved developers, and consists of a deployment of - deploy github to staging (done only by approved developers) diff --git a/app/assets/images/card-headers/documents.jpg b/app/assets/images/card-headers/documents.jpg new file mode 100644 index 00000000..d5b6584b Binary files /dev/null and b/app/assets/images/card-headers/documents.jpg differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 43b08cbe..e1cc8dbc 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,4 +13,5 @@ //= require autocomplete-rails //= require cocoon //= require medium-editor +//= require tribute //= require_tree . diff --git a/app/assets/javascripts/content_filtering.js b/app/assets/javascripts/content_filtering.js new file mode 100644 index 00000000..1fa26886 --- /dev/null +++ b/app/assets/javascripts/content_filtering.js @@ -0,0 +1,17 @@ +$(document).ready(function () { + // Override the :contains() selector to make it case-insensitive + $.expr[":"].contains = $.expr.createPseudo(function(arg) { + return function( elem ) { + return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0; + }; + }); + + $('#js-content-name-filter').keyup(function (e) { + var search_query = $(this).val(); + var content_list = $('.js-content-cards-list > .js-content-card-container'); + + // Show everything, then hide what doesn't match + content_list.hide(); + content_list.find(".js-content-name:contains(" + search_query + ")").closest('.js-content-card-container').show(); + }); +}); diff --git a/app/assets/javascripts/content_linking.js b/app/assets/javascripts/content_linking.js deleted file mode 100644 index bfa77840..00000000 --- a/app/assets/javascripts/content_linking.js +++ /dev/null @@ -1,76 +0,0 @@ -$(document).ready(function () { - function add_link_bar(link_bar_container) { - $('.content-field-link-bar-container').html(''); - $(link_bar_container).html($('#content-field-link-bar-template').html()) - $(link_bar_container).find('.content-field-link-bar').show(); - $(link_bar_container).find('.dropdown-trigger').dropdown({coverTrigger: false}); - $(link_bar_container).find('.tooltipped').tooltip({ enterDelay: 50 }); - add_link_bar_click_handlers(); - } - - $('.content-field').find('textarea').focus(function (focus_event) { - var focused_text_field = $(focus_event.target); - var parent_content_field = focused_text_field.closest('.content-field'); - - $('.show-when-focused').hide(); - parent_content_field.find('.show-when-focused').show(); - - $('.content-field').removeClass('focused'); - parent_content_field.addClass('focused'); - $('.content-field-link-bar').hide(); - parent_content_field.find('.content-field-link-bar').show(); - - add_link_bar(parent_content_field.find('.content-field-link-bar-container')); - }); - - // um sir excuse me why does this need to be in a setTimeout? It doesn't work - // unless it is... for whatever reason. Here be dragons. - setTimeout(function () { - $('.content-field').find('.chips input').focus(function (focus_event) { - var focused_text_field = $(focus_event.target); - var parent_content_field = focused_text_field.closest('.content-field'); - - $('.show-when-focused').hide(); - parent_content_field.find('.show-when-focused').show(); - - $('.content-field').removeClass('focused'); - parent_content_field.addClass('focused'); - $('.content-field-link-bar').hide(); - parent_content_field.find('.content-field-link-bar').show(); - }); - }, 100); - - function add_link_bar_click_handlers() { - $('.js-content-link-option').click(function (click_event) { - var selected_option = $(click_event.target); - var textarea_to_modify = selected_option - .closest('.content-field') - .find('textarea'); - var current_textarea_value = textarea_to_modify.val(); - - var token_to_insert = [ - '[[', - selected_option.data('content-type'), - '-', - selected_option.data('content-id'), - ']]' - ].join('') - - // If the final character of the textarea is a newline or a space - // character, then we want to just append our token. Otherwise, we want - // to prepend our token with a space so it is properly separated. - var final_letter_presently = current_textarea_value.slice(-1); - if (final_letter_presently != ' ' && final_letter_presently != "\n") { - // Prepend a space - token_to_insert = ' ' + token_to_insert; - } - - // Make the change! - textarea_to_modify.val(current_textarea_value + token_to_insert); - - // Don't jump to the top of the page after clicking! - click_event.stopPropagation(); - return false; - }); - } -}); diff --git a/app/assets/javascripts/field_focusing.js b/app/assets/javascripts/field_focusing.js new file mode 100644 index 00000000..7e3cee57 --- /dev/null +++ b/app/assets/javascripts/field_focusing.js @@ -0,0 +1,31 @@ +$(document).ready(function () { + $('.content-field').find('textarea').focus(function (focus_event) { + var focused_text_field = $(focus_event.target); + var parent_content_field = focused_text_field.closest('.content-field'); + + $('.show-when-focused').hide(); + parent_content_field.find('.show-when-focused').show(); + + $('.content-field').removeClass('focused'); + parent_content_field.addClass('focused'); + $('.content-field-link-bar').hide(); + parent_content_field.find('.content-field-link-bar').show(); + }); + + // um sir excuse me why does this need to be in a setTimeout? It doesn't work + // unless it is... for whatever reason. Here be dragons. + setTimeout(function () { + $('.content-field').find('.chips input').focus(function (focus_event) { + var focused_text_field = $(focus_event.target); + var parent_content_field = focused_text_field.closest('.content-field'); + + $('.show-when-focused').hide(); + parent_content_field.find('.show-when-focused').show(); + + $('.content-field').removeClass('focused'); + parent_content_field.addClass('focused'); + $('.content-field-link-bar').hide(); + parent_content_field.find('.content-field-link-bar').show(); + }); + }, 100); +}); diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 9aa7a8b5..e0b1691b 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -13,5 +13,6 @@ *= require font-awesome *= require medium-editor/medium-editor *= require medium-editor/themes/beagle + *= require tribute *= require_tree . */ diff --git a/app/assets/stylesheets/buttons.scss b/app/assets/stylesheets/buttons.scss new file mode 100644 index 00000000..252ddb99 --- /dev/null +++ b/app/assets/stylesheets/buttons.scss @@ -0,0 +1,5 @@ +.btn { + .material-icons { + vertical-align: top; + } +} diff --git a/app/assets/stylesheets/content_linking.scss b/app/assets/stylesheets/content_linking.scss deleted file mode 100644 index 2500e21b..00000000 --- a/app/assets/stylesheets/content_linking.scss +++ /dev/null @@ -1,3 +0,0 @@ -.content-field-link-bar, .dropdown-content { - display: none; -} diff --git a/app/assets/stylesheets/content_linking.scss.erb b/app/assets/stylesheets/content_linking.scss.erb new file mode 100644 index 00000000..e2c4f1b6 --- /dev/null +++ b/app/assets/stylesheets/content_linking.scss.erb @@ -0,0 +1,6 @@ +<% Rails.application.config.content_types[:all].each do |content_type| %> + .<%= content_type.name.downcase %>-link { + color: <%= content_type.hex_color %>; + border-bottom: 1px dashed <%= content_type.hex_color %>; + } +<% end %> \ No newline at end of file diff --git a/app/assets/stylesheets/content_types.scss b/app/assets/stylesheets/content_types.scss index a5460d31..d4ed612c 100644 --- a/app/assets/stylesheets/content_types.scss +++ b/app/assets/stylesheets/content_types.scss @@ -25,3 +25,12 @@ right: 7px; z-index: 3; } + +.centered-card-content { + margin: 0; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -80%); + text-align: center; +} diff --git a/app/assets/stylesheets/layers.scss b/app/assets/stylesheets/layers.scss index d656c7df..2d968824 100644 --- a/app/assets/stylesheets/layers.scss +++ b/app/assets/stylesheets/layers.scss @@ -7,3 +7,8 @@ .slider .slides li.active { z-index: 1 !important; } + +.slider { + height: 140px; + overflow: hidden; +} \ No newline at end of file diff --git a/app/authorizers/document_authorizer.rb b/app/authorizers/document_authorizer.rb index 144e0f39..03d2a952 100644 --- a/app/authorizers/document_authorizer.rb +++ b/app/authorizers/document_authorizer.rb @@ -1,4 +1,8 @@ class DocumentAuthorizer < ApplicationAuthorizer + def self.creatable_by?(user) + true + end + def readable_by?(user) [ resource.user_id == user.id diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1a31a429..377c2ec5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -69,4 +69,27 @@ class ApplicationController < ActionController::Base 0 end end + + def cache_linkable_content_for_each_content_type + linkable_classes = Rails.application.config.content_types[:all].map(&:name) & current_user.user_content_type_activators.pluck(:content_type) + linkable_classes -= ["Universe"] + + @linkables_cache = {} + linkable_classes.each do |class_name| + # class_name = "Character" + + @linkables_cache[class_name] = current_user.send("linkable_#{class_name.downcase.pluralize}") + .in_universe(@universe_scope) + + if @content.present? && @content.persisted? + @linkables_cache[class_name] = @linkables_cache[class_name] + .in_universe(@content.universe) + .reject { |content| content.class.name == class_name && content.id == @content.id } + end + + @linkables_cache[class_name] = @linkables_cache[class_name].sort_by(&:name) + .map { |c| [c.name, c.id] } + .compact + end + end end diff --git a/app/controllers/content_controller.rb b/app/controllers/content_controller.rb index 58b4a542..a4070e45 100644 --- a/app/controllers/content_controller.rb +++ b/app/controllers/content_controller.rb @@ -6,7 +6,7 @@ class ContentController < ApplicationController # old content to the new attributes styling. before_action :migrate_old_style_field_values, only: [:show, :edit] - before_action :populate_linkable_content_for_each_content_type, only: [:new, :edit] + before_action :cache_linkable_content_for_each_content_type, only: [:new, :edit] before_action :set_attributes_content_type, only: [:attributes] @@ -23,11 +23,11 @@ class ContentController < ApplicationController @content_type_class.attribute_categories(current_user) if @universe_scope.present? && @content_type_class != Universe - @content = @universe_scope.send(pluralized_content_name) + @content = @universe_scope.send(pluralized_content_name).includes(:page_tags, :image_uploads) else @content = ( - current_user.send(pluralized_content_name).includes(:page_tags) + - current_user.send("contributable_#{pluralized_content_name}").includes(:page_tags) + current_user.send(pluralized_content_name).includes(:page_tags, :image_uploads) + + current_user.send("contributable_#{pluralized_content_name}").includes(:page_tags, :image_uploads) ) unless @content_type_class == Universe @@ -42,12 +42,13 @@ class ContentController < ApplicationController @page_tags = PageTag.where( page_type: @content_type_class.name, page_id: @content.pluck(:id) - ) + ).order(:tag) if params.key?(:slug) - filtered_page_tags = @page_tags.where(slug: params[:slug]) - @content.select! { |content| filtered_page_tags.pluck(:page_id).include?(content.id) } + @filtered_page_tags = @page_tags.where(slug: params[:slug]) + @content.select! { |content| @filtered_page_tags.pluck(:page_id).include?(content.id) } end + @page_tags = @page_tags.uniq(&:tag) @content = @content.sort_by(&:name) respond_to do |format| @@ -182,7 +183,7 @@ class ContentController < ApplicationController upload_files params['image_uploads'], content_type.name, @content.id end - update_page_tags + update_page_tags if @content.respond_to?(:page_tags) successful_response(content_creation_redirect_url, t(:create_success, model_name: @content.try(:name).presence || humanized_model_name)) else @@ -214,7 +215,7 @@ class ContentController < ApplicationController end end - update_page_tags + update_page_tags if @content.respond_to?(:page_tags) if @content.user == current_user # todo this needs some extra validation probably to ensure each attribute is one associated with this page @@ -404,29 +405,6 @@ class ContentController < ApplicationController TemporaryFieldMigrationService.migrate_fields_for_content(content, current_user) if content.present? end - def populate_linkable_content_for_each_content_type - linkable_classes = Rails.application.config.content_types[:all].map(&:name) & current_user.user_content_type_activators.pluck(:content_type) - linkable_classes -= ["Universe"] - - @linkables_cache = {} - linkable_classes.each do |class_name| - # class_name = "Character" - - @linkables_cache[class_name] = current_user.send("linkable_#{class_name.downcase.pluralize}") - .in_universe(@universe_scope) - - if @content.present? && @content.persisted? - @linkables_cache[class_name] = @linkables_cache[class_name] - .in_universe(@content.universe) - .reject { |content| content.class.name == class_name && content.id == @content.id } - end - - @linkables_cache[class_name] = @linkables_cache[class_name].sort_by(&:name) - .map { |c| [c.name, c.id] } - .compact - end - end - def valid_content_types Rails.application.config.content_types[:all] end @@ -522,7 +500,7 @@ class ContentController < ApplicationController @navbar_actions << { label: "New #{content_type.name.downcase}", href: main_app.new_polymorphic_path(content_type) - } + } if user_signed_in? && current_user.can_create?(content_type) discussions_link = ForumsLinkbuilderService.worldbuilding_url(content_type) if discussions_link.present? @@ -550,11 +528,11 @@ class ContentController < ApplicationController href: main_app.polymorphic_path(content_type) } end - # - # @navbar_actions << { - # label: "New #{content_type.name.downcase}", - # href: main_app.new_polymorphic_path(content_type) - # } + + @navbar_actions << { + label: "New #{content_type.name.downcase}", + href: main_app.new_polymorphic_path(content_type) + } end end diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb index 0d41ab04..b07cd8f9 100644 --- a/app/controllers/documents_controller.rb +++ b/app/controllers/documents_controller.rb @@ -6,6 +6,8 @@ class DocumentsController < ApplicationController before_action :set_navbar_actions, except: [:edit] before_action :set_footer_visibility, only: [:edit] + before_action :cache_linkable_content_for_each_content_type, only: [:edit] + layout 'editor', only: [:edit] def index diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 94379ecf..f1612ee3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -5,8 +5,8 @@ class UsersController < ApplicationController def show @sidenav_expansion = 'my account' - - @user = User.find_by(id: params[:id]) + + @user = User.find_by(user_params) return redirect_to(root_path, notice: 'That user does not exist.') if @user.nil? @content = @user.public_content.select { |type, list| list.any? } @@ -70,4 +70,10 @@ class UsersController < ApplicationController notifier.ping ":bomb: :bomb: :bomb: #{user.email.split('@').first}@ (##{user.id}) just deleted their account." end + + private + + def user_params + params.permit(:id, :username) + end end diff --git a/app/models/concerns/has_image_uploads.rb b/app/models/concerns/has_image_uploads.rb index 113e94fc..129a234e 100644 --- a/app/models/concerns/has_image_uploads.rb +++ b/app/models/concerns/has_image_uploads.rb @@ -4,17 +4,10 @@ module HasImageUploads extend ActiveSupport::Concern included do - has_many :image_uploads + has_many :image_uploads, as: :content # todo: dependent: :destroy # todo: destroy from s3 on destroy - def image_uploads - ImageUpload.where( - content_type: self.class.name, - content_id: self.id - ) - end - def public_image_uploads self.image_uploads.where(privacy: 'public') end diff --git a/app/models/document.rb b/app/models/document.rb index 893473bd..c8d4f69e 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -25,4 +25,15 @@ class Document < ApplicationRecord def universe_field_value #todo when documents belong to a universe end + + def reading_estimate + words = (self.body || "").split(/\s+/).count + minutes = 1 + (words / 200).to_i + + "~#{minutes} minute read" + end + + def description + self.body + end end diff --git a/app/models/image_upload.rb b/app/models/image_upload.rb index d9676e25..6b9044c2 100644 --- a/app/models/image_upload.rb +++ b/app/models/image_upload.rb @@ -1,5 +1,6 @@ class ImageUpload < ApplicationRecord belongs_to :user + belongs_to :content, polymorphic: true has_attached_file :src, path: 'content/uploads/:style/:filename', diff --git a/app/models/user.rb b/app/models/user.rb index 4b05723c..a30c3da3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,7 +11,12 @@ class User < ApplicationRecord include HasContent include Authority::UserAbilities - validates_uniqueness_of :username, allow_nil: true, allow_blank: true + validates :username, + uniqueness: true, + allow_nil: true, + allow_blank: true, + length: { maximum: 40 }, + format: /\A[A-Za-z0-9\-_\$\+\!\*]+\z/ has_many :subscriptions, dependent: :destroy has_many :billing_plans, through: :subscriptions @@ -189,6 +194,14 @@ class User < ApplicationRecord found_key.user end + def profile_url + if self.username.present? + Rails.application.routes.url_helpers.profile_by_username_path(username: self.username) + else + Rails.application.routes.url_helpers.user_path(id: self.id) + end + end + private # Attributes that are non-public, and should be blacklisted from any public diff --git a/app/services/content_formatter_service.rb b/app/services/content_formatter_service.rb index 32a79399..9828146e 100644 --- a/app/services/content_formatter_service.rb +++ b/app/services/content_formatter_service.rb @@ -20,7 +20,7 @@ class ContentFormatterService < Service def self.show(text:, viewing_user: User.new) # We want to evaluate markdown first, because the markdown engine also happens # to strip out HTML tags. So: markdown, _then_ insert content links. - formatted_text = markdown.render(text).html_safe + formatted_text = markdown.render(text || '').html_safe substitute_content_links(formatted_text, viewing_user).html_safe end @@ -67,11 +67,11 @@ class ContentFormatterService < Service end def self.link_template(content_model) - chip_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: "content_link #{content_model.class}-link") } + inline_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: "content_link #{content_model.class.name.downcase}-link") } end def self.private_link_template(content_model) - chip_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: 'grey-text content_link disabled') } + inline_template(content_model.class) { link_to(content_model.name, link_for(content_model), class: 'grey-text content_link disabled') } end def self.unknown_link_template(attempted_key) @@ -95,12 +95,20 @@ class ContentFormatterService < Service end end + def self.inline_template(class_model=nil) + content_tag(:span, class: 'inline-link') do + content_tag(:span, class: class_model ? "#{class_model.color}-text" : '') do + yield + end + end + end + # This is a hack until I figure out how to include polymorphic paths in a service model. #todo remove this def self.link_for(content_model) [ Rails.env.production? ? 'https://' : 'http://', - 'www.notebook.ai', # Rails.application.routes.default_url_options[:host], + Rails.env.production? ? 'www.notebook.ai' : 'localhost:3000', # Rails.application.routes.default_url_options[:host]? '/plan/', content_model.class.name.downcase.pluralize, '/', diff --git a/app/services/page_tag_service.rb b/app/services/page_tag_service.rb index 46d07370..cc91a6e2 100644 --- a/app/services/page_tag_service.rb +++ b/app/services/page_tag_service.rb @@ -16,13 +16,13 @@ class PageTagService < Service when Creature.name ["Domesticated", "Wild", "Humanoid"] when Deity.name - ["Good", "Evil"] + ["Good", "Evil", "Neutral"] when Flora.name - ["Floral", "Weed"] + ["Floral", "Weed", "Tree", "Bush"] when Government.name ["Republic", "Democracy", "Monarchy", "Dictatorship"] when Group.name - ["Good", "Evil"] + ["Good", "Evil", "Secret"] when Item.name ["Weapon", "Armor", "Artifact", "Relic"] when Job.name diff --git a/app/services/subscription_service.rb b/app/services/subscription_service.rb index 75282b56..de365cac 100644 --- a/app/services/subscription_service.rb +++ b/app/services/subscription_service.rb @@ -13,7 +13,9 @@ class SubscriptionService < Service # Add any one-time referral bonuses add_any_referral_bonuses(user, plan_id) - user.update(selected_billing_plan_id: related_plan.id) + # We intentionally skip callbacks on this to ensure the billing plan changes even on invalid users + user.update_column(:selected_billing_plan_id, related_plan.id) + user.subscriptions.create( billing_plan: related_plan, start_date: DateTime.now, diff --git a/app/views/cards/ui/_alert.html.erb b/app/views/cards/ui/_alert.html.erb index 01324c87..a60de6da 100644 --- a/app/views/cards/ui/_alert.html.erb +++ b/app/views/cards/ui/_alert.html.erb @@ -1,7 +1,7 @@ <% if alert %> <%= content_for :javascript do %> M.toast({ - html: "<%= alert.gsub('"', '\"').html_safe %>", + html: "<%= alert.gsub('"', '\"').gsub("\n", ' ').html_safe %>", classes: 'rounded' }) <% end %> diff --git a/app/views/cards/ui/_notice.html.erb b/app/views/cards/ui/_notice.html.erb index da6c047e..c376709c 100644 --- a/app/views/cards/ui/_notice.html.erb +++ b/app/views/cards/ui/_notice.html.erb @@ -1,7 +1,7 @@ <% if notice %> <%= content_for :javascript do %> M.toast({ - html: "<%= notice.gsub('"', '\"').html_safe %>", + html: "<%= notice.gsub('"', '\"').gsub("\n", ' ').html_safe %>", classes: 'rounded' }) <% end %> diff --git a/app/views/content/_form.html.erb b/app/views/content/_form.html.erb index 07987257..affa4ae6 100644 --- a/app/views/content/_form.html.erb +++ b/app/views/content/_form.html.erb @@ -18,45 +18,6 @@ Tip: You don't need to save before changing categories (on the right). Just remember to save when you're done! - - <%= content_for :javascript do %> // todo hack this might be fixed with the latest materialize -- we should check $('.field_with_errors').find('textarea, input').addClass('invalid'); diff --git a/app/views/content/cards/_in_universe_content_list.html.erb b/app/views/content/cards/_in_universe_content_list.html.erb index 214858ec..49419f31 100644 --- a/app/views/content/cards/_in_universe_content_list.html.erb +++ b/app/views/content/cards/_in_universe_content_list.html.erb @@ -29,8 +29,7 @@ asset_path("card-headers/#{content_type_pluralized}.jpg") end %> -
-
+
<% if defined?(card_title) && !card_title.blank? %> <%= card_title %> diff --git a/app/views/content/components/_list_filter_bar.html.erb b/app/views/content/components/_list_filter_bar.html.erb index 8953900f..5f22fd25 100644 --- a/app/views/content/components/_list_filter_bar.html.erb +++ b/app/views/content/components/_list_filter_bar.html.erb @@ -1,50 +1,96 @@ - -
- - <%= Universe.icon %> - - +
+ <% end %> - - loyalty - + <% if @page_tags.present? && @page_tags.any? %> +
+ lighten-4 black-text tooltipped' + href='#' + data-target='tag-filter-dropdown' + data-position="bottom" + data-delay="500" + data-tooltip="Filter by tag"> + loyalty + <%= @filtered_page_tags.try(:first).try(:tag) %> + + +
+ <% end %> - star_outline - + -->   -
+   -
- - +
+ +
- --> + diff --git a/app/views/content/edit.html.erb b/app/views/content/edit.html.erb index d5e7b01d..e06dbe9e 100644 --- a/app/views/content/edit.html.erb +++ b/app/views/content/edit.html.erb @@ -28,6 +28,31 @@ height: 200, indicators: false }); + + var tribute = new Tribute({ + values: [ + <% @linkables_cache.each do |class_name, collection| %> + <% linkable_class = class_name.constantize %> + <% collection.each do |page_name, page_id| %> + { + key: "<%= page_name.gsub('"', "\"").gsub("\n", " ").gsub("\r", " ").strip %>", + value: '[[<%= class_name %>-<%= page_id %>]]', + color: '<%= linkable_class.color %>', + icon: '<%= linkable_class.icon %>' + }, + <% end %> + <% end %> + ], + selectTemplate: function (item) { + // We're overriding the default here so we don't prepend a @ + return item.original.value; + }, + menuItemTemplate: function (item) { + return '' + item.original.icon + '' + item.string; + }, + spaceSelectsMatch: false + }); + tribute.attach(document.querySelectorAll('.js-can-mention-pages')); }); diff --git a/app/views/content/form/_rich_text_input.html.erb b/app/views/content/form/_rich_text_input.html.erb index e87beea8..515d526e 100644 --- a/app/views/content/form/_rich_text_input.html.erb +++ b/app/views/content/form/_rich_text_input.html.erb @@ -36,11 +36,13 @@ <%= text_area_tag "#{content_name}[custom_attribute_values][][value]", value, - class: "materialize-textarea #{defined?(autocomplete) && autocomplete ? ('autocomplete ' + 'js-autocomplete-' + field[:id].to_s) : ''}", + class: "js-can-mention-pages materialize-textarea #{defined?(autocomplete) && autocomplete ? ('autocomplete ' + 'js-autocomplete-' + field[:id].to_s) : ''}", placeholder: placeholder %> - +

+ Tip: You can press @ to insert a link to your other pages. +

diff --git a/app/views/content/index.html.erb b/app/views/content/index.html.erb index d1a78061..afc21000 100644 --- a/app/views/content/index.html.erb +++ b/app/views/content/index.html.erb @@ -6,33 +6,10 @@ <%= render partial: 'content/components/parallax_header', locals: { content_type: content_type, content_class: @content_type_class } %> <% end %> - -<% if @page_tags.any? %> -
- View by tag:  - <%= link_to polymorphic_path(@content_type_class) do %> - - <% end %> - <% @page_tags.uniq(&:tag).each do |tag| %> - <%= - link_to send( - "page_tag_#{content_type.pluralize}_path", - slug: PageTagService.slug_for(tag.tag) - ) do - %> - - <% end %> - <% end %> -
-
-<% end %> - <% if @content.any? %> - <%= render partial: 'content/components/list_filter_bar', locals: { } %> -
- <%= render partial: 'content/list/list', locals: { content_list: @content, content_type: @content_type_class, show_add_another_form: true } %> -
+ <%= render partial: 'content/components/list_filter_bar', locals: { content_type: @content_type_class } %> + <%= render partial: 'content/list/cards', locals: { content_list: @content, content_type: @content_type_class, show_add_another_form: true } %> <% elsif @content.empty? %> diff --git a/app/views/content/list/_cards.html.erb b/app/views/content/list/_cards.html.erb new file mode 100644 index 00000000..ff1d745a --- /dev/null +++ b/app/views/content/list/_cards.html.erb @@ -0,0 +1,109 @@ +
+ <% content_list.each.with_index do |content, i| %> +
+
+
+ <% content_image = asset_path("card-headers/#{content_type.name.downcase.pluralize}.jpg") %> + <% if content.respond_to?(:image_uploads) %> + <% images = content.image_uploads %> + <% if images.any? %> + <% content_image = images.sample.src(:medium) %> + <% end %> + <% end %> +
+
+
+ + <%= content.name.presence || 'Untitled' %> + +

+ <% if content.is_a?(Document) %> + <%= content.reading_estimate %> + <% else %> + <%= sanitize ContentFormatterService.show(text: truncate(content.description, length: 140), viewing_user: current_user) %> + <% end %> +

+
+ +
+ + <%= content.name.presence || 'Document preview' %> + close + + <% if content.is_a?(Document) || (content.description.try(:length) || 0 > 140) %> +

+ <%= sanitize ContentFormatterService.show(text: truncate(content.description, length: 420, escape: false), viewing_user: current_user) %> +

+ <% end %> + <% if content.respond_to?(:page_tags) %> +

+ <% content.page_tags.each do |tag| %> + <% if user_signed_in? && content.user == current_user %> + <%= + link_to send( + "page_tag_#{content.class.name.downcase.pluralize}_path", + slug: PageTagService.slug_for(tag.tag) + ) do + %> + + <% end %> + <% else %> + + <% end %> + <% end %> +

+ <% end %> +
+

+ <%= content.created_at == content.updated_at ? 'created' : 'last updated' %> + <%= time_ago_in_words content.updated_at %> + ago +

+
+
+
+ <% end %> + + <% if current_user.can_create?(content_type) %> +
+ <%# TODO: We really need to fix up the new document path so we don't have weird overrides like this %> + <%= link_to content_type == Document ? edit_document_path(:new) : new_polymorphic_path(content_type), class: 'white-text' do %> +
+
+ + New
+ <%= content_type.name %> +
+

+ add +

+
+
+ <% end %> +
+ <% else %> +
+
+
+

+ <%= content_type.icon %> +

+

+ An active + <%= link_to 'Notebook.ai Premium', subscription_path, class: 'blue-text text-darken-2' %> subscription + is required to create additional + <%= content_type.name.downcase %> pages, but pages you've already created will always + be available here. +

+
+
+
+ <% end %> +
diff --git a/app/views/customization/content_types.html.erb b/app/views/customization/content_types.html.erb index 76067b52..fb04e552 100644 --- a/app/views/customization/content_types.html.erb +++ b/app/views/customization/content_types.html.erb @@ -35,6 +35,7 @@ You're on a Premium plan! All pages are available to you! <% else %> You're on a free Starter plan. You can create unlimited Characters, Locations, and Items. You can also create up to 5 Universes. + <%= link_to 'Click here to browse the available Premium plans.', subscription_path %> <% end %>

diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 9b5387c2..a2328d78 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,5 +1,7 @@ <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> - <%= devise_error_messages! %> +
+ <%= devise_error_messages! %> +
diff --git a/app/views/devise/registrations/panes/_information.html.erb b/app/views/devise/registrations/panes/_information.html.erb index 98dfc1a9..e89911a3 100644 --- a/app/views/devise/registrations/panes/_information.html.erb +++ b/app/views/devise/registrations/panes/_information.html.erb @@ -16,6 +16,8 @@
<%= f.label 'Username (users can @mention you with your username on the forums)' %>
<%= f.text_field :username %> + Your Notebook.ai profile will be available at https://www.notebook.ai/@username.
+ Up to 40 numbers, letters, and/or the following symbols are allowed: - _ $ + ! *
diff --git a/app/views/documents/edit.html.erb b/app/views/documents/edit.html.erb index 16b936ef..19fabd28 100644 --- a/app/views/documents/edit.html.erb +++ b/app/views/documents/edit.html.erb @@ -8,7 +8,7 @@
-
<%= @document.body.try(:html_safe) %>
+
<%= @document.body.try(:html_safe) %>
<%= render partial: 'documents/components/smart_sidebar', locals: { document: @document } %> @@ -27,7 +27,6 @@
<%= content_for :javascript do %> - var editor = new MediumEditor('#editor', { targetBlank: true, autoLink: true, @@ -169,4 +168,31 @@ } }); + $(document).ready(function(){ + // Page @mentions + var tribute = new Tribute({ + values: [ + <% @linkables_cache.each do |class_name, collection| %> + <% linkable_class = class_name.constantize %> + <% collection.each do |page_name, page_id| %> + { + key: "<%= page_name.gsub('"', "\"").gsub("\n", " ").gsub("\r", " ").strip %>", + value: '[[<%= class_name %>-<%= page_id %>]]', + color: '<%= linkable_class.color %>', + icon: '<%= linkable_class.icon %>' + }, + <% end %> + <% end %> + ], + selectTemplate: function (item) { + // We're overriding the default here so we don't prepend a @ + return '' + item.original.value + ''; + }, + menuItemTemplate: function (item) { + return '' + item.original.icon + '' + item.string; + }, + spaceSelectsMatch: false + }); + tribute.attach(document.querySelectorAll('.js-can-mention-pages')); + }); <% end %> diff --git a/app/views/documents/index.html.erb b/app/views/documents/index.html.erb index c6f7ef9e..b4bdddfd 100644 --- a/app/views/documents/index.html.erb +++ b/app/views/documents/index.html.erb @@ -1,49 +1,6 @@ <% if @documents.any? %> - <%= render partial: 'content/components/list_filter_bar', locals: { } %> - -
- <% @documents.each.with_index do |document, i| %> - <% if i % 3 == 0 %> -
- <% end %> -
-
-
- <%= image_tag "card-headers/document-mini.jpg", class: 'activator' %> -
-
- - <%= document.title.presence || 'Untitled document' %> - -

- ~<%= pluralize 1 + ((document.body || "").split(/\s+/).count / 200).to_i, 'minute' %> read -
- <%= document.created_at == document.updated_at ? 'created' : 'updated' %> - <%= time_ago_in_words document.updated_at %> - ago -

-
- -
- - <%= document.title.presence || 'Document preview' %> - close - -

- <%= sanitize truncate(document.body || 'This document is blank.', escape: false, length: 420) %> -

-
-
-
- <% end %> -
+ <%= render partial: 'content/components/list_filter_bar', locals: { content_type: Document } %> + <%= render partial: 'content/list/cards', locals: { content_list: @documents, content_type: Document } %> <% end %> <% if @documents.empty? %> diff --git a/app/views/documents/show.html.erb b/app/views/documents/show.html.erb index bd51aea1..72d3004f 100644 --- a/app/views/documents/show.html.erb +++ b/app/views/documents/show.html.erb @@ -4,7 +4,7 @@
-
<%= @document.body.try(:html_safe) || 'This document is blank.' %>
+
<%= ContentFormatterService.substitute_content_links(@document.body.try(:html_safe) || "", current_user).html_safe %>
<%= render partial: 'documents/components/smart_sidebar', locals: { document: @document } %> diff --git a/app/views/layouts/_sidenav.html.erb b/app/views/layouts/_sidenav.html.erb index 9fcd8fbd..a921efd7 100644 --- a/app/views/layouts/_sidenav.html.erb +++ b/app/views/layouts/_sidenav.html.erb @@ -135,7 +135,7 @@
  • - <%= link_to current_user, class: 'waves-effect' do %> + <%= link_to current_user.profile_url, class: 'waves-effect' do %> person Profile <% end %> diff --git a/app/views/main/dashboard.html.erb b/app/views/main/dashboard.html.erb index 3aeeed09..a03d8f51 100644 --- a/app/views/main/dashboard.html.erb +++ b/app/views/main/dashboard.html.erb @@ -40,7 +40,7 @@
- <%= link_to current_user do %> + <%= link_to current_user.profile_url do %>
diff --git a/app/views/subscriptions/new.html.erb b/app/views/subscriptions/new.html.erb index 5734a24a..5396c617 100644 --- a/app/views/subscriptions/new.html.erb +++ b/app/views/subscriptions/new.html.erb @@ -277,7 +277,7 @@ Of these, <%= pluralize converted_referrals.count, 'user' %> <%= converted_referrals.count == 1 ? 'has' : 'have' %> earned you both a Premium bonus! <% else %> -
  • None have signed up for Premium yet, however.
  • +
  • None have signed up for Premium yet. You receive a permanent 100MB boost to your upload storage whenever someone you refers upgrades to Premium. And they get a 100MB boost, too!
  • <% end %>
    diff --git a/app/views/thredded/posts/_post.html.erb b/app/views/thredded/posts/_post.html.erb new file mode 100644 index 00000000..48127ee4 --- /dev/null +++ b/app/views/thredded/posts/_post.html.erb @@ -0,0 +1,20 @@ +<% post, content = post_and_content if local_assigns.key?(:post_and_content) %> + +<% + muted_post = post.present? && post.to_model.content.split("\n").reject(&:empty?).all? { |paragraph| paragraph.strip.start_with?('(') && paragraph.strip.end_with?(')') } + muted_post_classes = 'grey lighten-4 grey-text text-darken-2' +%> + +<%= render 'thredded/posts_common/before_first_unread_post', post: post if post.first_unread_in_page? %> +<%= content_tag :article, id: dom_id(post), class: "thredded--post thredded--#{post.read_state}--post #{muted_post ? muted_post_classes : 'card'}" do %> + <%= render 'thredded/posts_common/actions', post: post, actions: local_assigns[:actions] %> + <%= render 'thredded/posts_common/header', post: post %> + <%= content || render('thredded/posts/content', post: post) %> + <% if post.pending_moderation? && !Thredded.content_visible_while_pending_moderation %> +

    <%= t 'thredded.posts.pending_moderation_notice' %>

    + <% elsif post.blocked? && post.can_moderate? %> +

    + <%= render 'thredded/shared/content_moderation_blocked_state', moderation_record: post.last_moderation_record %> +

    + <% end %> +<% end %> diff --git a/app/views/users/profile/_info.html.erb b/app/views/users/profile/_info.html.erb index d3107644..1cacbdb8 100644 --- a/app/views/users/profile/_info.html.erb +++ b/app/views/users/profile/_info.html.erb @@ -28,7 +28,7 @@ Username
    - <%= link_to "@#{@user.username}", thredded_path %> + <%= link_to "@#{@user.username}", profile_by_username_path(username: @user.username) %>

    <% end %> diff --git a/app/views/users/profile/_name.html.erb b/app/views/users/profile/_name.html.erb index 8d026950..a7e65e51 100644 --- a/app/views/users/profile/_name.html.erb +++ b/app/views/users/profile/_name.html.erb @@ -1,3 +1,5 @@

    <%= @user.name %> + + <%= render partial: 'thredded/users/badge', locals: { user: @user } %>

    diff --git a/app/views/users/profile/_public_pages.html.erb b/app/views/users/profile/_public_pages.html.erb index 1a26f6e4..8af92018 100644 --- a/app/views/users/profile/_public_pages.html.erb +++ b/app/views/users/profile/_public_pages.html.erb @@ -7,7 +7,7 @@
    <% @tabs.each do |tab| %> <% content_type_class = @user.send(tab).build.class %> - <%= link_to send("#{tab}_user_path"), class: "collection-item #{content_type_class.color}-text" do %> + <%= link_to send("#{tab}_user_path", { id: @user.id }), class: "collection-item #{content_type_class.color}-text" do %> <%= pluralize @content[tab].length, tab.to_s.singularize %> <%= content_type_class.icon %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 7376faeb..0fc9ba2d 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -5,7 +5,7 @@ image_src: @user.image_url(120) content_jsonld = { - '@id': user_url, + '@id': user_url(id: @user.id), '@type': 'http://schema.org/Person', 'http://schema.org/name': @user.name, 'http://schema.org/description': "#{@user.name}’s profile on notebook.ai", diff --git a/config/routes.rb b/config/routes.rb index 8e3c7f04..c7425271 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,8 @@ Rails.application.routes.draw do # todo page tags here end end + get '/@:username', to: 'users#show', as: :profile_by_username + scope '/my' do get '/content', to: 'main#dashboard', as: :dashboard get '/content/recent', to: 'main#recent_content', as: :recent_content diff --git a/spec/controllers/subscriptions_controller_spec.rb b/spec/controllers/subscriptions_controller_spec.rb index de9a7476..cbc7f59f 100644 --- a/spec/controllers/subscriptions_controller_spec.rb +++ b/spec/controllers/subscriptions_controller_spec.rb @@ -48,6 +48,36 @@ RSpec.describe SubscriptionsController, type: :controller do @user.update(stripe_customer_id: 'stripe-id') sign_in @user + @free_plan = BillingPlan.create( + name: 'Starter', + stripe_plan_id: 'starter', + monthly_cents: 0, # $0.00/mo + available: true, + + # Content creation and other permissions: + universe_limit: 5, + allows_core_content: true, + allows_extended_content: false, + allows_collective_content: false, + allows_collaboration: false, + bonus_bandwidth_kb: 123155 + ) + + @beta_plan = BillingPlan.create( + name: 'Early Adopters', + stripe_plan_id: 'early-adopters', + monthly_cents: 0, # $0.00/mo + available: true, + + # Content creation and other permissions: + universe_limit: 5, + allows_core_content: true, + allows_extended_content: false, + allows_collective_content: false, + allows_collaboration: false, + bonus_bandwidth_kb: 123155 + ) + @premium_plan = BillingPlan.create( name: 'Premium', stripe_plan_id: 'premium', @@ -73,21 +103,6 @@ RSpec.describe SubscriptionsController, type: :controller do allows_collaboration: true, bonus_bandwidth_kb: 123155 ) - - @free_plan = BillingPlan.create( - name: 'Starter', - stripe_plan_id: 'starter', - monthly_cents: 0, # $0.00/mo - available: true, - - # Content creation and other permissions: - universe_limit: 5, - allows_core_content: true, - allows_extended_content: false, - allows_collective_content: false, - allows_collaboration: false, - bonus_bandwidth_kb: 123155 - ) end describe "User with no plan (fallback to Starter) tries to upgrade" do @@ -101,7 +116,6 @@ RSpec.describe SubscriptionsController, type: :controller do describe "User on Starter" do before do # Create a Starter subscription for the user - @user.active_subscriptions.create(billing_plan: @free_plan, start_date: Time.now - 5.days, end_date: Time.now + 5.days) @user.update(selected_billing_plan_id: @free_plan.id) end @@ -169,12 +183,10 @@ RSpec.describe SubscriptionsController, type: :controller do describe "User on Premium" do before do # Create a premium subscription for the user - @user.active_subscriptions.create(billing_plan: @premium_plan, start_date: Time.now - 5.days, end_date: Time.now + 5.days) - expect(@user.active_subscriptions.map(&:billing_plan_id)).to eq([@premium_plan.id]) + @user.update(selected_billing_plan_id: BillingPlan.find_by(stripe_plan_id: 'premium').id) end it "allows downgrading to Starter" do - # Downgrade to Starter post :change, params: { stripe_plan_id: 'starter' }