diff --git a/.gitignore b/.gitignore index f35b9ea93..64f5e1ff9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ set_aws_credentials.sh # Ignore map images uploaded to Locations /locations +public/sitemap.xml.gz diff --git a/Gemfile b/Gemfile index 7e2d43b54..ebd700ce4 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,9 @@ gem 'jquery-rails' gem 'jquery-ui-rails' gem 'rails-jquery-autocomplete' +# SEO +gem 'meta-tags' + # Smarts # gem 'serendipitous', :path => "~/Code/indent/serendipitous-gem" gem 'serendipitous', git: 'git://github.com/indentlabs/serendipitous-gem.git' diff --git a/Gemfile.lock b/Gemfile.lock index 9fe868ff8..83886477f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -164,6 +164,8 @@ GEM railties (>= 3.2) medium-editor-rails (2.2.0) railties (>= 3.0) + meta-tags (2.2.0) + actionpack (>= 3.2.0) method_source (0.8.2) mime-types (3.0) mime-types-data (~> 3.2015) @@ -317,6 +319,7 @@ DEPENDENCIES jquery-ui-rails material_icons medium-editor-rails + meta-tags paperclip (~> 4.2.0) pg pry diff --git a/app/assets/images/card-headers/characters.jpg b/app/assets/images/card-headers/characters.jpg index c9d1d1eff..802d8b592 100644 Binary files a/app/assets/images/card-headers/characters.jpg and b/app/assets/images/card-headers/characters.jpg differ diff --git a/app/assets/images/card-headers/items.jpg b/app/assets/images/card-headers/items.jpg index 157866871..d213925c6 100644 Binary files a/app/assets/images/card-headers/items.jpg and b/app/assets/images/card-headers/items.jpg differ diff --git a/app/assets/images/card-headers/locations.jpg b/app/assets/images/card-headers/locations.jpg index 6d726b12b..738c067dd 100644 Binary files a/app/assets/images/card-headers/locations.jpg and b/app/assets/images/card-headers/locations.jpg differ diff --git a/app/assets/images/card-headers/universes.jpg b/app/assets/images/card-headers/universes.jpg index d110c17c8..411455cc9 100644 Binary files a/app/assets/images/card-headers/universes.jpg and b/app/assets/images/card-headers/universes.jpg differ diff --git a/app/assets/images/landing/digital-notebook.jpg b/app/assets/images/landing/digital-notebook.jpg new file mode 100644 index 000000000..ab9327fac Binary files /dev/null and b/app/assets/images/landing/digital-notebook.jpg differ diff --git a/app/assets/images/landing/screenshot.png b/app/assets/images/landing/screenshot.png new file mode 100644 index 000000000..f5f5e9a87 Binary files /dev/null and b/app/assets/images/landing/screenshot.png differ diff --git a/app/assets/stylesheets/landing.scss b/app/assets/stylesheets/landing.scss new file mode 100644 index 000000000..e633f0c80 --- /dev/null +++ b/app/assets/stylesheets/landing.scss @@ -0,0 +1,5 @@ +.perks-section { + i[class=material-icons] { + font-size: 120%; + } +} \ No newline at end of file diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 107f73289..367d0d9fc 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -1,10 +1,15 @@ # Controller for top-level pages of the site that do not have # an associated model class MainController < ApplicationController + layout "landing", only: [:index, :about_notebook] + def index redirect_to :dashboard if user_signed_in? end + def about_notebook + end + def comingsoon end diff --git a/app/models/user.rb b/app/models/user.rb index 11d01ba53..5110ea7ff 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,25 @@ class User < ActiveRecord::Base has_many :magics has_many :universes + # as_json creates a hash structure, which you then pass to ActiveSupport::json.encode to actually encode the object as a JSON string. + # This is different from to_json, which converts it straight to an escaped JSON string, + # which is undesireable in a case like this, when we want to modify it + def as_json(options={}) + options[:except] ||= blacklisted_attributes + super(options) + end + + # Returns this object as an escaped JSON string + def to_json(options={}) + options[:except] ||= blacklisted_attributes + super(options) + end + + def to_xml(options={}) + options[:except] ||= blacklisted_attributes + super(options) + end + def content { characters: characters, @@ -37,4 +56,18 @@ def content_count universes.length ].sum end + + private + + # Attributes that are non-public, and should be blacklisted from any public + # export (ex. in the JSON api, or SEO meta info about the user) + def blacklisted_attributes + [ + :password_digest, + :old_password, + :encrypted_password, + :reset_password_token, + :email + ] + end end diff --git a/app/views/cards/intros/_item_intro.html.erb b/app/views/cards/intros/_item_intro.html.erb index 5046dd79f..2b5d6163c 100644 --- a/app/views/cards/intros/_item_intro.html.erb +++ b/app/views/cards/intros/_item_intro.html.erb @@ -5,7 +5,7 @@

- Every weapon was made by someone, and every book has an owner. Keep track of every aspect of your items. + Every weapon was made by someone; every book has a story. Track of every aspect of your items.

\ No newline at end of file diff --git a/app/views/cards/intros/_location_intro.html.erb b/app/views/cards/intros/_location_intro.html.erb index f74f75d7f..fd9ce11d8 100644 --- a/app/views/cards/intros/_location_intro.html.erb +++ b/app/views/cards/intros/_location_intro.html.erb @@ -5,7 +5,7 @@

- Upload maps, link areas, track demographics, and keep track of history. + Upload maps, link areas, track demographics, and keep track of your world's history.

\ No newline at end of file diff --git a/app/views/characters/show.html.erb b/app/views/characters/show.html.erb index b14974f03..a7b3eda34 100644 --- a/app/views/characters/show.html.erb +++ b/app/views/characters/show.html.erb @@ -1 +1,14 @@ +<%# to_json will escape any values into unicode escape sequences, so we can call html_safe %> + + + <%= render partial: 'content/show', locals: { content: @content } %> diff --git a/app/views/content/_show.html.erb b/app/views/content/_show.html.erb index 0bd976fd8..b963865f3 100644 --- a/app/views/content/_show.html.erb +++ b/app/views/content/_show.html.erb @@ -1,4 +1,14 @@ -<% title @content.name %> +<% if @content.present? && @content.respond_to?(:as_jsonld) %> + +<% end %> + +<% +set_meta_tags title: content.name, description: content.description +%> + + <% content_for :sidebar_top do %> <%= render partial: 'cards/serendipitous/content_question', locals: { question: @question, content: @content } %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index a640dd124..188b0f789 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -26,6 +26,11 @@ <%= f.password_field :password_confirmation, autocomplete: "off" %> +
+ <%= f.label :promo_code %>
+ <%= f.text_field :promo_code, value: 'NANOPREP - 100% off Notebook.ai for life!', disabled: "disabled" %> +
+
<%= f.submit "Sign up", class: 'btn' %>
diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb index b14974f03..4c2964555 100644 --- a/app/views/items/show.html.erb +++ b/app/views/items/show.html.erb @@ -1 +1,12 @@ + + <%= render partial: 'content/show', locals: { content: @content } %> diff --git a/app/views/layouts/_ganalytics.html.erb b/app/views/layouts/_ganalytics.html.erb index 839a9917a..5bcc7fd91 100644 --- a/app/views/layouts/_ganalytics.html.erb +++ b/app/views/layouts/_ganalytics.html.erb @@ -2,7 +2,7 @@ var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-39217500-1']); - _gaq.push(['_setDomainName', 'indentapp.com']); + _gaq.push(['_setDomainName', 'notebook.ai']); _gaq.push(['_trackPageview']); (function() { @@ -11,4 +11,4 @@ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); - \ No newline at end of file + diff --git a/app/views/layouts/_seo.html.erb b/app/views/layouts/_seo.html.erb new file mode 100644 index 000000000..11b13053d --- /dev/null +++ b/app/views/layouts/_seo.html.erb @@ -0,0 +1,32 @@ + +<%= +# This belongs inside the tag. +# Default values and pointers here only. +# Most content should be set in the pages themselves. +# Any values set here can be overridden. + +# Default & site-wide values + +display_meta_tags site: 'Notebook', +publisher: 'https://plus.google.com/118076966717703203223', +image_src: image_url('card-headers/hero.png'), +description: 'Notebook is a set of tools for writers, game designers, and roleplayers to create magnificent universes — and everything within them.', +# Recommended keywords tag length: up to 255 characters, 20 words. +keywords: %w[writing author nanowrimo novel character fiction fantasy universe creative dnd roleplay larp game design], +og: { + title: :title, + site_name: 'Notebook', + url: request.url, + image: :image_src, + description: :description, +}, +twitter: { + card: 'summary', + title: :title, + site: '@IndentLabs', + image: :image_src, + url: request.url, + description: :description +} +%> + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 5acd21dfd..f181e22b7 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,7 +1,6 @@ - <%= content_for?(:title) ? yield(:title) : 'Notebook' %> <%= stylesheet_link_tag 'application' %> <%= javascript_include_tag 'application' %> <%= csrf_meta_tags %> @@ -10,6 +9,9 @@ + + <%# is set in _seo.html.erb %> + <%= render 'layouts/seo' %> </head> <body> @@ -29,7 +31,6 @@ <%= render 'layouts/ganalytics' %> - <a href="https://plus.google.com/118076966717703203223" rel="publisher"></a> </main> <%= render 'layouts/footer' %> diff --git a/app/views/layouts/landing.html.erb b/app/views/layouts/landing.html.erb new file mode 100644 index 000000000..8785bd536 --- /dev/null +++ b/app/views/layouts/landing.html.erb @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title><%= content_for?(:title) ? yield(:title) : 'Notebook' %> + <%= stylesheet_link_tag 'application' %> + <%= javascript_include_tag 'application' %> + <%= csrf_meta_tags %> + + + + + + + + + <%= render 'layouts/navbar' %> + +
+ <%= yield %> + + <%= render 'layouts/ganalytics' %> + + +
+ + + diff --git a/app/views/locations/show.html.erb b/app/views/locations/show.html.erb index b14974f03..96ba891ec 100644 --- a/app/views/locations/show.html.erb +++ b/app/views/locations/show.html.erb @@ -1 +1,12 @@ + + <%= render partial: 'content/show', locals: { content: @content } %> diff --git a/app/views/main/about_notebook.html.erb b/app/views/main/about_notebook.html.erb new file mode 100644 index 000000000..8ab53b933 --- /dev/null +++ b/app/views/main/about_notebook.html.erb @@ -0,0 +1,65 @@ +
+
+
+ <%= image_tag 'landing/digital-notebook.jpg', style: "height: 600px;" %> + Your digital notebook is here. +
+
+

+ Notebook is a set of tools for writers, game designers, and roleplayers to create magnificent universes – and everything within them. +

+

+ From a simple interface in your browser, on your phone, or on your tablet, you can do everything you'd ever want to do while creating your own little (or big!) world. +

+
+
+ <%= link_to 'Get started', new_user_registration_path %> +
+
+
+ +
+
+ <%= render 'cards/intros/universe_intro' %> +
+
+ <%= render 'cards/intros/character_intro' %> +
+
+ <%= render 'cards/intros/location_intro' %> +
+
+ <%= render 'cards/intros/item_intro' %> +
+
+ +

Pricing chart

+ +

October promotion

+ +

Second CTA

+ + +
+
+
+ flash_on +

Speeds up development

+

We did most of the heavy lifting for you to provide a default stylings that incorporate our custom components.

+
+
+
+
+ group +

User Experience Focused

+

By utilizing elements and principles of Material Design, we were able to create a framework that focuses on User Experience.

+
+
+
+
+ settings +

Easy to work with

+

We have provided detailed documentation as well as specific code examples to help new users get started.

+
+
+
\ No newline at end of file diff --git a/app/views/main/dashboard.html.erb b/app/views/main/dashboard.html.erb index 88419bd37..262232186 100644 --- a/app/views/main/dashboard.html.erb +++ b/app/views/main/dashboard.html.erb @@ -211,4 +211,4 @@ - \ No newline at end of file + diff --git a/app/views/main/index.html.erb b/app/views/main/index.html.erb index 51181999e..60f6f243c 100644 --- a/app/views/main/index.html.erb +++ b/app/views/main/index.html.erb @@ -1,22 +1,411 @@ -
-
-
-
- <%= image_tag 'card-headers/hero.jpg', width: '100%' %> - Your digital notebook is here. -
-
-

- Notebook is a set of tools for writers, game designers, and roleplayers to create magnificent universes – and everything within them. -

-

- From a simple interface in your browser, on your phone, or on your tablet, you can do everything you'd ever want to do while creating your own little (or big!) world. +

+
+
+

+

+ Your digital notebook is here. +

+
+
+ Notebook.ai grows with your ideas and collaborates back with you. +
+
+
+ <%= link_to 'Get Yours', new_user_registration_path, class: 'btn-large waves-effect waves-light teal lighten-1' %> +

+ Earn a free lifetime membership when you sign up during October
+ with promotion code NANOPREP. +
+

+ +
+
+
+ <%= image_tag 'landing/digital-notebook.jpg', style: 'display: block; transform: translate3d(-50%, 342px, 0px); opacity: 0.25' %> +
+
+ +
+
+
+
+ <%= render 'cards/intros/universe_intro' %> +
+
+ <%= render 'cards/intros/character_intro' %> +
+
+ <%= render 'cards/intros/location_intro' %> +
+
+ <%= render 'cards/intros/item_intro' %> +
+
+
+
+ +
+
+ +
+
+
+

bubble_chart

+
Promotes creativity
+

+ Your Notebook now asks you questions about your characters and ideas, and your answers are saved too. +

+
+
+ +
+
+

schedule

+
Speeds up writing
+

+ With a universe of information at your fingertips, you can look up anything quickly and keep writing. +

+
+
+ +
+
+

assignment_turned_in

+
Continuuity checks
+

+ When every little fact is stored in one place, you can rest easy you won't accidentally contradict yourself. +

+
+
+
+
+
+
+

face

+
Deeper characters
+

+ Get to know your characters. Track every detail of their thoughts, appearance, and personality. +

+
+
+ +
+
+

public

+
Richer worlds
+

+ Immerse yourself in vibrant worlds with tracking available for any kind of locations. +

+
+
+ +
+
+

security

+
Items of legend
+

+ Every object has a backstory that can shine as bright as any character — if you let it. +

+
+
+
+
+
+
+

search

+
Searchable
+

+ Quickly find anything, anywhere. Even that random thought you jotted down at 3am two years ago. +

+
+
+ +
+
+

list

+
Organizable
+

+ Focus on one universe at a time and filter everything else out. Reorganize, sort, and write freely. +

+
+
+ +
+
+

cloud

+
Backed up forever
+

+ Never lose a notebook full of priceless ideas again. Notebook.ai is backed up and always available. +

+
+
+
+ +
+
+
+

settings_ethernet

+
Grows with you
+

+ Plan as much as you want in your Notebook, for life. The only limits here are your imagination. +

+
+
+ +
+
+

group

+
Brainstorm together
+

+ Invite anyone to review any content you decide to share. Sometimes a second set of eyes makes all the difference. +

+
+
+ +
+
+

vpn_key

+
Access for life
+

+ Signing up for Notebook is just like buying a notebook: one payment and it's yours for life. +

+
+
+
+ +
+
+ +
+
+
+ "Imagine sliced bread as a notebook. This is even better." +

+ — Andrew Brown, Author & CEO +
+
+
+ <%= image_tag 'landing/screenshot.png', style: 'display: block; transform: translate3d(-50%, 145px, 0px); opacity: 0.25' %> +
+
+ +
+

Notebook Pricing

+
+
+
+

class
+
+ Ethereal
+ Free trial
+
    +
  • Plan up to 2 universes
  • +
  • Plan up to 10 characters
  • +
  • Plan up to 5 locations
  • +
  • Plan up to 5 items
  • +
+
+ <%= link_to 'Get Started', new_user_registration_path, class: 'btn-large waves-effect waves-light teal lighten-1' %> +
+
+
+
+
+
+

book
+
+ Eternal
+ $19.99 Free for life! +
+
    +
  • Plan unlimited universes
  • +
  • Plan unlimited characters
  • +
  • Plan unlimited locations
  • +
  • Plan unlimited items
  • +
+
+ <%= link_to 'Get Started', new_user_registration_path, class: 'btn-large waves-effect waves-light teal lighten-1' %> +
+
+
+
+
+ Earn a free lifetime membership when you sign up during October
+ with promotion code NANOPREP! +
+
+
+ + + + + + diff --git a/app/views/main/privacyinfo.html.erb b/app/views/main/privacyinfo.html.erb index b818af3a7..4febb1048 100644 --- a/app/views/main/privacyinfo.html.erb +++ b/app/views/main/privacyinfo.html.erb @@ -1,3 +1,8 @@ +<% +set_meta_tags title: 'Privacy Policy', +description: 'Notebook will always do its best to maintain the security and privacy of your data, in order to ensure that it is you and only you that has access to view, modify, or remove it, unless you explicitly designate otherwise.' +%> +
@@ -26,4 +31,4 @@
-
\ No newline at end of file +
diff --git a/app/views/universes/show.html.erb b/app/views/universes/show.html.erb index 42b8b9f46..2dcdb0f2b 100644 --- a/app/views/universes/show.html.erb +++ b/app/views/universes/show.html.erb @@ -1,3 +1,14 @@ + + <%= render partial: 'content/show', locals: { content: @content } %>
@@ -10,4 +21,4 @@
<%= render partial: 'content/cards/in_universe_content_list', locals: { content_type: :item, content_list: @content.items } %> -
\ No newline at end of file +
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 0e971604f..db0a0e349 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,3 +1,17 @@ + + <% tabs = %w(universes characters locations items) diff --git a/config/environments/development.rb b/config/environments/development.rb index 362f7c58c..3f3407289 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -39,4 +39,6 @@ secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] } } + + default_url_options[:host] = 'localhost:3000' end diff --git a/config/environments/production.rb b/config/environments/production.rb index 889f2eec8..9afa96925 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -91,4 +91,6 @@ # Do not dump schema after migrations. # TODO: double check this config.active_record.dump_schema_after_migration = false + + default_url_options[:host] = 'www.notebook.ai' end diff --git a/config/environments/test.rb b/config/environments/test.rb index da1933d63..c228c40dc 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -36,4 +36,6 @@ config.active_support.test_order = :random config.active_record.raise_in_transactional_callbacks = true + + default_url_options[:host] = 'localhost:3000' end diff --git a/config/routes.rb b/config/routes.rb index 7de451230..8a7a0ff82 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ # Info pages scope '/about' do + get 'notebook', to: 'main#about_notebook', as: :about_notebook get '/privacy', to: 'main#privacyinfo', as: :privacy_policy end diff --git a/test/integration/universe_stories_test.rb b/test/integration/universe_stories_test.rb new file mode 100644 index 000000000..ab3704e6a --- /dev/null +++ b/test/integration/universe_stories_test.rb @@ -0,0 +1,43 @@ +require 'test_helper' + +# Tests scenarios related to interacting with Universes +class UniverseStoriesTest < ActionDispatch::IntegrationTest + setup do + @user = log_in_as_user + @universe = create(:universe, user: @user) + end + + test 'universe is displayed on universes list' do + visit universes_path + assert page.has_content?(@universe.name), + "Page body didn't contain universe name: "\ + "#{@universe.name} not found in \n#{page.body}" + end + + test 'universe list edit button edits universe' do + visit universe_path(@universe) + click_on 'Edit this universe' + assert_equal edit_universe_path(@universe), current_path + end + + test 'universe list view button shows universe' do + visit universes_path + within(:css, '.collection-item:first') do + click_on @universe.name + end + assert_equal universe_path(@universe), current_path, + "Not on universe path for universe #{@universe.name}: "\ + "#{@universe.name} not found in \n#{page.body}" + end + + test 'a user can create a new universe' do + new_universe = build(:universe) + visit universes_path + click_on 'Create another universe' + fill_in 'universe_name', with: new_universe.name + click_on 'Create Universe' + + assert_equal universe_path(Universe.where(name: new_universe.name).first), + current_path + end +end