├── .gitignore ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── api │ │ │ ├── notebooks.coffee │ │ │ ├── notes.coffee │ │ │ ├── tags.coffee │ │ │ └── users.coffee │ │ ├── application.js │ │ ├── cable.js │ │ ├── channels │ │ │ └── .keep │ │ └── notebooks.coffee │ └── stylesheets │ │ ├── api │ │ ├── home │ │ │ ├── homeTotalLayout.css │ │ │ ├── home_sidebar.css │ │ │ └── home_user_dashboard.css │ │ ├── notebooks.scss │ │ ├── notebooks │ │ │ ├── new_notebook.css │ │ │ ├── notebook_index.css │ │ │ ├── notebook_info.css │ │ │ └── notebook_scrollbar.css │ │ ├── notes.scss │ │ ├── notes │ │ │ ├── new_note.css │ │ │ ├── note_form_topbar.css │ │ │ ├── note_index.css │ │ │ ├── note_info_page_modal.css │ │ │ └── note_item.css │ │ ├── session │ │ │ ├── plainForm.css │ │ │ ├── splashForm.css │ │ │ └── splashSideoutModal.css │ │ ├── tags.scss │ │ ├── tags │ │ │ ├── tagbar.css │ │ │ └── tags_index.css │ │ └── users.scss │ │ ├── application.css │ │ ├── loading-icon.css │ │ └── snow.css ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── api │ │ ├── notebooks_controller.rb │ │ ├── notes_controller.rb │ │ ├── sessions_controller.rb │ │ ├── tags_controller.rb │ │ └── users_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ └── static_pages_controller.rb ├── helpers │ ├── api │ │ ├── notebooks_helper.rb │ │ ├── notes_helper.rb │ │ ├── tags_helper.rb │ │ └── users_helper.rb │ └── application_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── note.rb │ ├── notebook.rb │ ├── tag.rb │ ├── tagging.rb │ └── user.rb └── views │ ├── api │ ├── notebooks │ │ ├── index.json.jbuilder │ │ └── show.json.jbuilder │ ├── notes │ │ ├── index.json.jbuilder │ │ └── show.json.jbuilder │ ├── sessions │ │ ├── _session.json.jbuilder │ │ └── show.json.jbuilder │ ├── tags │ │ ├── create.json.jbuilder │ │ ├── show.json.jbuilder │ │ └── single_tag.json.jbuilder │ └── users │ │ └── show.json.jbuilder │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ └── static_pages │ └── root.html.erb ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring └── update ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── secrets.yml └── spring.rb ├── db ├── migrate │ ├── 20170418162509_create_users.rb │ ├── 20170421022646_create_notebooks.rb │ ├── 20170421022851_create_notes.rb │ ├── 20170421041353_delete_author_id_column_from_notes_table.rb │ ├── 20170421042844_add_null_false_to_notebook_id_in_notes.rb │ ├── 20170421123310_add_default_tag_to_notebooks.rb │ ├── 20170421124106_add_null_false_to_default_tag_in_notebooks.rb │ ├── 20170421161450_add_unique_scoping_to_title_in_notebooks.rb │ ├── 20170430043631_remove_null_constraint_on_notes_for_body.rb │ ├── 20170512020834_create_tags.rb │ ├── 20170512020905_create_taggings.rb │ ├── 20170512034336_add_null_constraints_to_tags_and_taggings.rb │ └── 20170512160114_add_db_uniqueness_constraint_to_taggings.rb ├── schema.rb └── seeds.rb ├── docs ├── README.md ├── api-endpoints.md ├── component-hierarchy.md ├── sample-state.md ├── sample_state_2.md ├── schema.md └── wireframes │ ├── 01-signup-splash.png │ ├── 02-signup-plain.png │ ├── 03-signin.png │ ├── 04-home.png │ ├── 05-newnote.png │ ├── 06-notebooks.png │ ├── 07-shownotebook.png │ ├── 08-newnotebook.png │ ├── 09-newtag.png │ ├── 10-searchnotes.png │ └── xml │ ├── 01-signup-splash.xml │ ├── 02-signup-plain.xml │ ├── 03-signin.xml │ ├── 04-home.xml │ ├── 05-newnote.xml │ ├── 06-notebooks.xml │ ├── 07-shownotebook.xml │ ├── 08-newnotebook.xml │ ├── 09-newtag.xml │ └── 10-searchnotes.xml ├── frontend ├── actions │ ├── auth_actions.js │ ├── notebooks_actions.js │ ├── notes_actions.js │ └── tags_actions.js ├── components │ ├── app.jsx │ ├── auth │ │ ├── auth_form.jsx │ │ ├── auth_form_conditional.jsx │ │ ├── auth_form_container.jsx │ │ ├── splash_form_content.jsx │ │ ├── splash_sidebar.jsx │ │ └── unused_modal_code.jsx │ ├── home │ │ ├── home.jsx │ │ └── home_container.jsx │ ├── home_sidebar │ │ ├── home_modals │ │ │ └── user_dashboard.jsx │ │ └── home_sidebar.jsx │ ├── notebooks │ │ ├── new_notebook.jsx │ │ ├── notebook_index.jsx │ │ ├── notebook_index_container.jsx │ │ ├── notebook_info_page.jsx │ │ ├── notebook_item.jsx │ │ └── notebook_scrollbar.jsx │ ├── notes │ │ ├── new_note.jsx │ │ ├── new_note_basic_single_submit.jsx │ │ ├── note_form.jsx │ │ ├── note_form_top_bar.jsx │ │ ├── note_index.jsx │ │ ├── note_indices_container.jsx │ │ ├── note_info_page.jsx │ │ ├── note_item.jsx │ │ └── notes_to_array.js │ ├── root.jsx │ └── tags │ │ ├── tag_show.jsx │ │ ├── tag_show_container.jsx │ │ ├── tagbar.jsx │ │ └── tags_index.jsx ├── niftynote.jsx ├── reducers │ ├── current_note_reducer.js │ ├── current_notebook_reducer.js │ ├── loading_reducer.js │ ├── notebooks_reducer.js │ ├── notes_reducer.js │ ├── root_reducer.js │ ├── session_reducer.js │ └── tags_reducer.js ├── store │ └── store.js └── util │ ├── notebooks_api_util.js │ ├── notes_api_util.js │ ├── session_api_util.js │ └── tags_api_util.js ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package-lock.json ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico ├── images │ ├── N.png │ ├── Niftynote.html │ ├── NiftynoteHomePage.png │ ├── NiftynoteNewNote.png │ ├── NiftynoteNewNotebook.png │ ├── NiftynoteNotebookInfo.png │ ├── NiftynoteSplashPage.png │ ├── Niftynote_files │ │ ├── action_cable.self-5454023407ffec0d29137c7110917e1e745525ae9afbc05f52104c4cd6597429.js │ │ ├── application.self-6a69034c30ceaed4553ecbd74160365019f3c570e0140f88078c496bf67720c1.css │ │ ├── application.self-f571b9db02bf4ed04c38a6a29072ac382f7d4f47db1fab9817aeadb0d8665235.js │ │ ├── bundle.self-80e1c9fbd5a9b92a217a7e13a1676f99b1aaa323af1029937ad59d7f5e1eceab.js │ │ ├── cable.self-6e0514260c1aa76eaf252412ce74e63f68819fd19bf740595f592c5ba4c36537.js │ │ ├── css │ │ ├── edb8528b4a.css │ │ ├── edb8528b4a.js │ │ ├── homeTotalLayout.self-8c727b1c78a0d50ce353b92c43cbffc8e470ad425ea972323c85652e3aaa3b0e.css │ │ ├── home_note_form_topbar.self-1d87b37ed5c4348d1bd578a6f8b3a9c944fb04cbbba665b490666cd5b2b4cfbb.css │ │ ├── home_note_index.self-8de44c309cfa64fcb1a3ee7441cb7b065729ffb7ab8eeff1050a49cd9a104c3e.css │ │ ├── home_note_item.self-473d6e07a5c8b8ce2cabe63717b3fd30e4ef5c58cead1ac99de9c7d60c8542b3.css │ │ ├── home_show_note.self-ccf7d72f5798d978f0812eb523ff6a4fcb32fd8f52cf733e0aba671c539ded45.css │ │ ├── home_sidebar.self-16cd005b1092bb687eded750450a492b8d8f27a3624b053cfa47ad1ccac4141e.css │ │ ├── home_user_dashboard.self-848e9fd1cc4095f8a81c3ff375b303757e49a2e31f11516f95cff429d53ea84f.css │ │ ├── jquery.self-bd7ddd393353a8d2480a622e80342adf488fb6006d667e8b42e4c0073393abee.js │ │ ├── jquery_ujs.self-784a997f6726036b1993eb2217c9cb558e1cbb801c6da88105588c56f13b466a.js │ │ ├── livereload.js │ │ ├── loading-icon.self-eff88a3c18a9c8f10b3f5009d76ba3e80a9cdb3f841fa61a7483704300172c27.css │ │ ├── new_notebook.self-e37986705a2c53bf9e509b1cc8e3eee9a3dbb6e5bdc1013e733094e778f34b0e.css │ │ ├── note_info_page_modal.self-76af19c385105da801ae1105072f58a5a72b2310c355b678fa87b99981d88f9b.css │ │ ├── notebook_form.self-fa0188729f5b9634dd1168734c5905dd630cbeb6b6285be467385a9354d36d9f.css │ │ ├── notebook_index.self-6da585deaaab686ca17b9f34b488cab89ab30bf2caa1114589d257e149159e5a.css │ │ ├── notebook_info.self-85802483fa5bf43ace2b3e2c690d9a3261322685ffc099241eae9f714b8dde43.css │ │ ├── notebook_scrollbar.self-818dd773e0c83d69f90c5e2769177861ccfbf3b8d2dadd37d7f2aec60f337428.css │ │ ├── notebooks.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05(1).js │ │ ├── notebooks.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js │ │ ├── notebooks.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css │ │ ├── notes.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js │ │ ├── notes.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css │ │ ├── plainForm.self-ba91fa425d670cefed004fe6be46901cfe1d09187691249bad3d7aff8b38584e.css │ │ ├── quill_customize.self-b96b21c4064fe7178e1cb734d7e363e38219bb891efb8abd5ff5ac595b665f77.css │ │ ├── snow.self-00c7492774498e615ba99611ffaba0cab91e44275cb2396decb83029011c9534.css │ │ ├── splashForm.self-f60850dea040db8b5383a91935f0fad864fc211f870f0351eb03647926265612.css │ │ ├── splashSideoutModal.self-edafd66fbcd066f874db70a4225bb498a8d38c30b622c8b05430d2990b9903aa.css │ │ ├── swfobject.js │ │ ├── users.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js │ │ ├── users.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css │ │ └── web_socket.js │ ├── darkspace.jpg │ ├── earth.jpg │ ├── landscape.jpg │ ├── loading.gif │ └── nightsky.jpg └── robots.txt ├── test ├── controllers │ ├── .keep │ ├── api │ │ ├── notebooks_controller_test.rb │ │ ├── notes_controller_test.rb │ │ ├── tags_controller_test.rb │ │ └── users_controller_test.rb │ └── notebooks_controller_test.rb ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ ├── notebooks.yml │ ├── notes.yml │ ├── taggings.yml │ ├── tags.yml │ └── users.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── note_test.rb │ ├── notebook_test.rb │ ├── tag_test.rb │ ├── tagging_test.rb │ └── user_test.rb └── test_helper.rb ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bundle.js 3 | bundle.js.map 4 | .byebug_history 5 | .DS_Store 6 | npm-debug.log 7 | tmp/ 8 | development.log 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.5.7' 3 | 4 | git_source(:github) do |repo_name| 5 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 6 | "https://github.com/#{repo_name}.git" 7 | end 8 | 9 | 10 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 11 | gem 'rails', '~> 5.0.2' 12 | # Use postgresql as the database for Active Record 13 | gem 'pg', '~> 0.18' 14 | # Use Puma as the app server 15 | gem 'puma', '~> 3.0' 16 | # Use SCSS for stylesheets 17 | gem 'sass-rails', '~> 5.0' 18 | # Use Uglifier as compressor for JavaScript assets 19 | gem 'uglifier', '>= 1.3.0' 20 | # Use CoffeeScript for .coffee assets and views 21 | gem 'coffee-rails', '~> 4.2' 22 | # See https://github.com/rails/execjs#readme for more supported runtimes 23 | # gem 'therubyracer', platforms: :ruby 24 | 25 | # Use jquery as the JavaScript library 26 | gem 'jquery-rails' 27 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 28 | gem 'jbuilder', '~> 2.5' 29 | # Use Redis adapter to run Action Cable in production 30 | # gem 'redis', '~> 3.0' 31 | # Use ActiveModel has_secure_password 32 | gem 'bcrypt', '~> 3.1.7' 33 | 34 | # Use Capistrano for deployment 35 | # gem 'capistrano-rails', group: :development 36 | 37 | group :development, :test do 38 | # Call 'byebug' anywhere in the code to stop execution and get a console 39 | gem 'byebug', platform: :mri 40 | end 41 | 42 | gem 'faker' 43 | 44 | gem 'google-analytics-rails' 45 | 46 | group :development do 47 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 48 | gem 'web-console', '>= 3.3.0' 49 | gem 'listen', '~> 3.0.5' 50 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 51 | gem 'spring' 52 | gem 'spring-watcher-listen', '~> 2.0.0' 53 | gem 'better_errors' 54 | gem 'binding_of_caller' 55 | gem 'pry-rails' 56 | gem 'annotate' 57 | gem "guard", ">= 2.2.2", :require => false 58 | gem "guard-livereload", :require => false 59 | gem "rack-livereload" 60 | gem "rb-fsevent", :require => false 61 | end 62 | 63 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 64 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 65 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec features) \ 6 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} 7 | 8 | ## Note: if you are using the `directories` clause above and you are not 9 | ## watching the project directory ('.'), then you will want to move 10 | ## the Guardfile to a watched dir and symlink it back, e.g. 11 | # 12 | # $ mkdir config 13 | # $ mv Guardfile config/ 14 | # $ ln -s config/Guardfile . 15 | # 16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 17 | 18 | guard 'livereload' do 19 | extensions = { 20 | css: :css, 21 | scss: :css, 22 | sass: :css, 23 | js: :js, 24 | coffee: :js, 25 | html: :html, 26 | png: :png, 27 | gif: :gif, 28 | jpg: :jpg, 29 | jpeg: :jpeg, 30 | # less: :less, # uncomment if you want LESS stylesheets done in browser 31 | } 32 | 33 | rails_view_exts = %w(erb haml slim) 34 | 35 | # file types LiveReload may optimize refresh for 36 | compiled_exts = extensions.values.uniq 37 | watch(%r{public/.+\.(#{compiled_exts * '|'})}) 38 | 39 | extensions.each do |ext, type| 40 | watch(%r{ 41 | (?:app|vendor) 42 | (?:/assets/\w+/(?[^.]+) # path+base without extension 43 | (?\.#{ext})) # matching extension (must be first encountered) 44 | (?:\.\w+|$) # other extensions 45 | }x) do |m| 46 | path = m[1] 47 | "/assets/#{path}.#{type}" 48 | end 49 | end 50 | 51 | # file needing a full reload of the page anyway 52 | watch(%r{app/views/.+\.(#{rails_view_exts * '|'})$}) 53 | watch(%r{app/helpers/.+\.rb}) 54 | watch(%r{config/locales/.+\.yml}) 55 | end 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Niftynote 2 | * [Live link](https://niftynote.herokuapp.com) 3 | 4 | ### Background 5 | 6 | Built with Rails on the backend and React/Redux on the frontend, Nifynote is an Evernote-inspired, single-page RESTful web app for note-taking. 7 | 8 | ### Highlights 9 | * Efficient auto-save for notes 10 | * Rich-text editing 11 | * New account creation, user login and guest login 12 | * Users can organize notes in notebooks and/or tags 13 | 14 | ### Also Built With: 15 | * Ruby 16 | * JavaScript 17 | * jQuery 18 | * HTML5 19 | * CSS3 20 | * ActiveRecord 21 | 22 | ### Screenshots 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/api/notebooks.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/notes.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/tags.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/users.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/notebooks.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/home/homeTotalLayout.css: -------------------------------------------------------------------------------- 1 | .homeTotalLayout { 2 | display: flex; 3 | height: 100vh; 4 | font-family: 'Yantramanav', sans-serif; 5 | min-width: 800px; 6 | } 7 | 8 | .homeTotalLayout .homeSidebar { 9 | width: 75px; 10 | } 11 | 12 | .homeTotalLayout .entireNotesIndexCol { 13 | width: 350px; 14 | } 15 | 16 | .homeLeftSide { 17 | display: flex; 18 | } 19 | 20 | .outerhomeRightSide { 21 | width: 100%; 22 | } 23 | 24 | .innerhomeRightSide { 25 | width: 100%; 26 | display: flex; 27 | flex-direction: column; 28 | align-items: center; 29 | } 30 | 31 | .innerhomeRightSide .noteFormTopBarContainer { 32 | position: relative; 33 | width: 100%; 34 | height: 50px; 35 | } 36 | 37 | .innerhomeRightSide .noteFormTopBarContainer > * > * { 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | } 42 | 43 | /*CSS styling for the updateNote window on homepage below*/ 44 | 45 | .quillContainer { 46 | display: flex; 47 | flex-direction: column; 48 | max-width: 960px; 49 | min-width: 300px; 50 | height: 100%; 51 | margin-left: 50px; 52 | /*border: 1px solid red;*/ 53 | } 54 | 55 | .quillContainer .ql-editor { 56 | margin-top: 60px; 57 | height: 70vh; 58 | padding-left: 3px; 59 | } 60 | 61 | .quillContainer .scrollbarAndTags { 62 | display: flex; 63 | flex-wrap: nowrap; 64 | margin-left: 2px; 65 | /*border: 1px solid blue;*/ 66 | } 67 | 68 | .quillContainer .scrollbarAndTags > * { 69 | margin-right: 10px; 70 | } 71 | 72 | .quillContainer .ql-toolbar.ql-snow { 73 | min-width: 570px; 74 | border-top: none; 75 | border-left: none; 76 | border-right: none; 77 | padding-left: 0; 78 | /*visibility: hidden;*/ 79 | } 80 | 81 | /*.quillContainer #makeQuillToolbarVisible { 82 | visibility: visible; 83 | -webkit-animation: slider 1s; 84 | }*/ 85 | 86 | .quillContainer #updateNoteTitle { 87 | width: 100%; 88 | outline: none; 89 | border: none; 90 | color: #2DBE60; 91 | font-size: 30px; 92 | font-weight: 200; 93 | padding: 0px 0px 0px 0px; 94 | overflow: hidden; 95 | position: absolute; 96 | top: 160px; 97 | } 98 | 99 | .quillContainer .ql-container.ql-snow { 100 | height: 70vh; 101 | } 102 | 103 | 104 | /*the following disables user from editing the currentNote when going to a notebook without any notes*/ 105 | .blankOutRightSideWhenNoNotesInNotebook { 106 | display: none; 107 | } 108 | 109 | @keyframes fadein { 110 | from { opacity: 0 } 111 | to { opacity: 1 } 112 | }; 113 | 114 | /*@-webkit-keyframes slider { 115 | from { opacity: 0 } 116 | to { opacity: 1 } 117 | from { position: absolute; right: calc(-1*(100% - 430px)); } 118 | to { position: absolute; right: 0px; } 119 | };*/ 120 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/home/home_user_dashboard.css: -------------------------------------------------------------------------------- 1 | /*CSS below apparently necessary to target modal's z-index*/ 2 | body > div:nth-child(3) > div { 3 | z-index: 3; 4 | } 5 | 6 | .userDashboardModal { 7 | position: absolute; 8 | z-index: 3; 9 | bottom: 40px; 10 | left: 80px; 11 | background-color: white; 12 | font-family: 'Yantramanav', sans-serif; 13 | text-align: center; 14 | font-size: 20px; 15 | outline: none; 16 | border: 2px solid rgba(41, 179, 91, 0.36); 17 | -webkit-animation: fadein 0.5s; 18 | } 19 | 20 | .userDashboardModal button { 21 | background-color: #28A956; 22 | width: 160px; 23 | height: 40px; 24 | border-radius: 4px; 25 | border-style: none; 26 | font-size: 18px; 27 | color: white; 28 | cursor: pointer; 29 | transition: all 0.3s ease 0s; 30 | } 31 | 32 | .userDashboardModal > div { 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | justify-content: center; 37 | } 38 | 39 | .userDashboardModal > div > * { 40 | margin: 15px; 41 | } 42 | 43 | .userDashboardModal img { 44 | height: 80px; 45 | } 46 | 47 | @-webkit-keyframes fadein { 48 | from { opacity: 0 } 49 | to { opacity: 1 } 50 | }; 51 | 52 | /* 53 | 54 | #modaltofade { 55 | -webkit-animation: fadeout 2s; 56 | opacity: 0.5; 57 | } 58 | 59 | @-webkit-keyframes fadeout { 60 | from { opacity: 1 } 61 | to { opacity: 0 } 62 | };*/ 63 | 64 | /* 65 | .ReactModalPortal div { 66 | -webkit-animation: fadeout 2s; 67 | background-color: yellow; 68 | } 69 | 70 | */ 71 | 72 | /*ReactModal__Overlay ReactModal__Overlay--after-open" style="position: fixed; top: 0px; left: 0px; right: 0px; bottom: 0px; background-color: transparent;">

Welcome guest@example.com!

*/ 73 | 74 | /*another way to have things slide in*/ 75 | 76 | /*@keyframes slidein { 77 | from { 78 | margin-left: 100%; 79 | width: 300%; 80 | } 81 | 82 | to { 83 | margin-left: 0%; 84 | width: 100%; 85 | } 86 | }*/ 87 | 88 | /*experiments trying to get modal to transition"*/ 89 | 90 | /*transition: all 1s ease 2s;*/ 91 | /*-webkit-transition: width 2s, height 2s, background-color 2s, -webkit-transform 2s;*/ 92 | /*transition-property: background-color, color; 93 | transition-duration: 1s; 94 | transition-timing-function: ease-out;*/ 95 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notebooks.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the api/Notebooks controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notebooks/new_notebook.css: -------------------------------------------------------------------------------- 1 | /*this piece of CSS is to target NewNotebook modal's z-index upon appearance 2 | it appears to be very fragile */ 3 | 4 | /*this applies to notebook info AS WELL*/ 5 | .ReactModal__Overlay { 6 | z-index: 15; 7 | } 8 | 9 | /*the below is what needs to be targeted*/ 10 | /*class="ReactModal__Overlay ReactModal__Overlay--after-open"*/ 11 | 12 | /*this works but is brittle: 13 | /*body > div:nth-child(7) > div > div*/ 14 | 15 | .newNotebookPage { 16 | /*position: absolute; 17 | z-index: 4;*/ 18 | outline: none; 19 | height: 100vh; 20 | display: flex; 21 | flex-direction: column; 22 | padding-top: 230px; 23 | align-items: center; 24 | font-family: 'Yantramanav', sans-serif; 25 | -webkit-animation: fadein 1.5s; 26 | } 27 | 28 | .newNotebookPage > * { 29 | margin: 25px; 30 | } 31 | 32 | .newNotebookPage .newNotebookHeader { 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | color: #b5b3b3; 37 | border-bottom: 1px solid rgb(212, 210, 210); 38 | } 39 | 40 | .newNotebookPage .newNotebookHeader > * { 41 | margin: 10px; 42 | padding-bottom: 3px; 43 | } 44 | 45 | .newNotebookPage i { 46 | font-size: 40px; 47 | } 48 | 49 | .newNotebookPage .newNotebookHeader h1 { 50 | font-size: 17.5px; 51 | text-transform: uppercase; 52 | } 53 | 54 | .newNotebookPage input { 55 | font-size: 50px; 56 | border: none; 57 | font-weight: 100; 58 | border: rgba(212, 210, 210, 0.69); 59 | outline: none; 60 | text-align: center; 61 | } 62 | 63 | ::-webkit-input-placeholder { /* WebKit, Blink, Edge */ 64 | color: rgba(212, 210, 210, 0.69); 65 | } 66 | :-moz-placeholder { /* Mozilla Firefox 4 to 18 */ 67 | color: rgba(212, 210, 210, 0.69); 68 | opacity: 1; 69 | } 70 | ::-moz-placeholder { /* Mozilla Firefox 19+ */ 71 | color: rgba(212, 210, 210, 0.69); 72 | opacity: 1; 73 | } 74 | :-ms-input-placeholder { /* Internet Explorer 10-11 */ 75 | color: rgba(212, 210, 210, 0.69); 76 | } 77 | 78 | .newNotebookPage button { 79 | background-color: #28A956; 80 | width: 190px; 81 | height: 40px; 82 | margin: 20px 5px 5px 5px; 83 | border-radius: 4px; 84 | border-style: none; 85 | font-size: 18px; 86 | color: white; 87 | cursor: pointer; 88 | transition: all 0.3s ease 0s; 89 | } 90 | 91 | .newNotebookPage button:first-child { 92 | background-color: rgba(206, 206, 206, 0.62); 93 | color: gray; 94 | } 95 | 96 | 97 | .newNotebookPage button:last-child:hover { 98 | background: #387a09; 99 | } 100 | 101 | .newNotebookPage button:first-child:hover { 102 | background: lightgray; 103 | color: white; 104 | } 105 | 106 | @-webkit-keyframes fadein { 107 | from { opacity: 0 } 108 | to { opacity: 1 } 109 | }; 110 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notebooks/notebook_info.css: -------------------------------------------------------------------------------- 1 | /*this grabs the modal when it appears, 2 | but it's fragile. selector taken from 3 | selector tool in chrome dev tools*/ 4 | 5 | body > div:nth-child(6) > div { 6 | z-index: 20; 7 | } 8 | 9 | .notebookInfoPage { 10 | outline: none; 11 | height: 100vh; 12 | display: flex; 13 | padding-top: 190px; 14 | align-items: center; 15 | flex-direction: column; 16 | font-family: 'Yantramanav', sans-serif; 17 | font-size: 17px; 18 | font-weight: 300; 19 | -webkit-animation: fadein 1.5s; 20 | } 21 | 22 | .notebookInfoPage .notebookInfoHeader { 23 | display: flex; 24 | flex-direction: column; 25 | justify-content: center; 26 | align-items: center; 27 | padding-bottom: 14px; 28 | border-bottom: 1px solid rgb(212, 210, 210); 29 | } 30 | 31 | .notebookInfoPage .notebookInfoHeader > * { 32 | padding-top: 20px; 33 | } 34 | 35 | .editNotebookSection { 36 | margin-top: 30px; 37 | display: flex; 38 | flex-flow: column; 39 | align-items: flex-start; 40 | text-align: center; 41 | 42 | width: 545px; 43 | } 44 | 45 | /* .editNotebookSection > div > * { 46 | margin: 10px; 47 | } */ 48 | 49 | .notebookInfoPage a { 50 | text-decoration: none; 51 | color: #28A956; 52 | } 53 | 54 | .notebookInfoPage i { 55 | font-size: 40px; 56 | } 57 | 58 | .notebookInfoPage ul { 59 | list-style: none; 60 | } 61 | 62 | .notebookInfoPage ul li { 63 | font-weight: 300; 64 | text-align: left; 65 | margin-bottom: 10px; 66 | } 67 | 68 | .notebookInfoPage button { 69 | background-color: #28A956; 70 | width: 190px; 71 | height: 40px; 72 | margin: 20px 5px 5px 5px; 73 | border-radius: 4px; 74 | border-style: none; 75 | font-size: 18px; 76 | color: white; 77 | cursor: pointer; 78 | transition: all 0.3s ease 0s; 79 | } 80 | 81 | .notebookInfoPage input[type="text"] { 82 | margin: 10px; 83 | height: 35px; 84 | width: 450px; 85 | border-radius: 4px; 86 | border-style: none; 87 | border: 1px solid rgba(206, 206, 206, 0.62); 88 | font-size: 18px; 89 | outline: none; 90 | } 91 | 92 | .notebookInfoPage input[type="checkbox"] { 93 | cursor: pointer; 94 | margin-left: 10px; 95 | margin-bottom: 15px; 96 | } 97 | 98 | .notebookInfoPage button:first-of-type { 99 | background-color: rgba(206, 206, 206, 0.62); 100 | color: gray; 101 | } 102 | 103 | .notebookInfoPage button:last-of-type:hover { 104 | background: #387a09; 105 | } 106 | 107 | .notebookInfoPage button:first-of-type:hover { 108 | background: lightgray; 109 | color: white; 110 | } 111 | 112 | .notebookInfoPage .buttons { 113 | display: flex; 114 | margin-top: 30px; 115 | } 116 | 117 | @-webkit-keyframes fadein { 118 | from { opacity: 0 } 119 | to { opacity: 1 } 120 | }; 121 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notebooks/notebook_scrollbar.css: -------------------------------------------------------------------------------- 1 | /*remove this after done tesing with scrollbar*/ 2 | /*.ql-snow, .entireNotebookScrollMenu { 3 | display: none; 4 | }*/ 5 | 6 | .entireNotebookScrollMenu .searchField { 7 | display: none; 8 | width: 0; 9 | height: 0; 10 | } 11 | 12 | .entireNotebookScrollMenu { 13 | display: flex; 14 | justify-content: flex-start; 15 | } 16 | 17 | .entireNotebookScrollMenu > * { 18 | margin-right: 5px; 19 | color: gray; 20 | } 21 | 22 | .entireNotebookScrollMenu i { 23 | position: relative; 24 | top: 3.5px; 25 | left: 6px; 26 | } 27 | 28 | #newNoteDropdownSelectNotebook { 29 | outline: none; 30 | font-family: 'Yantramanav', sans-serif; 31 | background-color: transparent; 32 | border: 1px solid gray; 33 | font-size: 15px; 34 | width: 30%; 35 | margin: 0px 0px 0px 10px; 36 | } 37 | 38 | #homePageNotebookScrollbar { 39 | outline: none; 40 | font-family: 'Yantramanav', sans-serif; 41 | background-color: transparent; 42 | border: none; 43 | font-size: 15px; 44 | position: relative; 45 | top: 2px; 46 | } 47 | 48 | .entireNotebookScrollMenu:hover:after, 49 | .newNotePickNotebook:hover:after { 50 | position: absolute; 51 | color: white; 52 | background-color: darkgray; 53 | margin-left: -250px; 54 | margin-top: -30px; 55 | padding: 5px; 56 | font-family: 'Yantramanav', sans-serif; 57 | font-size: 12px; 58 | width: 220px; 59 | text-align: center; 60 | -webkit-animation: fadein 1s; 61 | z-index: 100; 62 | } 63 | 64 | .entireNotebookScrollMenu:hover:after { 65 | content: "MOVE NOTE TO ANOTHER NOTEBOOK"; 66 | margin-left: -20px; 67 | } 68 | 69 | .newNotePickNotebook:hover:after { 70 | content: "PICK A NOTEBOOK FOR YOUR NOTE"; 71 | width: 180px; 72 | } 73 | 74 | @keyframes fadein { 75 | from { opacity: 0 } 76 | to { opacity: 1 } 77 | }; 78 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notes.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the api/Notes controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notes/new_note.css: -------------------------------------------------------------------------------- 1 | .quill2bigContainer { 2 | position: relative; 3 | height: 100vh; 4 | width: 100%; 5 | display: flex; 6 | justify-content: center; 7 | font-family: 'Yantramanav', sans-serif; 8 | min-height: 620px; 9 | -webkit-animation: fadein 1.5s; 10 | } 11 | 12 | .quill2bigContainer .newNoteDoneButton { 13 | position: absolute; 14 | top: 15px; 15 | right: 15px; 16 | height: 30px; 17 | width: 65px; 18 | color: white; 19 | border-radius: 5px; 20 | background-color: #2DBE60; 21 | } 22 | 23 | .quill2bigContainer button:hover { 24 | background-color: #13853B; 25 | } 26 | 27 | .quillContainer2 { 28 | display: flex; 29 | flex-direction: column; 30 | max-width: 960px; 31 | min-width: 300px; 32 | margin-left: 50px; 33 | margin-top: 30px; 34 | position: relative; 35 | width: 100%; 36 | z-index: 0; 37 | } 38 | 39 | .quillContainer2 > div.quill > div.ql-container.ql-snow > div.ql-editor { 40 | margin-top: 60px; 41 | height: 70vh; 42 | font-size: 16px; 43 | padding: 12px 0px 0px 0px; 44 | } 45 | 46 | .quillContainer2 > div.quill > div.ql-toolbar.ql-snow { 47 | min-width: 570px; 48 | border-top: none; 49 | border-left: none; 50 | border-right: none; 51 | } 52 | 53 | .quillContainer2 .ql-container.ql-snow { 54 | height: 70vh; 55 | } 56 | 57 | .quillContainer2 .quill2InputTitle { 58 | outline: none; 59 | color: #2DBE60; 60 | font-size: 35px; 61 | font-weight: 200; 62 | overflow: hidden; 63 | position: absolute; 64 | top: 105px; 65 | width: 100%; 66 | border: none; 67 | } 68 | 69 | .quillContainer2 ::-webkit-input-placeholder { /* WebKit, Blink, Edge */ 70 | color: #2DBE60; 71 | } 72 | 73 | .quillContainer2 :-moz-placeholder { /* Mozilla Firefox 4 to 18 */ 74 | color: #2DBE60; 75 | opacity: 1; 76 | } 77 | 78 | .quillContainer2 ::-moz-placeholder { /* Mozilla Firefox 19+ */ 79 | color: #2DBE60; 80 | opacity: 1; 81 | } 82 | 83 | .quillContainer2 :-ms-input-placeholder { /* Internet Explorer 10-11 */ 84 | color: #2DBE60; 85 | } 86 | 87 | @-webkit-keyframes fadein { 88 | from { opacity: 0 } 89 | to { opacity: 1 } 90 | }; 91 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notes/note_form_topbar.css: -------------------------------------------------------------------------------- 1 | .entireNoteformTopbar { 2 | height: 50px; 3 | position: fixed; 4 | display: flex; 5 | justify-content: space-between; 6 | z-index: 1; 7 | } 8 | 9 | .topbarRightSide { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .topbarRightSide button { 16 | padding: 10px; 17 | } 18 | 19 | /*Don't display buttons on right side 20 | until features are added*/ 21 | .topbarRightSide { 22 | display: none; 23 | } 24 | 25 | /*Don't display reminder and shortcut 26 | buttons until they've been implemented*/ 27 | .topbarLeftSide button:first-child, 28 | .topbarLeftSide button:nth-child(2) { 29 | display: none; 30 | } 31 | 32 | .topbarLeftSide > * { 33 | outline: none; 34 | border-style: none; 35 | font-size: 20px; 36 | padding: 8px; 37 | background-color: transparent; 38 | color: gray; 39 | margin: 6px; 40 | cursor: pointer; 41 | margin-left: 16px 42 | } 43 | 44 | .topbarLeftSide > *:hover { 45 | color: #2DBE60; 46 | background-color: white; 47 | } 48 | 49 | .topbarLeftSide .fa-info-circle:hover:after, 50 | .topbarLeftSide .fa-trash:hover:after { 51 | font-family: 'Yantramanav', sans-serif; 52 | position: absolute; 53 | margin-left: -40px; 54 | margin-top: 22px; 55 | content: "Note info"; 56 | background-color: darkgray; 57 | color: white; 58 | padding: 5px; 59 | -webkit-animation: fadein 1s; 60 | width: 60px; 61 | font-size: 14px; 62 | } 63 | 64 | .topbarLeftSide .fa-info-circle:hover:after { 65 | content: "Note info"; 66 | } 67 | 68 | .topbarLeftSide .fa-trash:hover:after { 69 | content: "Delete note"; 70 | width: 65px; 71 | } 72 | 73 | @keyframes fadein { 74 | from { opacity: 0 } 75 | to { opacity: 1 } 76 | }; 77 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notes/note_info_page_modal.css: -------------------------------------------------------------------------------- 1 | /*this grabs the modal when it appears, 2 | but it's fragile. selector taken from 3 | selector tool in chrome dev tools*/ 4 | 5 | body > div:nth-child(5) > div { 6 | z-index: 4; 7 | } 8 | 9 | .noteInfoSpread { 10 | outline: none; 11 | height: 100vh; 12 | display: flex; 13 | justify-content: center; 14 | padding-top: 10%; 15 | font-family: 'Yantramanav', sans-serif; 16 | -webkit-animation: fadein 1.5s; 17 | } 18 | 19 | .noteInfoSpread .noteInfoHeader3 { 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | color: gray; 24 | } 25 | 26 | .noteInfoSpread figure { 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: center; 30 | align-items: center; 31 | padding-bottom: 20px; 32 | border-bottom: 1px solid rgb(212, 210, 210); 33 | 34 | } 35 | 36 | .noteInfoSpread figure > * { 37 | margin: 5px 38 | } 39 | 40 | .noteInfoSpread figure i { 41 | font-size: 40px; 42 | } 43 | 44 | .noteInfoSpread figure text { 45 | font-size: 15px; 46 | } 47 | 48 | .noteInfoSpread h1 { 49 | font-size: 50px; 50 | } 51 | 52 | .noteInfoSpread ul { 53 | margin-top: 30px; 54 | list-style: none; 55 | } 56 | 57 | .noteInfoSpread ul li { 58 | font-size: 20px; 59 | font-weight: 300; 60 | text-align: left; 61 | margin: 10px; 62 | } 63 | 64 | .noteInfoSpread button { 65 | background-color: #28A956; 66 | width: 100%; 67 | height: 40px; 68 | margin-top: 20px; 69 | margin-bottom: 5px; 70 | border-radius: 4px; 71 | border-style: none; 72 | font-size: 18px; 73 | color: white; 74 | cursor: pointer; 75 | transition: all 0.3s ease 0s; 76 | } 77 | 78 | .noteInfoSpread button:hover { 79 | background: #387a09; 80 | } 81 | 82 | @-webkit-keyframes fadein { 83 | from { opacity: 0 } 84 | to { opacity: 1 } 85 | }; 86 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/notes/note_item.css: -------------------------------------------------------------------------------- 1 | /*hide this until feature is implemented*/ 2 | .wholeNoteItem .fa-star { 3 | display: none; 4 | } 5 | 6 | .wholeNoteItem { 7 | position: relative; 8 | width: 100%; 9 | height: 120px; 10 | overflow: hidden; 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | border-bottom: 1px solid lightgray; 15 | cursor: pointer; 16 | } 17 | 18 | .noteItemButtons { 19 | display: none; 20 | margin-right: 5px; 21 | 22 | } 23 | 24 | .wholeNoteItem:hover .noteItemButtons { 25 | display: inherit; 26 | position: absolute; 27 | right: 0; 28 | top: 0; 29 | } 30 | 31 | .noteItemButtons > * { 32 | outline: none; 33 | border-style: none; 34 | font-size: 20px; 35 | padding: 8px; 36 | background-color: #2DBE60; 37 | cursor: pointer; 38 | } 39 | 40 | .noteItemButtons button .fa { 41 | cursor: pointer; 42 | } 43 | 44 | .noteItemBox { 45 | height: 98px; 46 | } 47 | 48 | .noteItemTitle { 49 | font-size: 18px; 50 | } 51 | 52 | .timePassed { 53 | font-size: 13px; 54 | padding-bottom: 5px; 55 | text-transform: uppercase; 56 | } 57 | 58 | .bodySnippet { 59 | font-size: 13px; 60 | width: 310px; 61 | } 62 | 63 | .timePassed, .bodySnippet { 64 | color: gray; 65 | } 66 | 67 | .wholeNoteItem:hover div, 68 | .wholeNoteItem:hover button, 69 | .wholeNoteItem:hover { 70 | color: white; 71 | background-color: #2DBE60; 72 | } 73 | 74 | .wholeNoteItem .fa:hover { 75 | display: inherit; 76 | transform: scale(1.5); 77 | color: #E6E6E6; 78 | } 79 | 80 | /*only show these buttons when features are developed*/ 81 | .wholeNoteItem .fa-clock-o, 82 | .wholeNoteItem .fa-comments { 83 | display: none; 84 | } 85 | 86 | .wholeNoteItem .fa-trash:hover:after { 87 | font-family: 'Yantramanav', sans-serif; 88 | position: absolute; 89 | margin-left: -72px; 90 | background-color: darkgray; 91 | color: white; 92 | padding: 3px; 93 | -webkit-animation: fadein 1s; 94 | width: 3s0px; 95 | font-size: 10px; 96 | content: "Delete note"; 97 | } 98 | 99 | @keyframes fadein { 100 | from { opacity: 0 } 101 | to { opacity: 1 } 102 | }; 103 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/session/splashSideoutModal.css: -------------------------------------------------------------------------------- 1 | .splashSidebarContainer { 2 | font-family: 'Yantramanav', sans-serif; 3 | font-weight: 300; 4 | position: fixed; 5 | height: 100%; 6 | width: 15vh; 7 | min-width: 300px; 8 | background: rgba(0, 0, 0, 0.9); 9 | z-index: 1; 10 | right: 0; 11 | top: 0; 12 | margin: 0; 13 | -webkit-animation: slidein 0.5s; 14 | } 15 | 16 | .splashSidebarContainer .buttonContainer { 17 | display: flex; 18 | justify-content: flex-end; 19 | margin-right: 15px; 20 | margin-top: 15px; 21 | } 22 | 23 | .splashSidebarContainer button { 24 | color: white; 25 | border: none; 26 | background-color: transparent; 27 | padding: 18px; 28 | cursor: pointer; 29 | font-size: 16px; 30 | outline: none; 31 | } 32 | 33 | .splashSidebarLinks { 34 | margin-left: 30px; 35 | } 36 | 37 | .splashSidebarLinks div > * { 38 | color: white; 39 | text-decoration: none; 40 | } 41 | 42 | .splashSidebarLinks .linksgroup1, 43 | .splashSidebarLinks .linksgroup2 { 44 | display: flex; 45 | flex-direction: column; 46 | font-size: 20px; 47 | padding-bottom: 15px; 48 | border-bottom: 1px solid rgba(243, 241, 241, 0.47); 49 | } 50 | 51 | .splashSidebarLinks .linksgroup1 > * { 52 | margin-bottom: 10px; 53 | } 54 | 55 | .splashSidebarLinks .linksgroup2 { 56 | margin-top: 100px; 57 | padding-bottom: 0px; 58 | } 59 | 60 | .splashSidebarLinks .linksgroup2 > * { 61 | margin-bottom: 40px; 62 | } 63 | 64 | @-webkit-keyframes slidein { 65 | from { right: -25% } 66 | to { right: 0 } 67 | }; 68 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/tags.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the api/tags controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/tags/tagbar.css: -------------------------------------------------------------------------------- 1 | .quillContainer .entireTagBar { 2 | /*border: 1px solid red;*/ 3 | width: 100%; 4 | display: flex; 5 | } 6 | 7 | .quillContainer .entireTagBar .existingTagsList { 8 | list-style: none; 9 | display: flex; 10 | } 11 | 12 | .quillContainer .entireTagBar .existingTagsList li { 13 | margin-left: 3px; 14 | border: 1px solid lightgray; 15 | padding: 0px 5px 0px 5px; 16 | font-size: 14px; 17 | line-height: 22px; 18 | border-radius: 5px; 19 | background-color: #F8F8F8; 20 | display: flex; 21 | } 22 | 23 | .quillContainer .entireTagBar .existingTagsList li .tagBarTagText { 24 | white-space: nowrap; 25 | } 26 | 27 | .quillContainer .entireTagBar .existingTagsList li button { 28 | margin-left: 3px; 29 | background-color: transparent; 30 | /*border: 1px solid red;*/ 31 | color: rgba(0, 0, 0, 0.5); 32 | cursor: pointer; 33 | -webkit-transform: scaleY(0.7); 34 | } 35 | 36 | .quillContainer .entireTagBar i { 37 | color: gray; 38 | position: relative; 39 | top: 4px; 40 | } 41 | 42 | .quillContainer .entireTagBar > * { 43 | margin-right: 6px; 44 | } 45 | 46 | .quillContainer .entireTagBar input { 47 | outline: none; 48 | border: none; 49 | width: 100%; 50 | font-size: 13px; 51 | position: relative; 52 | right: 3px; 53 | cursor: pointer; 54 | } 55 | 56 | .quillContainer .entireTagBar input::-webkit-input-placeholder { /* WebKit, Blink, Edge */ 57 | color: rgba(0, 0, 0, 0.5); 58 | } 59 | .quillContainer .entireTagBar input:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ 60 | color: rgba(0, 0, 0, 0.5); 61 | opacity: 1; 62 | } 63 | .quillContainer .entireTagBar input::-moz-placeholder { /* Mozilla Firefox 19+ */ 64 | color: rgba(0, 0, 0, 0.5); 65 | opacity: 1; 66 | } 67 | .quillContainer .entireTagBar input:-ms-input-placeholder { /* Internet Explorer 10-11 */ 68 | color: rgba(0, 0, 0, 0.5); 69 | } 70 | .quillContainer .entireTagBar input::-ms-input-placeholder { /* Microsoft Edge */ 71 | color: rgba(0, 0, 0, 0.5); 72 | } 73 | 74 | .quillContainer .entireTagBar:hover:after { 75 | position: absolute; 76 | color: white; 77 | background-color: darkgray; 78 | margin-top: -30px; 79 | padding: 5px; 80 | font-family: 'Yantramanav', sans-serif; 81 | font-size: 12px; 82 | width: 30px; 83 | text-align: center; 84 | z-index: 100; 85 | content: "TAGS"; 86 | -webkit-animation: fadein 1s; 87 | } 88 | -------------------------------------------------------------------------------- /app/assets/stylesheets/api/users.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the api/users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | *= require_session 16 | *= require_loading-icon 17 | */ 18 | 19 | 20 | /* --- Reset user agent styles --- */ 21 | html, body, header, h1, h2, nav, ul, li, a, footer, small, button, submit { 22 | margin: 0; 23 | padding: 0; 24 | border: 0; 25 | font: inherit; 26 | outline: none; 27 | } 28 | 29 | #root { 30 | overflow: hidden; 31 | } 32 | -------------------------------------------------------------------------------- /app/assets/stylesheets/loading-icon.css: -------------------------------------------------------------------------------- 1 | .loadingContainer { 2 | display: flex; 3 | height: 100vh; 4 | justify-content: center; 5 | align-items: center; 6 | background: white; 7 | width: 350px; 8 | } 9 | 10 | /*.loadingContainer img { 11 | width: 50px; 12 | height: auto; 13 | }*/ 14 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/api/notebooks_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::NotebooksController < ApplicationController 2 | def index 3 | @notebooks = current_user.notebooks 4 | end 5 | 6 | def create 7 | @notebook = Notebook.new(notebook_params) 8 | @notebook.author_id = current_user.id 9 | 10 | if @notebook.save! 11 | render :show 12 | else 13 | errors = @notebook.errors.full_messages 14 | render json: errors, status: 422 15 | end 16 | end 17 | 18 | def show 19 | @notebook = current_user.notebooks.find(params[:id]) 20 | 21 | if @notebook 22 | render :show 23 | else 24 | errors = ["Couldn't find notebook"] 25 | render json: errors, status: 422 26 | end 27 | end 28 | 29 | def update 30 | @notebook = current_user.notebooks.find(params[:id]) 31 | mod_params = notebook_params 32 | prev_default = current_user.notebooks.find_by_defaultNotebook(true) 33 | 34 | if @notebook == prev_default 35 | mod_params = {title: params[:notebook][:title], defaultNotebook: true} 36 | end 37 | 38 | if @notebook.update!(mod_params) 39 | if @notebook != prev_default && (params[:notebook][:defaultNotebook] == "true") 40 | prev_default.update({defaultNotebook: false}) 41 | end 42 | render :show 43 | else 44 | errors = @notebook.errors.full_messages 45 | render json: errors, status: 422 46 | end 47 | end 48 | 49 | def destroy 50 | @notebook = current_user.notebooks.find(params[:id]) 51 | 52 | if @notebook == current_user.notebooks.find_by_defaultNotebook(true) 53 | errors = ["Cannot delete your default notebook!"] 54 | render json: errors, status: 422 55 | return 56 | end 57 | 58 | if @notebook.destroy 59 | default_notebook_id = current_user.notebooks.find_by_defaultNotebook(true).id 60 | 61 | @notebook.notes.each do |note| 62 | note.update({notebook_id: default_notebook_id}) 63 | end 64 | 65 | render :show 66 | else 67 | errors = @notebook.errors.full_messages 68 | render json: errors, status: 422 69 | end 70 | end 71 | 72 | private 73 | 74 | def notebook_params 75 | params.require(:notebook).permit(:title, :defaultNotebook) 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /app/controllers/api/notes_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::NotesController < ApplicationController 2 | def index 3 | @notes = current_user.notes 4 | end 5 | 6 | def create 7 | @note = Note.new(note_params) 8 | 9 | #will params present themselves in this format? may not work! 10 | if !params[:note][:notebook_id] || params[:note][:notebook_id] == "" 11 | @note.notebook_id = current_user.notebooks.find_by_defaultNotebook(true).id 12 | end 13 | 14 | if !params[:note][:title] || params[:note][:title] == "" 15 | @note.title = "Untitled" 16 | end 17 | 18 | if @note.save 19 | render :show 20 | else 21 | errors = @note.errors.full_messages 22 | render json: errors, status: 422 23 | end 24 | end 25 | 26 | def update 27 | @note = current_user.notes.find(params[:id]) 28 | 29 | mod_note_params = note_params 30 | 31 | default_notebook_id = current_user.notebooks.find_by_defaultNotebook(true).id 32 | 33 | if params[:note][:title] == '' && params[:note][:notebook_id] == '' 34 | mod_note_params = {title: 'Untitled', body: params[:note][:body], notebook_id: default_notebook_id} 35 | elsif params[:note][:title] == '' && params[:note][:notebook_id] != '' 36 | mod_note_params = {title: 'Untitled', body: params[:note][:body], notebook_id: params[:note][:notebook_id]} 37 | elsif params[:note][:title] != '' && params[:note][:notebook_id] == '' 38 | mod_note_params = {title: params[:note][:title], body: params[:note][:body], notebook_id: default_notebook_id} 39 | end 40 | 41 | if @note.update(mod_note_params) 42 | render :show 43 | else 44 | errors = @note.errors.full_messages 45 | render json: errors, status: 422 46 | end 47 | end 48 | 49 | def show 50 | @note = current_user.notes.find(params[:id]) 51 | 52 | if @note 53 | render :show 54 | else 55 | errors = ["Couldn't find note"] 56 | render json: errors, status: 422 57 | end 58 | end 59 | 60 | def destroy 61 | @note = current_user.notes.find(params[:id]) 62 | 63 | if @note.destroy 64 | render :show 65 | else 66 | errors = @note.errors.full_messages 67 | render json: errors, status: 422 68 | end 69 | end 70 | 71 | private 72 | 73 | def note_params 74 | params.require(:note).permit(:title, :body, :notebook_id) 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /app/controllers/api/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::SessionsController < ApplicationController 2 | def create 3 | email, password = params[:user][:email], params[:user][:password] 4 | @user = User.find_by_credentials(email, password) 5 | 6 | if @user 7 | signin!(@user) 8 | render 'api/sessions/show' 9 | else 10 | case 11 | when email == "" && password == "" 12 | errors = ["Email and password can't be blank"] 13 | when email == "" && password != "" 14 | errors = ["Email can't be blank"] 15 | when email != "" && password == "" 16 | errors = ["Password can't be blank"] 17 | when !/\A\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+\z/.match(email) 18 | errors = ["Email has incorrect format"] 19 | else 20 | errors = ["Invalid username/password"] 21 | end 22 | render json: errors, status: 422 23 | end 24 | end 25 | 26 | def destroy 27 | logout! 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/api/tags_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::TagsController < ApplicationController 2 | def index 3 | @tags = current_user.tags.group('tags.id') 4 | render :show 5 | end 6 | 7 | def create 8 | new_tags_ids = [] 9 | JSON.parse(tag_params[:names]).each do |name| 10 | existing_tag = Tag.find_by_name(name) 11 | tag_id_to_add = existing_tag ? existing_tag.id : Tag.create(name: name).id 12 | new_tags_ids << tag_id_to_add 13 | end 14 | 15 | ids_of_destroyed_tags = 16 | current_user.notes.find(tag_params[:note_id]).tag_ids - new_tags_ids 17 | 18 | current_user.notes.find(tag_params[:note_id]).tag_ids = new_tags_ids 19 | 20 | @destroyed_tags_to_update = Tag.where(id: ids_of_destroyed_tags) 21 | 22 | @tags = current_user.notes.find(tag_params[:note_id]).tags 23 | render :create 24 | end 25 | 26 | # this shouldn't be necessary because note.tags works 27 | # def show 28 | # @tags = current_user.notes.find(tag_params[:note_id]).tags 29 | # end 30 | 31 | def update 32 | proposed_tag_name = JSON.parse(tag_params[:names])[0] 33 | existing_tag = Tag.find_by_name(proposed_tag_name) 34 | 35 | @tag = existing_tag ? existing_tag : Tag.create(name: proposed_tag_name) 36 | 37 | current_user.taggings.each do |tagging| 38 | if tagging.tag_id == params[:id].to_i 39 | tagging.update(tag_id: @tag.id) 40 | end 41 | end 42 | 43 | render :single_tag 44 | end 45 | 46 | def destroy 47 | @tag = Tag.find(params[:id]) 48 | current_user.taggings.each do |tagging| 49 | if tagging.tag_id == @tag.id 50 | tagging.destroy 51 | end 52 | end 53 | 54 | render :single_tag 55 | end 56 | 57 | private 58 | 59 | def tag_params 60 | params.require(:tag).permit(:names, :note_id) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::UsersController < ApplicationController 2 | 3 | def create 4 | @user = User.new(user_params) 5 | 6 | if @user.save 7 | signin!(@user) 8 | render :show 9 | else 10 | errors = @user.errors.full_messages 11 | render json: errors, status: 422 12 | end 13 | end 14 | 15 | private 16 | 17 | def user_params 18 | params.require(:user).permit(:email, :password) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | 4 | helper_method :current_user, :logged_in? 5 | 6 | def current_user 7 | @current_user ||= User.find_by(session_token: session[:session_token]) 8 | end 9 | 10 | def logged_in? 11 | !!current_user 12 | end 13 | 14 | def signin!(user) 15 | session[:session_token] = user.reset_session_token! 16 | end 17 | 18 | def logout! 19 | if current_user 20 | current_user.reset_session_token! 21 | session[:session_token] = nil 22 | render json: {} 23 | else 24 | render json: 'No one logged in', status: 404 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/static_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticPagesController < ApplicationController 2 | def root 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/api/notebooks_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::NotebooksHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/notes_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::NotesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/tags_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::TagsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/users_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/note.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: notes 4 | # 5 | # id :integer not null, primary key 6 | # title :string not null 7 | # body :text 8 | # notebook_id :integer not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | 13 | class Note < ApplicationRecord 14 | validates :title, :notebook, presence: true 15 | 16 | belongs_to :notebook 17 | 18 | has_one :author, 19 | through: :notebook, 20 | source: :author 21 | 22 | has_many :taggings, dependent: :destroy 23 | 24 | has_many :tags, 25 | through: :taggings, 26 | source: :tag 27 | end 28 | -------------------------------------------------------------------------------- /app/models/notebook.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: notebooks 4 | # 5 | # id :integer not null, primary key 6 | # author_id :integer not null 7 | # title :string not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # defaultNotebook :boolean default("false"), not null 11 | # 12 | 13 | class Notebook < ApplicationRecord 14 | validates :title, :author, presence: true 15 | validates_uniqueness_of :title, scope: :author 16 | 17 | belongs_to :author, 18 | primary_key: :id, 19 | foreign_key: :author_id, 20 | class_name: :User 21 | 22 | has_many :notes 23 | end 24 | 25 | #consider db and model level validations to make sure only one default 26 | #notebook is possible 27 | # validate :only_one_default_notebook_possible 28 | # 29 | # def only_one_default_notebook_possible 30 | # if Notebook.all 31 | # end 32 | # validate :expiration_date_cannot_be_in_the_past, 33 | # :discount_cannot_be_greater_than_total_value 34 | # 35 | # def expiration_date_cannot_be_in_the_past 36 | # if expiration_date.present? && expiration_date < Date.today 37 | # errors.add(:expiration_date, "can't be in the past") 38 | # end 39 | # end 40 | -------------------------------------------------------------------------------- /app/models/tag.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: tags 4 | # 5 | # id :integer not null, primary key 6 | # name :string not null 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | class Tag < ApplicationRecord 12 | validates :name, presence: true, uniqueness: true 13 | 14 | has_many :taggings, dependent: :destroy 15 | 16 | has_many :notes, 17 | through: :taggings, 18 | source: :note 19 | end 20 | -------------------------------------------------------------------------------- /app/models/tagging.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: taggings 4 | # 5 | # id :integer not null, primary key 6 | # tag_id :integer not null 7 | # note_id :integer not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not n ull 10 | # 11 | 12 | class Tagging < ApplicationRecord 13 | validates :note, :tag, presence: true 14 | validates_uniqueness_of :tag, scope: :note 15 | 16 | belongs_to :note 17 | belongs_to :tag 18 | end 19 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # email :string not null 7 | # password_digest :string not null 8 | # session_token :string not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | 13 | class User < ApplicationRecord 14 | attr_reader :password 15 | 16 | validates :email, :password_digest, :session_token, presence: true 17 | validates :password, length: { minimum: 6, allow_nil: true } 18 | 19 | validates :email, uniqueness: true, 20 | format: { with: /\A\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+\z/, 21 | message: "has incorrect format"} 22 | 23 | after_initialize :ensure_session_token 24 | after_create_commit :create_default_notebook 25 | 26 | has_many :notebooks, 27 | primary_key: :id, 28 | foreign_key: :author_id, 29 | class_name: :Notebook 30 | 31 | has_many :notes, 32 | through: :notebooks, 33 | source: :notes 34 | 35 | has_many :taggings, 36 | through: :notes, 37 | source: :taggings 38 | 39 | has_many :tags, 40 | through: :notes, 41 | source: :tags 42 | 43 | def self.find_by_credentials(email, password) 44 | user = User.find_by email: email 45 | return user if user && user.valid_password?(password) 46 | nil 47 | end 48 | 49 | def reset_session_token! 50 | self.session_token = User.generate_session_token 51 | self.save! 52 | self.session_token 53 | end 54 | 55 | def password=(password) 56 | @password = password 57 | self.password_digest = BCrypt::Password.create(password) 58 | end 59 | 60 | def valid_password?(password) 61 | BCrypt::Password.new(self.password_digest).is_password?(password) 62 | end 63 | 64 | private 65 | 66 | def self.generate_session_token 67 | SecureRandom.urlsafe_base64(16) 68 | end 69 | 70 | def ensure_session_token 71 | self.session_token ||= User.generate_session_token 72 | end 73 | 74 | def create_default_notebook 75 | self.notebooks.create(title: 'MyDefaultNotebook', defaultNotebook: true) 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /app/views/api/notebooks/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @notebooks.each do |notebook| 2 | note_count = notebook.notes.length 3 | 4 | json.set! notebook.id do 5 | json.extract! notebook, :id, :title, :author_id, :created_at, :updated_at, :defaultNotebook 6 | json.note_count note_count 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/api/notebooks/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @notebook, :id, :title, :author_id, :created_at, :updated_at, :defaultNotebook 2 | -------------------------------------------------------------------------------- /app/views/api/notes/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @notes.each do |note| 2 | json.set! note.id do 3 | json.extract! note, :id, :title, :body, :created_at, :updated_at, :notebook_id, :tags 4 | json.notebookId note.notebook.id 5 | end 6 | end 7 | 8 | # note_created_date = note.created_at.strftime("%m/%d/%Y at %I:%M%p") 9 | # note_updated_date = note.updated_at.strftime("%m/%d/%Y at %I:%M%p") 10 | -------------------------------------------------------------------------------- /app/views/api/notes/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @note, :id, :title, :body, :created_at, :updated_at, :notebook_id, :tags 2 | 3 | 4 | # json.tags JSON.@note.tags 5 | # 6 | # @note.tags.each do |tag| 7 | # json.set! tag.id do 8 | # json.extract! tag, :id, :name 9 | # end 10 | # end 11 | -------------------------------------------------------------------------------- /app/views/api/sessions/_session.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! user, :id, :email 2 | -------------------------------------------------------------------------------- /app/views/api/sessions/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! 'api/sessions/session', user: @user 2 | -------------------------------------------------------------------------------- /app/views/api/tags/create.json.jbuilder: -------------------------------------------------------------------------------- 1 | @tags.each do |tag| 2 | notes_ids = tag.notes.map { |note| note.id } 3 | json.set! :tags do 4 | json.set! tag.id do 5 | json.extract! tag, :id, :name 6 | json.notesIds notes_ids 7 | json.notesCount tag.notes.length 8 | end 9 | end 10 | end 11 | 12 | @destroyed_tags_to_update.each do |tag| 13 | notes_ids = tag.notes.map { |note| note.id } 14 | json.set! :destroyedTagsToUpdate do 15 | json.set! tag.id do 16 | json.extract! tag, :id, :name 17 | json.notesIds notes_ids 18 | json.notesCount tag.notes.length 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/api/tags/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | @tags.each do |tag| 2 | notes_ids = tag.notes.map { |note| note.id } 3 | json.set! tag.id do 4 | json.extract! tag, :id, :name 5 | json.notesIds notes_ids 6 | json.notesCount tag.notes.length 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/api/tags/single_tag.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @tag, :id, :name 2 | json.notesIds @tag.notes.map { |note| note.id } 3 | json.notesCount @tag.notes.length 4 | -------------------------------------------------------------------------------- /app/views/api/users/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @user, :id, :email 2 | 3 | @user.notebooks.each do |notebook| 4 | json.set! notebook.id do 5 | json.extract! notebook, :id, :title, :defaultNotebook 6 | end 7 | end 8 | 9 | #if you want to put in a partial you can following this: 10 | # json.partial! 'api/users/user', user: @user 11 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Niftynote 6 | 7 | <%= csrf_meta_tags %> 8 | 9 | <%= stylesheet_link_tag 'application', media: 'all' %> 10 | <%= javascript_include_tag 'application' %> 11 | 12 | <%= analytics_init if Rails.env.production? %> 13 | 14 | 15 | 16 | 17 | <%= yield %> 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/static_pages/root.html.erb: -------------------------------------------------------------------------------- 1 | 7 | 8 |
React not working!
9 | 10 | <%# you changed this file from the partial to sessions/show cuz you deleted the partial %> 11 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Niftynote 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload) 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => 'public, max-age=172800' 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | # Debug mode disables concatenation and preprocessing of assets. 41 | # This option may cause significant delays in view rendering with a large 42 | # number of complex assets. 43 | config.assets.debug = true 44 | 45 | # Suppress logger output for asset requests. 46 | config.assets.quiet = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => 'public, max-age=3600' 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 6 | 7 | # Enable per-form CSRF tokens. Previous versions had false. 8 | Rails.application.config.action_controller.per_form_csrf_tokens = true 9 | 10 | # Enable origin-checking CSRF mitigation. Previous versions had false. 11 | Rails.application.config.action_controller.forgery_protection_origin_check = true 12 | 13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 14 | # Previous versions had false. 15 | ActiveSupport.to_time_preserves_timezone = true 16 | 17 | # Require `belongs_to` associations by default. Previous versions had false. 18 | Rails.application.config.active_record.belongs_to_required_by_default = true 19 | 20 | # Do not halt callback chains when a callback returns false. Previous versions had true. 21 | ActiveSupport.halt_callback_chains_on_return_false = false 22 | 23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 25 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_Niftynote_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum, this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # The code in the `on_worker_boot` will be called if you are using 36 | # clustered mode by specifying a number of `workers`. After each worker 37 | # process is booted this block will be run, if you are using `preload_app!` 38 | # option you will want to use this block to reconnect to any threads 39 | # or connections that may have been created at application boot, Ruby 40 | # cannot share connections between processes. 41 | # 42 | # on_worker_boot do 43 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 44 | # end 45 | 46 | # Allow puma to be restarted by `rails restart` command. 47 | plugin :tmp_restart 48 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root to: 'static_pages#root' 3 | 4 | namespace :api, default: { format: :json } do 5 | resources :users, only: [:create] 6 | resource :session, only: [:create, :destroy] 7 | resources :notebooks, except: [:new, :edit] 8 | resources :notes, except: [:new, :edit] 9 | resources :tags, except: [:new, :edit] 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 9feb7fc0e2d8a775a74dc97b7442e7d3c5c7e09ef7f476e8bcd5d8223e435b3106b842e49ea05eb25dc583b84bf4cf43a8be2d07839804cc0de3d81621c95a52 15 | 16 | test: 17 | secret_key_base: a73886a3cb89e202039d7c517405fb7347ad6a11b258b947eadb8b6997944ecd45a510973055b98e1c0e505e51236c8afcb5eb78e173e927c6e73a5168979030 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /db/migrate/20170418162509_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :users do |t| 4 | t.string :email, null: false 5 | t.string :password_digest, null: false 6 | t.string :session_token, null: false 7 | 8 | t.timestamps 9 | end 10 | 11 | add_index :users, :email, unique: true 12 | add_index :users, :session_token 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20170421022646_create_notebooks.rb: -------------------------------------------------------------------------------- 1 | class CreateNotebooks < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :notebooks do |t| 4 | t.integer :author_id, null: false 5 | t.string :title, null: false 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :notebooks, :author_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20170421022851_create_notes.rb: -------------------------------------------------------------------------------- 1 | class CreateNotes < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :notes do |t| 4 | t.string :title, null: false 5 | t.text :body, null: false 6 | t.integer :author_id 7 | t.integer :notebook_id 8 | 9 | t.timestamps 10 | end 11 | 12 | add_index :notes, :author_id 13 | add_index :notes, :notebook_id 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20170421041353_delete_author_id_column_from_notes_table.rb: -------------------------------------------------------------------------------- 1 | class DeleteAuthorIdColumnFromNotesTable < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :notes, :author_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170421042844_add_null_false_to_notebook_id_in_notes.rb: -------------------------------------------------------------------------------- 1 | class AddNullFalseToNotebookIdInNotes < ActiveRecord::Migration[5.0] 2 | def change 3 | change_column_null :notes, :notebook_id, false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170421123310_add_default_tag_to_notebooks.rb: -------------------------------------------------------------------------------- 1 | class AddDefaultTagToNotebooks < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :notebooks, :defaultNotebook, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170421124106_add_null_false_to_default_tag_in_notebooks.rb: -------------------------------------------------------------------------------- 1 | class AddNullFalseToDefaultTagInNotebooks < ActiveRecord::Migration[5.0] 2 | def change 3 | change_column_null :notebooks, :defaultNotebook, false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170421161450_add_unique_scoping_to_title_in_notebooks.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueScopingToTitleInNotebooks < ActiveRecord::Migration[5.0] 2 | def change 3 | add_index :notebooks, [:title, :author_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170430043631_remove_null_constraint_on_notes_for_body.rb: -------------------------------------------------------------------------------- 1 | class RemoveNullConstraintOnNotesForBody < ActiveRecord::Migration[5.0] 2 | def change 3 | change_column_null :notes, :body, true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170512020834_create_tags.rb: -------------------------------------------------------------------------------- 1 | class CreateTags < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :tags do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20170512020905_create_taggings.rb: -------------------------------------------------------------------------------- 1 | class CreateTaggings < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :taggings do |t| 4 | t.integer :tag_id 5 | t.integer :note_id 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :taggings, :tag_id 11 | add_index :taggings, :note_id 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20170512034336_add_null_constraints_to_tags_and_taggings.rb: -------------------------------------------------------------------------------- 1 | class AddNullConstraintsToTagsAndTaggings < ActiveRecord::Migration[5.0] 2 | def change 3 | change_column_null :tags, :name, false 4 | change_column_null :taggings, :tag_id, false 5 | change_column_null :taggings, :note_id, false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20170512160114_add_db_uniqueness_constraint_to_taggings.rb: -------------------------------------------------------------------------------- 1 | class AddDbUniquenessConstraintToTaggings < ActiveRecord::Migration[5.0] 2 | def change 3 | add_index :taggings, [:note_id, :tag_id], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | 9 | User.destroy_all 10 | Notebook.destroy_all 11 | Note.destroy_all 12 | Tag.destroy_all 13 | Tagging.destroy_all 14 | 15 | user1 = User.create!(email: 'guest@example.com', password: 'password') 16 | user2 = User.create!(email: 'jon@example.com', password: 'password') 17 | 18 | notebook1 = user1.notebooks.create!(title: 'Star Wars') 19 | notebook2 = user1.notebooks.create!(title: 'Friends') 20 | notebook3 = user1.notebooks.create!(title: 'Harry Potter') 21 | 22 | notebook4 = user2.notebooks.create!(title: 'Pokemon') 23 | 24 | 20.times do |i| 25 | notebook1.notes.create!( 26 | title: Faker::StarWars.character, 27 | body: Faker::StarWars.quote 28 | ) 29 | end 30 | 31 | 20.times do |i| 32 | notebook2.notes.create!( 33 | title: Faker::Friends.character, 34 | body: Faker::Friends.quote 35 | ) 36 | end 37 | 38 | 20.times do |i| 39 | notebook3.notes.create!( 40 | title: Faker::HarryPotter.character, 41 | body: Faker::HarryPotter.quote 42 | ) 43 | end 44 | 45 | 20.times do |i| 46 | notebook4.notes.create!( 47 | title: Faker::Pokemon.name, 48 | body: Faker::Pokemon.location 49 | ) 50 | end 51 | 52 | tag1 = Tag.create!(name: 'tag1') 53 | tag2 = Tag.create!(name: 'tag2') 54 | tag3 = Tag.create!(name: 'tag3') 55 | 56 | Tagging.create!(tag_id: tag1.id, note_id: user1.notes.first.id) 57 | Tagging.create!(tag_id: tag2.id, note_id: user1.notes.first.id) 58 | Tagging.create!(tag_id: tag1.id, note_id: user1.notes[1].id) 59 | Tagging.create!(tag_id: tag2.id, note_id: user1.notes[2].id) 60 | Tagging.create!(tag_id: tag1.id, note_id: user1.notes.last.id) 61 | Tagging.create!(tag_id: tag2.id, note_id: user1.notes.last.id) 62 | 63 | Tagging.create!(tag_id: tag3.id, note_id: user2.notes.first.id) 64 | Tagging.create!(tag_id: tag1.id, note_id: user2.notes.first.id) 65 | Tagging.create!(tag_id: tag3.id, note_id: user2.notes[1].id) 66 | 67 | Tagging.create!(tag_id: tag3.id, note_id: user1.notes.last.id) 68 | 69 | #sample for how to use faker, gem would have to be included 70 | # 10.times do |i| 71 | # u = User.create!(username: Faker::Name.name, password: "password") 72 | # c = Cat.create!( 73 | # user_id: u.id, 74 | # name: Faker::Hipster.word, 75 | # color: Cat::CAT_COLORS.sample, 76 | # sex: ["M", "F"].sample, 77 | # description: Faker::Hipster.sentence, 78 | # birth_date: Faker::Date.between(10.years.ago, Date.today) 79 | # ) 80 | # end 81 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Niftynote 2 | 3 | * [Heroku link](https://niftynote.herokuapp.com/) 4 | * [Trello link](https://trello.com/b/JfAYGhwV/niftynote) 5 | 6 | ## Minimum Viable Product 7 | 8 | Inspired by Evernote, Niftynote is a full-stack web application that allows its users to seamlessly create, edit and organize their notes. 9 | 10 | Within two weeks, this app will demonstrate the following features with styling and smooth navigation: 11 | - [ ] New account creation, user login and guest login 12 | - [ ] Hosting on Heroku 13 | - [ ] Notes 14 | - [ ] Notebooks to organize notes 15 | - [ ] Tags 16 | - [ ] Rich-text editing 17 | - [ ] Adequate seeding for demos 18 | - [ ] Production README 19 | 20 | ## Design Documentation 21 | * [View Wireframes][wireframes] 22 | * [React components][components] 23 | * [API endpoints][api-endpoints] 24 | * [Database schema][schema] 25 | * [Sample State][sample-state] 26 | 27 | [wireframes]: wireframes 28 | [components]: component-hierarchy.md 29 | [sample-state]: sample-state.md 30 | [api-endpoints]: api-endpoints.md 31 | [schema]: schema.md 32 | 33 | ## Implementation Timeline 34 | 35 | ### Phase 1: Setup backend and frontend User Authentication (2 days) 36 | 37 | **Objective:** Allow users to securely create accounts, sign in and sign out of the web application 38 | 39 | ### Phase 2: Notebooks (2 days) 40 | 41 | **Objective:** Setup notebooks that each have many notes and can also be created, read, updated and destroyed 42 | 43 | ### Phase 3: Build out notes component (3 days) 44 | 45 | **Objective 1:** Setup CRUD (create, read, update and destroy) functionality for notes through the API 46 | 47 | **Objective 2:** Add rich text editing so note text can be bolded, italicized, hyperlinked, etc. 48 | 49 | ### Phase 4: Tags (1 day) 50 | 51 | **Objective:** Give users the ability to tag notes with multiple tags, untag notes and delete tags 52 | 53 | ### Phase 5 (Bonus): - Infinite scroll for notes index (W2 Friday) 54 | 55 | **Objective:** Older notes in notes index are automatically fetched and rendered as user scrolls down 56 | 57 | ### Potential Bonus Features 58 | - [ ] Allow sharing of notes/notebooks 59 | - [ ] Add search for notes, notebooks and tags 60 | - [ ] Set reminders on notes 61 | - [ ] Work chat 62 | - [ ] Mark notes as starred (shortcuts) 63 | -------------------------------------------------------------------------------- /docs/api-endpoints.md: -------------------------------------------------------------------------------- 1 | # API Endpoints 2 | 3 | ## HTML API 4 | 5 | ### Root 6 | 7 | - `GET /` - loads React web app 8 | 9 | ## JSON API 10 | 11 | ### Users 12 | 13 | - `POST /api/users` 14 | 15 | ### Session 16 | 17 | - `POST /api/session` 18 | - `DELETE /api/session` 19 | 20 | ### Notes 21 | 22 | - `GET /api/notes` 23 | - Fetches notes for current user 24 | - Narrows down results for specific tag names 25 | - Narrows down results for a specific notebook 26 | - Narrows down results by search query (bonus) 27 | - `POST /api/notes` 28 | - `GET /api/notes/:id` 29 | - `PATCH /api/notes/:id` 30 | - `DELETE /api/notes/:id` 31 | 32 | ### Notebooks 33 | 34 | - `GET /api/notebooks` 35 | - `POST /api/notebooks` 36 | - `GET /api/notebooks/:id` 37 | - (when notebook is fetched, notes for notebook should 38 | also be fetched through specific GET request to /api/notes) 39 | - `PATCH /api/notebooks/:id` 40 | - `DELETE /api/notebooks/:id` 41 | 42 | ### Tags 43 | 44 | - `GET /api/tags` (when user clicks on Tags sidebar button) 45 | - fetches tag index for current user 46 | - `DELETE /api/tags/:tag_id` 47 | - delete a tag entirely 48 | - `GET /api/notes/:note_id/tags` 49 | - fetches tags for a specific note 50 | - `POST /api/notes/:note_id/tags` 51 | - create a tag, add to existing note 52 | -------------------------------------------------------------------------------- /docs/component-hierarchy.md: -------------------------------------------------------------------------------- 1 | # Component Hierarchy 2 | 3 | **Root** 4 | - App 5 | 6 | **Auth Form Container** 7 | - Auth Form 8 | + Sign Up Splash 9 | + Sign In Plain 10 | + Sign Up Plain 11 | 12 | **Home Container** 13 | - Home 14 | + User status 15 | + Note Index Container 16 | + Note Top Bar 17 | + Sidebar 18 | 19 | **Note Index Container** 20 | - Note Index 21 | + Note Index Item 22 | - Show/Edit Note 23 | + Add to new/existing notebook 24 | + Rich text editor 25 | 26 | **New Note Form** 27 | - Add to new/existing notebook 28 | - Rich text editor 29 | 30 | **Confirm Delete Note/Notebook/Tag** 31 | 32 | **Error List Component** 33 | 34 | **Notebook Index Container** 35 | - Notebook index 36 | + Notebook index item 37 | - Create/update notebook bar 38 | - Notebook search (bonus) 39 | - Show Notebook (header) 40 | - Note Index Container 41 | 42 | **New Notebook Form** 43 | 44 | **New Tag Form** 45 | 46 | **Tag Index Container** 47 | - Tag Index 48 | - Tax Index Item 49 | - Tag Search (bonus) 50 | 51 | **Search Notes** (bonus) 52 | 53 | **Shortcut Index Container** (bonus) 54 | - Shortcut Index 55 | 56 | **Trash folder** (bonus) 57 | 58 | # Routes 59 | 60 | |Path | Component | 61 | |-------------------------------|--------------------------| 62 | | /hello | AuthFormContainer | 63 | | / | AuthFormContainer | 64 | | /signup | AuthFormContainer | 65 | | /signin | AuthFormContainer | 66 | | /home | HomeContainer | 67 | | /notes/:noteId | NoteIndexContainer | 68 | | /newnote | NewNoteForm | 69 | | /delete/:noteId | ConfirmDelete | 70 | | /delete/:notebookId | ConfirmDelete | 71 | | /delete/:tagId | ConfirmDelete | 72 | | /notebooks | NotebookIndexContainer | 73 | | /notebooks/:notebookId | NotebookIndexContainer | 74 | | /newnotebook | NewNotebookForm | 75 | | /newtag | NewTagForm | 76 | | /tags | TagIndexContainer | 77 | | /search/notes | SearchNotes | 78 | | /search/tags | SearchTags | 79 | | /search/notebooks | SearchNotebooks | 80 | | /shortcuts | Shortcuts | 81 | -------------------------------------------------------------------------------- /docs/sample-state.md: -------------------------------------------------------------------------------- 1 | ```js 2 | { 3 | session: { 4 | currentUser: { 5 | email, 6 | id 7 | }, 8 | errors: {} 9 | }, 10 | 11 | notes: { 12 | 1: { 13 | id: 1, 14 | title: "The Very First Title", 15 | body: "The Very First Body", 16 | tags: { 0: 1, 1: 2} 17 | } 18 | 19 | 2: { 20 | id: 2, 21 | title: "Second Note", 22 | body: "Another note", 23 | tags: { 0: 1, 1: 4} 24 | } 25 | } 26 | 27 | notebooks: { 28 | 1: { 29 | id: 1, 30 | title: "My First Notebook", 31 | notes: { 0: 1, 1: 2 }, 32 | } 33 | } 34 | 35 | tags: { 36 | 1: { 37 | id: 1, 38 | name: "Tag 1", 39 | notes: { 0: 1, 1: 2, 2: 3}, 40 | } 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/sample_state_2.md: -------------------------------------------------------------------------------- 1 | ```js 2 | { 3 | session: { 4 | currentUser: { 5 | email, 6 | id 7 | }, 8 | errors: {} 9 | }, 10 | 11 | user: { 12 | avatar: "www.photos.com/5" 13 | } 14 | 15 | notes: { 16 | 1: { 17 | id: 1, 18 | title: "The Very First Title", 19 | body: "The Very First Body" 20 | } 21 | 22 | 2: { 23 | id: 2, 24 | title: "Second Note", 25 | body: "Another note" 26 | } 27 | } 28 | 29 | notebooks: { 30 | 1: { 31 | id: 1, 32 | title: "My First Notebook" 33 | } 34 | } 35 | 36 | tags: { 37 | 1: { 38 | id: 1, 39 | name: "Tag 1" 40 | } 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/schema.md: -------------------------------------------------------------------------------- 1 | # Schema Information 2 | 3 | ## users 4 | column name | data type | details 5 | ----------------|-----------|----------------------- 6 | id | integer | not null, primary key 7 | password_digest | string | not null 8 | email | string | not null, indexed, unique 9 | session_token | string | not null, indexed, unique 10 | 11 | ## notes 12 | column name | data type | details 13 | ------------|-----------|----------------------- 14 | id | integer | not null, primary key 15 | title | string | not null 16 | body | text | not null 17 | author_id | integer | not null, foreign key, indexed 18 | notebook_id | integer | not null, foreign key, indexed 19 | 20 | ## notebooks 21 | column name | data type | details 22 | ------------|-----------|----------------------- 23 | id | integer | not null, primary key 24 | author_id | integer | not null, foreign key, indexed 25 | title | string | not null 26 | 27 | ## tags 28 | column name | data type | details 29 | ------------|-----------|----------------------- 30 | id | integer | not null, primary key 31 | author_id | integer | not null, foreign key 32 | name | string | not null 33 | 34 | ## taggings 35 | column name | data type | details 36 | ------------|-----------|----------------------- 37 | id | integer | not null, primary key 38 | note_id | integer | not null, foreign key, indexed 39 | tag_id | integer | not null, foreign key, indexed 40 | -------------------------------------------------------------------------------- /docs/wireframes/01-signup-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/01-signup-splash.png -------------------------------------------------------------------------------- /docs/wireframes/02-signup-plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/02-signup-plain.png -------------------------------------------------------------------------------- /docs/wireframes/03-signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/03-signin.png -------------------------------------------------------------------------------- /docs/wireframes/04-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/04-home.png -------------------------------------------------------------------------------- /docs/wireframes/05-newnote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/05-newnote.png -------------------------------------------------------------------------------- /docs/wireframes/06-notebooks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/06-notebooks.png -------------------------------------------------------------------------------- /docs/wireframes/07-shownotebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/07-shownotebook.png -------------------------------------------------------------------------------- /docs/wireframes/08-newnotebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/08-newnotebook.png -------------------------------------------------------------------------------- /docs/wireframes/09-newtag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/09-newtag.png -------------------------------------------------------------------------------- /docs/wireframes/10-searchnotes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/docs/wireframes/10-searchnotes.png -------------------------------------------------------------------------------- /docs/wireframes/xml/01-signup-splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 5Vlbt5o6EP41PvYsIID66K3W1Xbrkr17eWRLNrKKhBPjVs+vPwMkXJLY2oo92uMDJpMr3zczmQwdNNocptRP1x9JgOOOZQSHDhp3LMt0egj+MsmRS4yeU0hCGgVcVgm86B8sOnLpLgrwttGRERKzKG0KVyRJ8Io1ZD6lZN/s9kLi5qqpH2JF4K38WJV+jgK2LqQ9x6jk73AUrsXKpsFbnv3Vt5CSXcLX61joJf8VzRtfzMX7b9d+QPY1EZp00IgSworS5jDCcQaugK0Y9/ZEa7lvihN2zgCrGPDqxzssdpzvix0FFvt1xLCX+qusvgfCO2i4ZpsYaiYUt4ySb3hEYkLz/sjIf9Ci7oVv7xVThg81Ed/bFJMNZvQIXUSrwJUrksur+4oU0+px4brGSF8M9LkmhOXcFRpQ4IDowen9oeCYbYDT14DjxrDC8IXAK9VRcv/eEdHwZpsb+wA6wOsfqkYohdl/TEIiZoI9FJMVTbeFvtVEX3i4GvqWowHfaQF74SZvVTNtcQBwbMAcFXAcSwMO6reBjurUJh8Hsw83BZEjqY+N+qr+GBqI7DYQQgpCi4HnfZ4vxzcFUtf9L0FyLvZwZk/n4bzZ9AEanxbweDtfZs/lZHIPPk9WWse0FT5s+1pOzz3Jxzb1k4v5mGWPgQeP6dPEe6zxUUx/g3y4yJEiAKTah3ktPs4Ij+IowSUIIpI2mwilmEawOM7wycLnZ5LgRSUbpiRKWL5PZ9hxxiAhO5ZNPCqD/7bcTVdS754mouprA6oW4NQFVHcNp+ItNHBqtbMNOC01RAIL5DYdRK+VPReiZypLYIVGP4kLAIE1kacYPI3/nHcwtEj7O0YKb5QP8OMoTKAc45dsqgzZCO6gAy5mJC182ypKwsesMn5jt6TpdjNAc7pq9Ip08VkbzJgKMz99rnZ1fnw88UbL2eJxNn+4+mGqmuAVDAYJF/uj47WVC51lX85LX8fLp9l4MhcH7OM7iHSM4WD0frqcPz2Mz2Xqrs3NlOLY0sPVPaHupm61wasujr0rO7C7Z4b9ZQrwEri69waXckvqqm7janDZOq8hwYWTYJAlZaGWkDyCOQssfIjYl8y4/3J47SsfA1jRY9FkOaJeNYqEsp1P71NWq5/Ee0t2dIWbOgBjQyzA4maEg0bqWGWlnuP4TuhNceyz6LWZcNYxwVdYZO6tIt12pOCqK7mK4n34qIpPZSLF2OQUTAGCMhEw6h9r3bj7PblhJK3juo3MNBSKGSutKzE9TxEvv67DS2iOLwyqC8GhsSKbFLQ3P6P+/COrDPUkuhp3oWsdWbbuqt+OUzkJzY3Ydb8nx4C/aNemITFYfh/4gWH/iu3pzsw2PgaAOaz/d6ZnGpJvL22xbnvutWxPl9ZpOc32WzJrCuAaWr7zdUeyQ537aynzDNXqg29hfdVndTT5Fw== -------------------------------------------------------------------------------- /docs/wireframes/xml/02-signup-plain.xml: -------------------------------------------------------------------------------- 1 | 2 | 5Vlbc9o4FP41PDZjSb7gR0JotjPtJlOy0+6jgxXbU2OxQgTYX79HlmRjS+nSYjo04cFYR0dH8neukkZkutzd8mSVf2IpLUfYS3cjcjPCGHs+gT9J2SsKQuNIUTJepJrWEubFv1QTPU3dFClddxgFY6UoVl3iglUVXYgOLeGcbbtsT6zszrpKMmoR5ouktKlfilTkijoOvJb+By2y3MyMPN3zmCy+ZZxtKj3fCJOn+qe6l4mRpfnXeZKy7QGJzEZkyhkT6m25m9JSgmtgU+Pev9DbrJvTShwzAKsBz0m5oWbF9brE3mCxzQtB56tkIdtbUPiIXOdiWUILwetacPaNTlnJeM1PvPoHPVo05YLuXlweaj4arImyJRV8Dyx6QBCqEdqOxhq1basT1BDzA4XERh+JNoSsEd2CAS8aDzc25HVigwaAJrhwaIx7aWwIscEhgQMbMkangxNa4Mw+TT58vCyE4q71+H5kIYQ9B0L+ANYTWQDdT+bzL3efby4ao4DEvw6j2OFhYQkTXD8xWP4hSOE/G2Y63q3rXDoBBjRe7dpOeMvk//TzbPIwg+7JdHr3158PRiqsRwlWbJeliAh3FBESZCvCx+dRBDpnrFM9psRAZ7Lb6Ei7xQMEP2RHv98MLn98ZCgcBC47Flpw0SqdyJoWWhWr6LFg0V0hvgLNuwp06289BoDhe9WFA9NuO0097tfiEy4O2mpxNLXK5/9F+wBNV+Y1NE7LRBTPXfEuhPUM96yow6FWZhgHV0GEfKjV62fc9YQ4vjroDMZd8Wu24QuqJR4Wzf1J/O9OgnCnN+pOAoBmVFiTgIaT/QHbSjKsX/5Q0itxohD17E5JbK2w0cVxhjk+OQHBRzgSEAVT5vXWbbkCa65TzlEZCDxcdG2fU5gseawZpMVrzIA7uB4FN0BJNoKpBdUDkrLIKngv6ZMUJcNGAZu+iSYLJiPRGgJTUWUPsnHzzh8myGDiVlenXHdV63iAFGZknKJLz6VLgDd/c6qMe6r0kUOV4blUiSxVTkpOk1QuME8gboL8Sj4WC7aRH/T+daEfmsBkHAnb2Rq5Eswg6LuOS4ZwpLnEDXsfqjfhQWFAujoMbA86Wyw84lTnJwuuS6+NmuNLE7jinohj65943BPk9wS9UOP8RBmC/ZM9DkUuj/t4d3t39r3vWfYrze7XHK6HjpM/EtlmFA/hPq7d8I/pgzgj4BSSmJDZa6IT11sIhP2tJ3aosol6g0dCe+up01BRKdHYu93QtRjOH84AGXFBdq5DOeepXA+csqgzhu36BwitKC9gcirxkTdGj5Bl7luaw2TZRkjB0+a+ayA4Q69fzNrllDk+GvoSgdjbErkzVH6fFs+tzyvSI+9TYIYO32uKDWGMepqxiyTisHMyhGbsXcZvbueROcb9jp2bu58T7Rya7f2tKnnaW3Iy+w8= -------------------------------------------------------------------------------- /docs/wireframes/xml/03-signin.xml: -------------------------------------------------------------------------------- 1 | 2 | 7Vtbc9o4FP41TJ+aQZZs48cE0mxn2klm2Zl2Hx1QjKfGokYE2F+/siVh60LGCTKFEB4S6+jq75wjnfMJenA439wV8WL2nUxx1vP6000PjnqeBxDss3+lZMslAwi4ICnSqWhUC8bpf1gIRb9klU7xUmlICcloulCFE5LneEIVWVwUZK02eyKZOusiTrAhGE/izJT+SKd0Jt7C79fyv3CazOTMoC9qHuPJr6Qgq1zM1/PgU/Xh1fNYjiXaL2fxlKwbInjbg8OCEMqf5pshzkpsJWy835c9tbt1FzinbTp4vMNznK2wXHG1LrqVWKxnKcXjRTwpy2um7x68mdF5xkqAPS5pQX7hIclIUbWH/erDasTQuKB4s3d5YPfSzJgwmWNabFkT0cEPeA9pRgK1da0TsBPOGgqJpD5iYQjJbugaDPYg8LBjA98nNsABNOjEoZHuJbCBngkO9C3YMMM5HBzfAOf2+/XXb6eFUKRazw6NBkJe34IQcgBQYAD0cD0e/7j/e3TSGCE4OB5GocXDgoxNcPNE2PKbIAW/V0RWfF5WZ+k1awAGi01dyZ6S8v84TXJW+TWXw7GF8BF5/WlpIPQUDfjQYqXI60YDgw73OF4jQwvQkU9HLX16FzkdgFZ07mihsKV3u0BLnsEvwYXz6XUZyrJSTnLcFiy8SelPJutf+aL0r+jDgCm2vMrzZbmulGE4qoaPC9oo88XhqRY1L8mqmGDFAFjHBFPFg1ropIG57ViWsgJnMU2f1UXY9CBmeCBptVcKlSNfDQv8UNsm+PuIXs2oWRtIjy+QHjZwFIyBmD7jbaPZomyw3L9gqM0TBEo0zx74iLXN7TBtZ4bg4FOGvYTllMHMcIsqP5svmO1Wx0ur04b5M1UtvcBssvixalDat8CMtfZvev6ISeIVJXxBVYc4K484OMrwUzlUuUmkLLO7FmJKyn1nybahNE/+KQujz8jNluJBu7qUmNwWknsOjitgS+Veqcu+TZcM3tnFqTLSVAmBRZVBV6o0M88RyT+VOpzFbPdjo5cxXDyZkFX5Ol/eF/aBp57Mu3ITe9QV9rbU1oUbDQscU9xQ2yW4USC7SFVa0kxgTcRdqNJMxF0FWXvioZOJdAZapAP1AKVtpBMNtIH0zGpPpPOWYMSkBV6d8oY2z/t2f3ffebbbSY4SSKJtq7lK031gaJqRCxoLHE5BQOtO+GoK4ry3QKRugZ4tKARdbYFdkhgOwPmj/D7okrP4g+C4IPil8Z0sNjrDb2EGrQw/HDigbzwzbz59ih+h0ICoM/pamsu5cfw+jI4Iku2G8eJJ/kB+V+AYJH+nN5lH4a39sKXFuuCtLXebZwYXGrTcBZ3AZUtp3GSgx6T5TyatDSL/yg8BYvlt9TdSPSGKrhqV/kAdvm3KG6AXJwGeUhuqk3RE/IcB0OzuMOLfwfXyB/Hf20v8S3Udhfi3XlR/EP9vVKVO/CNgUWVXxL/lFv2yiH+V7go986zujPiHtoz3g/h/qyol8yRV6VvcqCviH5rpuauw69QjJJ34R5E2xFuJf4S0gdwR/9DkCi6e+JcmLIl/G2ncFfEPD6clPoj/xl2nJP5Diw67Iv6hyWsI9NOcD+3171Z4Sd05ggPIjG8wWsy+MzYOtuA2srQ6KkyfbyC0wEXKJsclPuXPQR7Z8fJQyywmS1a0HHi4+zGLIziDvh7LmvGUZI9cXyBAk/ooE0Pu99P0ufZ5LnosdAmbQWn3rvaGCGiaMcMjaLFz6EIzttz/rO08lN/0eMHO5bXPgXbOivWPs3isU/8CDt7+Dw== -------------------------------------------------------------------------------- /docs/wireframes/xml/04-home.xml: -------------------------------------------------------------------------------- 1 | 2 | 7Vtbc+smEP41nmkfktHd9mPiJG0fTnumzkzbRyxhiYksXIRip7++IMC6YYfYss5R2jxE0oJg+XZZ7QVP3MVm/xMB2+QLjmA6caxoP3EfJo5j+zOXXTjlTVKsmS8oMUGRpFWEJfoHqo6SWqAI5o2OFOOUom2TGOIsgyFt0AAheNfstsZpc9YtiGGHsAxB2qX+gSKaCOrMtyr6zxDFiZrZtmTLCoQvMcFFJuebOO66/BPNG6DGkv3zBER4VyO5jxN3QTCm4m6zX8CUg6tgE+89HWk98E1gRk1e8OfijVeQFlCxXDJG3xQYuwRRuNyCkD/vmMQn7n1CNyl7stltTgl+gQucYlL2d63y79CiAOR9QYrijN2ncM3Yu5dzQ0Lh/ugC7AMsTN8g3kBK3lgX+cJcAqk0TbK/q6Rm+0psSV1kljOT+iJ1JT6MXeHFbiRkevimGvSClC9tjRn/dRiDvwusGm7yUuPvWAfX3u6rRnYX8+syYUrhPD1GiA/yK6aQXRbsTYAySNQcrIeYRrx0TcH1IKmZ0xTVYQ/UZDUNNKKaqc11iaTcQfW8B7hcq63ZXbgOZqcO17wPuIKxweUZwOU514JrdrEdYGvV2AG+81cYv/ARYMq+dAxM5+l3FCb8iwj3fGjIjAQnWz/kxSrEmy3OYDknXosvpDIa1gqmePfj57Ae9tTQevg9iFe5JZfI19fK97fnx+U4BXL2hrP72HC2PXYDpfv8XREvZ+x4ucPiNTp/oY2XNyxe3tjx8ofFyx87XsGweI3eIZ0Oi5cuMh0VXrNh8dLlQYRbFqFXrYPH3e8bmdDgHl6Z0+h4eGqUfAuy8+MAdgUbLp9slW/LZ8uA9MBChjJzkIkEwqqgFDMuGBrWBpAXgRlnChACI9GAaVIGC+siCynCGVshfau5qGIhzcUxconS9R3X/hNIncDCNC3RR2Dh6czaBwOLmTaBhCK4AuRoaFFT7Sbpsmlr4WdbO08y8o4ClZFuQ2cIZKyAVdmBa8oWo4yWgvDvJ/4D15SCYsGuTnG4yqAQpHeSTPFWbNIQZfEzf3i48fpRMLulYIfETj2Ro1MwrwcF8w3iJBjFcCkfcxhv2LoeK9I9zKI7nlJnrWEK8hyFptuXDUTe/uRQWreWN1WUv7jMbp25qwhfIUFsZZBIYQoOOVunIXcsZrhiKEmuXgo1lHV5YEUjMAUUvTZn1CEvZ/jKFa4m5KAl5GlLeDkuSAjlW5X8OgM5s9ZAs9ZAYsmdgZiIwFutm9wQxgx7nnWaL/uy/modR/vP/VP92Y1YYaX1BxmbbQRdQPfBVP3RFB1r/CWLmIU4I0k/atvmd3L6HdN2MHf1Xef0YdoMQoK6aYPpCu8aho0TWIPC6yJbZ2qzvpWB8juFsjMNVOA1B2obuiP26ZwtaxDD/C/gg0G3/J4kXFWzry/iywsnJ60yt3aOdX/CEf5M1ngevGuN7fmVrLGaerQHCdr11rnaTu/VW9u74yz0DNKnV3PTTS1b090OLD2cA1m71ufsoPofNnYtf3tumxm74/72BeYw0GWF+6gjLwpCRFG4yP8jrmk77J7rzk+417KGJr5pmqJtzrfu920X2y6+28VRt9E9vwcYTTzAccKoywJdDUadl/VJYNQcA7kajAZnJUcKo8rmDAHj1MBTHCmMvuUPB6NJZnekMA64qafOp4UxGHJTG3ypy2PxMJKuY4g3PESp4abQcZrRzCrF4ctzgrIm1txzfQIblPLVLsrBHGsJMl7Q/LKUHVS8VNWov98EjtNKrAXzM0Oa9pn4oF1I6i9/M708f2NrC4miVM1lSTHnPIrY/wzuPlhXvKDAqeeLV9JzUSjP5Nlc8XiSDeM5A7Pi/7lrVr+zUTN43ckoiOX68gQTGhZUru+Scm4fjOUQkPLYcyWCb85TjJWCSqYsJMpAHD9Iw9uzePrUxXCrWeabajwmbY6yXQY1+Eixx+qHVMK4VT9Xcx//BQ== -------------------------------------------------------------------------------- /docs/wireframes/xml/05-newnote.xml: -------------------------------------------------------------------------------- 1 | 2 | zVZdb5swFP01PFYCPFj2uKbJNmltpCZSnx24ASsGM+MUsl+/iz8gNETrulQbL7bPvb7XPuck4JF50X6RtMrvRQrcC/209cidF4ZBNCM4dMjRIv4sMkgmWWqxAVizn+ASLXpgKdSjRCUEV6wag4koS0jUCKNSimacthN83LWiGZwB64Tyc/SJpSo36CzyB/wrsCx3nQPfRrY02WdSHErbzwvJTj8mXFBXy+bXOU1FcwKRhUfmUghlZkU7B96R62gz+5YXov25JZTqNRtCs+GZ8gO4E+tzqaPjosmZgnVFk27doOAeuc1VwXEV4LRWUuxhLriQOp/4+sGILQ1SQXvxeEF/aXQTiAKUPGKK2+B4tUaK7bIZRAnCmQXzE0U+uY3UOiHraw9s4MQSMk0OmSAn5tjhdifw/KcsxT8OwgVuau3nz5iAd22HIM6ybnwQCrZC7LsKwNG/SF24fGRJ3vkc2q40pEzDph+GTUtT4HoamYjzeHAd1UI/GqkWBBOy+XF0Llt0BdU+XFQtZc+TonWU31DOstKoluA9QZ7r5urUFS1fYq91BIknHbF4wtjDarPAYbl6vL8ofA9PHQJhfcf3d4ll685S9T62mfq1T9rmY3AF30R/7RsOO/U717z1PwNHWnSCldu60mv/zdDm2+Z7Z7TVsjfdf2YrzeQ/NlX8557C5fDq1rGTDySy+AU= -------------------------------------------------------------------------------- /docs/wireframes/xml/06-notebooks.xml: -------------------------------------------------------------------------------- 1 | 3ZlLj5swEMc/DVJ7qAR2YLPHhm4fh/aSQ89OMA/FMNSYkvTTdwAbkiarIjVr1csF++8Ze/iNeUzi0bg8fpKszr9CwoVH/OTo0Q8eIcGj7+OpV05aoT4ZlUwWidZmYVv84lrUjllbJLy5MFQAQhX1pbiHquJ7daExKaG7NEtBXK5as4xfCds9E9fq9yJR+aiuQ3/WP/Miy83KgbniHdsfMgltpdfzCE2HYxwumZlL2zc5S6A7k+iTR2MJoMZWeYy56OEabKPfx2dGp7glr9QiB+3xk4mWm5CHwNTJwOjyQvFtzfZ9v8OMe3STq1JgL8BmoyQceAwC5GBP/eGYRgzA3paJIquwLXiK4W302lwqfnz2AoIJC+43DiVX8oQm2oGYnJitZjLRzXkLKNVifpa0aZcyvVmyafIZGDY0s9v8iFV89+C1Cv/Ka0VeChe9gSsS/VZIAeM/5xb9aMEMvGuGJ8R7NCBhfZwHsZX152+g+A7g0KDFlypBOsSP0ZEVFZfYftOI4YFCfGj7ZVIJZb8aijsm35ogMPwxjnHW15rK4B6pXLm+84lVXKHruKhVXJHruFZWcT24jiu0imvtOq7IKq5H13E9WMVlihh3ea2t8iLO8QoWfHlN2v1rjgU1G3pgrYydzX9dvQXRH99kwTXI8AZHw/+fMC6o3VzFuLaI8VZN9zow0tAixgX1lKMYpzeHDYwL6ixXMa5DexgX1F+OYgxt3tQL6jJHMUYvd1Njd/4BfBg7+5uBPv0G -------------------------------------------------------------------------------- /docs/wireframes/xml/07-shownotebook.xml: -------------------------------------------------------------------------------- 1 | 2 | 3VpZc+I4EP411DylyifHYyBkZmtqw9aQqnkWtrBVEZZXFgH2169OwLYycYJxxfBiq3V06+tWq7vNwJ9t9t8pyNO/SQzxwHPi/cB/GHieG459/hCUg6Y441BREopiTTsRlug/aAZq6hbFsCgNZIRghvIyMSJZBiNWogFKya48bE1wmWsOElgjLCOA69TfKGapoo5D50T/AVGSGs6uo3tWIHpJKNlmmt/A89fyp7o3wKylxxcpiMnujOTPB/6MEsLU22Y/g1iAa2BT8x7f6D3KTWHGmkzw1IRXgLfQSCzlYgeDxS5FDC5zEIn2jit84E9TtsG85fLXglHyAmcEEyrH+478HXsMfmIswCjJ+DuGay7dVPOGlMH9m/K7R1S4uUGygYwe+BA9YaJxNIamxd+dlOaGRmvpucYcb6zNRZtKclz7BBd/0YjZ0fMt6A2x2NqacPnPYRz+uyWm466QBn/PB/huvj918rdEPJcptwnvcR4jscgTYZA/ZnwmQBmkhgcfodioSddUXAuaGntlVR2PwJmuRkOLqsbmbF2iqaBTO28BLt+pWnYdrqPXOYdr0gZcYd/gChrAFXjXgmt4sR/ge7X4AXHyV4S8iBUg5hcdB9N7/IWiVFyIcC+WhtxJkBvxCe6ooU8IW1Da6HKlhValLZ7n08XiJx+w/LH4LTz34un5/q+n+a9+aunTZ8sNWlDTpO+uyBs2dkWXo2UC6P7CFQRNrasNz+26fccr9Ls0r27zhSvANWzsvFoxL1uG0Cu8Rt3i1bs4vYrXuFu8bIG6ijFi9GqNYUTYeKcTcRHEyFy8FsSYVYocZJ+PX/kTbIR+slWRy7bTgPTAQ12Z8WYq8V1tGSNcCo6GswH0RWEmhAKUwlh1EJaKzNhZb7OIIZLxHbLDWbylNlLeHCdLlK4fhbVf+KiFzk3T6TZCZ3Mqe1s2qmbXk7COnjW7HrWBXoPsms9AecEb06+NY7WoYwlGbOW3IGwBRlvWfRswHs2zCxhtefCNwGgpJ1wNxvHNwmjLyK4GY4N0v6cwhk7YGYzGDd8ijB0ear9BdaCnMA47PNR+k5s6i+/Fh1veijAoChQ1RU8tBePaF90KNJ7D84UEmkDOjtY7aBgahRgw9FrmaINIc/iHIFlS3psIsOJhvUpYWZAtjaCedQL63YX8apFHbbm2kNTYcdvNlHh5vdyd2JLEQn7slMme+tZRTRDfqJmfpbll0mXiRGSTkwxKTh8RpGmmjIVGLGxTkCWw+APTJhnsm5hcKB1PruVJALFMskkWwS8iGSiZjoNkYUB+KxOVgS8JJsg+KtmFDGNU5BgcCqk5LNyc1OeHRPjDOTP/3jFsg7oERkPfCq2u4p3TZNfFOwUb+UW0dGtQyKUDKzlA3BW5cITSNYbTQfgglLFlRO/AcvGKKxdFAN9rMiO5UkeEsuRZNB7ugtoNPmnnynbd6i3h165sz1ak+ExpkTdP/zpS18Ppv13+/H8= -------------------------------------------------------------------------------- /docs/wireframes/xml/08-newnotebook.xml: -------------------------------------------------------------------------------- 1 | 2 | 5VZNj5swEP01SO1hJcAhpceG3W0v3UsqVT0acMCKYagxC+mv79jYIRRW3VYrbaTlwvjN9xuTiUeSavgsaVN+hZwJL/TzwSO3XhgGUUzwpZGTRfw4GpFC8txiE7Dnv5gztGjHc9bODBWAULyZgxnUNcvUDKNSQj83O4CYZ21owRbAPqNiiX7nuSpHNI78Cf/CeFG6zIFvNSnNjoWErrb5vJAczDOqK+piWfu2pDn0FxC580giAdQoVUPChCbX0Tb63T+hPdctWa2e42AH9UhFx1zFpi51clz0JVds39BMn3scuEd2paoEngIUWyXhyBIQII098c2DGhuaScWGJ8sLzk3jbWJQMSVPaOIcHK/2Im3tsZ+GEoSxBcuLiXx0jtTehOIce2IDBUvIOjnRCjlbgRl2OX+ckbT92emJ7bBTdUMFL2qPfNL3E/tkctKjVNi3idM2tP4TOwBysxZcK25a863o2GTbDMvID6xH3QMolgIcUbwHWbnQ2PAYfZ4R4bVCEDZ9ztHrbd1FSeVKJ1fW9//2+O4bV+gX+j+gkxeDfv+qPa0yfrUcJrTOzMJCgFb656xO28YYPAfad2nF1Zvi++/5XmpnjBq3c7WtLfPW1vgia2XzYb5Wgni5V6JNsFwrm+Df1woep31udBf/msjdbw== -------------------------------------------------------------------------------- /docs/wireframes/xml/09-newtag.xml: -------------------------------------------------------------------------------- 1 | 2 | 5VbBjpswEP0apPawEuCQ0mPDbttL95JKVY8OTMBag6kxC+nXd2zsEC+suq1WaqRywX4zfjPzxmQSkKweP0naVl9EATyIw2IMyG0Qx1GSEnxp5GSRME0mpJSssNgM7NlPcI4W7VkBneeohOCKtT6Yi6aBXHkYlVIMvttRcD9qS0tYAPuc8iX6jRWqmtA0CWf8M7CycpGj0FoONH8opegbGy+IydE8k7mmjsv6dxUtxHABkbuAZFIINa3qMQOuxXWyTec+PmM95y2hUS85YBv1SHkPLmOTlzo5LYaKKdi3NNf7ARsekF2lao67CJedkuIBMsGFNP4kNA9aLDVIBeOz6UXnovE2gahByRO6uANOV3uRtnY7zE2J4tSC1UVH3ruD1N6E8sw9q4ELK8i6OMmKOFuOEXYFe/RE2v7odcd2WKm6oZyVTUA+6PuJdYKc7bgq7dvwdC1tnmJHgdqskWvDTWe+Fc1Ntu24ZL6HAW1faenosMiJ0Y+C8FpwhE1tPnq95TqWg1yp5Mrq/tsa39zTGtD6XfRy6u3bf1rNqtZXq15Gm9yMJwRorX+8mkPXGoeXQPv+UDP1X+n9+3ivNSEmi5uw2temeWtzfJUhsnnnD5EoXU6RZBMth8gm+vMhgtt5ehvbxX8kcvcL -------------------------------------------------------------------------------- /docs/wireframes/xml/10-searchnotes.xml: -------------------------------------------------------------------------------- 1 | 3ZhNc5swEIZ/DcfMIMkQcqzdpL20Fx96lkF8jAWiQg64v74rIYGpySSdOjSDL0jvrqTVsxLD2iO7svsiaZ1/EwnjHvaTziOfPYwxuQ/goZVzr6AQR72SySKx2ijsi1/Mir5VT0XCmomjEoKrop6KsagqFquJRqUU7dQtFXy6ak0zdiXsY8qv1R9FovJejQJ/1L+yIsvdysi3lgONj5kUp8qu52GSml9vLqmby/o3OU1EeyGRR4/spBCqb5XdjnEN12Hrxz29YB3ilqxSbxmw6Qc8U35iLmITlzo7Fm1eKLavaaz7LSTcI9tclRx6CJqNkuLIdoILafyJb36DxfHTvpQXWQVtzlKIbmvXZlKx7sX40UAFjhsTJVPyDC7urG3sUXMnzSWiHdOGsG+d8oucPThPas9KNkw+8oKGRTaPL1gU3w14obfwctrNcYWv44IBcMmhs/3Q5w6FU47Dm+uCYzCD0eH/F4r366UYLUcxWi1FEixH8WG1FDd4OYru5bFGjFGwHEa0WozBgnca4dViDJe81HPf1iHXO0uK5wnO8OdJf/BvYaPqzlL4BB4GxGCFVmafZpZUAIS5abThrjFFlZ6FBHXXD7ueiJY6X9WhqU3fn5H2jMo4h8Z3oaAms4vD7vv1pzGBbDbn1MVqCR2MrSNJ8L9rC4Rv8LGM5oqLq+w0jOsCGILKqX5UkKSDEEdTMRu7zV4qRfme+fgzAe9f6+GZyzyfj+Dv0wHdsQo3tov/Osjjbw== -------------------------------------------------------------------------------- /frontend/actions/auth_actions.js: -------------------------------------------------------------------------------- 1 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER'; 2 | export const RECEIVE_ERRORS = 'RECEIVE_ERRORS'; 3 | 4 | import * as APIUtil from '../util/session_api_util'; 5 | 6 | export const signin = (user) => (dispatch) => { 7 | return APIUtil.signin(user) 8 | .then((user) => dispatch(receiveCurrentUser(user)), 9 | (result) => dispatch(receiveErrors(result.responseJSON))); 10 | }; 11 | 12 | export const logout = () => (dispatch) => { 13 | return APIUtil.logout() 14 | .then(() => dispatch(receiveCurrentUser(null)), 15 | (result) => dispatch(receiveErrors(result.responseJSON))); 16 | }; 17 | 18 | export const signup = (user) => (dispatch) => { 19 | return APIUtil.signup(user) 20 | .then((user) => dispatch(receiveCurrentUser(user)), 21 | (result) => dispatch(receiveErrors(result.responseJSON))); 22 | }; 23 | 24 | export const receiveCurrentUser = (user) => { 25 | return { 26 | type: RECEIVE_CURRENT_USER, 27 | user 28 | }; 29 | }; 30 | 31 | export const receiveErrors = (errors) => { 32 | return { 33 | type: RECEIVE_ERRORS, 34 | errors 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /frontend/actions/notebooks_actions.js: -------------------------------------------------------------------------------- 1 | import * as NotebooksAPIUtil from '../util/notebooks_api_util' 2 | 3 | export const RECEIVE_NOTEBOOKS = 'RECEIVE_NOTEBOOKS'; 4 | export const RECEIVE_NOTEBOOK = 'RECEIVE_NOTEBOOK'; 5 | export const REMOVE_NOTEBOOK = 'REMOVE_NOTEBOOK'; 6 | export const RESET_CURRENT_NOTEBOOK = 'RESET_CURRENT_NOTEBOOK'; 7 | 8 | 9 | export const receiveNotebooks = (notebooks) => ( 10 | { 11 | type: RECEIVE_NOTEBOOKS, 12 | notebooks 13 | } 14 | ); 15 | 16 | export const receiveNotebook = (notebook) => ( 17 | { 18 | type: RECEIVE_NOTEBOOK, 19 | notebook 20 | } 21 | ); 22 | 23 | export const removeNotebook = (notebook) => ( 24 | { 25 | type: REMOVE_NOTEBOOK, 26 | notebook 27 | } 28 | ); 29 | 30 | export const resetCurrentNotebook = () => ({type: RESET_CURRENT_NOTEBOOK}); 31 | 32 | export const fetchNotebooks = () => dispatch => { 33 | return NotebooksAPIUtil.fetchNotebooks() 34 | .then(notebooks => dispatch(receiveNotebooks(notebooks))) 35 | }; 36 | 37 | export const fetchNotebook = (id) => dispatch => { 38 | return NotebooksAPIUtil.fetchNotebook(id) 39 | .then(notebook => dispatch(receiveNotebook(notebook))) 40 | }; 41 | 42 | 43 | export const createNotebook = (notebook) => dispatch => { 44 | return NotebooksAPIUtil.createNotebook(notebook) 45 | .then(notebook => { 46 | dispatch(receiveNotebook(notebook)); 47 | }); 48 | }; 49 | 50 | export const updateNotebook = (notebook) => dispatch => { 51 | return NotebooksAPIUtil.updateNotebook(notebook) 52 | .then(notebook => { 53 | dispatch(receiveNotebook(notebook)); 54 | }); 55 | }; 56 | 57 | export const deleteNotebook = (id) => dispatch => { 58 | return NotebooksAPIUtil.deleteNotebook(id) 59 | .then( 60 | notebook => dispatch(removeNotebook(notebook))) 61 | // errors => { 62 | // debugger 63 | // console.log(errors) 64 | // }) 65 | }; 66 | -------------------------------------------------------------------------------- /frontend/actions/notes_actions.js: -------------------------------------------------------------------------------- 1 | import * as NotesAPIUtil from '../util/notes_api_util' 2 | 3 | export const RECEIVE_NOTES = 'RECEIVE_NOTES'; 4 | export const RECEIVE_NOTE = 'RECEIVE_NOTE'; 5 | export const REMOVE_NOTE = 'REMOVE_NOTE'; 6 | export const RESET_CURRENT_NOTE = 'RESET_CURRENT_NOTE'; 7 | export const START_LOADING_ALL_NOTES = 'START_LOADING_ALL_NOTES'; 8 | 9 | // you're not using this right now/may not need to use it 10 | export const START_LOADING_NOTE = 'START_LOADING_NOTE'; 11 | 12 | export const receiveNotes = (notes) => ( 13 | { 14 | type: RECEIVE_NOTES, 15 | notes 16 | } 17 | ); 18 | 19 | export const receiveNote = (note) => ( 20 | { 21 | type: RECEIVE_NOTE, 22 | note 23 | } 24 | ); 25 | 26 | export const removeNote = (note) => ( 27 | { 28 | type: REMOVE_NOTE, 29 | note 30 | } 31 | ); 32 | 33 | export const resetCurrentNote = () => ({type: RESET_CURRENT_NOTE}); 34 | 35 | export const fetchNotes = () => dispatch => { 36 | startLoadingAllNotes() 37 | return NotesAPIUtil.fetchNotes() 38 | .then(notes => { 39 | dispatch(receiveNotes(notes)); 40 | }); 41 | }; 42 | 43 | export const fetchNote = (id) => dispatch => { 44 | return NotesAPIUtil.fetchNote(id) 45 | .then(note => { 46 | dispatch(receiveNote(note)); 47 | }); 48 | }; 49 | 50 | export const createNote = (note) => dispatch => { 51 | return NotesAPIUtil.createNote(note) 52 | .then(note => { 53 | dispatch(receiveNote(note)); 54 | }); 55 | }; 56 | 57 | export const updateNote = (note) => dispatch => { 58 | return NotesAPIUtil.updateNote(note) 59 | .then(note => { 60 | dispatch(receiveNote(note)); 61 | }); 62 | }; 63 | 64 | export const deleteNote = (id) => dispatch => { 65 | return NotesAPIUtil.deleteNote(id) 66 | .then(note => { 67 | dispatch(removeNote(note)); 68 | }); 69 | }; 70 | 71 | export const startLoadingAllNotes = () => ( 72 | {type: START_LOADING_ALL_NOTES} 73 | ); 74 | 75 | export const startLoadingNote = () => ( 76 | {type: START_LOADING_NOTE} 77 | ); 78 | -------------------------------------------------------------------------------- /frontend/actions/tags_actions.js: -------------------------------------------------------------------------------- 1 | import * as TagsAPIUtil from '../util/tags_api_util' 2 | 3 | export const RECEIVE_ALL_TAGS = 'RECEIVE_ALL_TAGS'; 4 | export const RECEIVE_INDIVIDUAL_NOTE_TAGS = 'RECEIVE_INDIVIDUAL_NOTE_TAGS'; 5 | export const RECEIVE_TAG = 'RECEIVE_TAG'; 6 | export const REMOVE_TAG = 'REMOVE_TAG'; 7 | 8 | export const receiveAllTags = (tags) => ( 9 | { 10 | type: RECEIVE_ALL_TAGS, 11 | tags 12 | } 13 | ); 14 | 15 | export const receiveIndividualNoteTags = (tags, tagsToUpdate) => ( 16 | { 17 | type: RECEIVE_INDIVIDUAL_NOTE_TAGS, 18 | tags, 19 | tagsToUpdate 20 | } 21 | ); 22 | 23 | export const receiveTag = (tag) => ( 24 | { 25 | type: RECEIVE_TAG, 26 | tag 27 | } 28 | ); 29 | 30 | export const removeTag = (tag) => ( 31 | { 32 | type: REMOVE_TAG, 33 | tag 34 | } 35 | ); 36 | 37 | export const fetchAllTags = () => dispatch => { 38 | return TagsAPIUtil.fetchAllTags() 39 | .then(tags => { 40 | dispatch(receiveAllTags(tags)); 41 | }); 42 | }; 43 | 44 | export const createTags = (names, note_id) => dispatch => { 45 | return TagsAPIUtil.createTags(names, note_id) 46 | .then(newTagsStateANDTagsToUpdate => { 47 | let tags; 48 | if (typeof newTagsStateANDTagsToUpdate.tags === 'undefined') { 49 | tags = {}; 50 | } else { 51 | tags = newTagsStateANDTagsToUpdate.tags; 52 | } 53 | 54 | let tagsToUpdate; 55 | if (typeof newTagsStateANDTagsToUpdate.destroyedTagsToUpdate === 'undefined') { 56 | tagsToUpdate = {}; 57 | } else { 58 | tagsToUpdate = newTagsStateANDTagsToUpdate.destroyedTagsToUpdate; 59 | } 60 | 61 | dispatch(receiveIndividualNoteTags(tags, tagsToUpdate)); 62 | }); 63 | }; 64 | 65 | export const updateTag = (tagId, names) => dispatch => { 66 | return TagsAPIUtil.updateTag(tagId, names) 67 | .then(tag => { 68 | dispatch(receiveTag(tag)); 69 | }); 70 | }; 71 | // in react, you're going to want to GET INDEX to refresh after you call this 72 | 73 | export const deleteTag = (tagId) => dispatch => { 74 | return TagsAPIUtil.deleteTag(tagId) 75 | .then(tag => { 76 | dispatch(removeTag(tag)); 77 | }); 78 | }; 79 | -------------------------------------------------------------------------------- /frontend/components/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const App = ({ children }) => ( 4 |
5 | { children } 6 |
7 | ); 8 | 9 | export default App; 10 | -------------------------------------------------------------------------------- /frontend/components/auth/auth_form_conditional.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthForm from './auth_form' 3 | 4 | class AuthFormConditional extends React.Component { 5 | render() { 6 | let cssTag = (this.props.formType === 'splashSignUp') ? 'splashForm' : 'plainForm'; 7 | 8 | return (
9 | 10 |
) 11 | } 12 | } 13 | 14 | export default AuthFormConditional; 15 | -------------------------------------------------------------------------------- /frontend/components/auth/auth_form_container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { signin, signup, receiveErrors } from '../../actions/auth_actions'; 3 | import AuthFormConditional from './auth_form_conditional'; 4 | import { withRouter } from 'react-router'; 5 | 6 | const mapStateToProps = (state, ownProps) => { 7 | 8 | let formType; 9 | switch (ownProps.location.pathname) { 10 | case '/': 11 | formType = 'splashSignUp' 12 | break; 13 | case '/signup': 14 | formType ='plainSignUp' 15 | break; 16 | case '/signin': 17 | formType = 'signin' 18 | break; 19 | default: 20 | console.log("Location pathname isn't being caught"); 21 | } 22 | 23 | return { 24 | // loggedIn: !!state.session.currentUser, 25 | //not sure if loggedIn is needed.. 26 | errors: state.session.errors, 27 | formType 28 | }; 29 | }; 30 | 31 | const mapDispatchToProps = (dispatch, ownProps) => { 32 | let processForm = ownProps.location.pathname === '/signin' ? signin : signup; 33 | 34 | return { 35 | signInAsGuest: () => dispatch(signin({email: 'guest@example.com', password: 'password'})), 36 | processForm: (user) => dispatch(processForm(user)), 37 | receiveErrors: (errors) => dispatch(receiveErrors(errors)) 38 | }; 39 | }; 40 | 41 | export default withRouter( 42 | connect( 43 | mapStateToProps, mapDispatchToProps 44 | )(AuthFormConditional) 45 | ); 46 | -------------------------------------------------------------------------------- /frontend/components/auth/splash_form_content.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | export const backgroundImg = ( 5 | 6 | ); 7 | 8 | export const splashMessage = ( 9 |
10 |

Note-taking just got noteworthy.

11 |
12 | Never forget a nifty thought again. 13 | Niftynote helps you save, edit and organize 14 | all your ideas in one place. 15 |
16 |
17 | ); 18 | 19 | export const splashFooter = ( 20 |
21 |

Get organized by joining Niftynote today.

22 |
23 | ); 24 | -------------------------------------------------------------------------------- /frontend/components/auth/splash_sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | class SplashSidebar extends React.Component { 5 | render() { 6 | return ( 7 |
8 | 9 |
10 | 11 |
12 | 13 |
14 |
15 | SIGN IN 16 | SIGN UP 17 |
18 | 19 |
20 | Github 21 | LinkedIn 22 | Resume 23 | Website 24 |
25 |
26 | 27 |
28 | ) 29 | } 30 | } 31 | 32 | export default SplashSidebar; 33 | -------------------------------------------------------------------------------- /frontend/components/home/home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import NoteForm from '../notes/note_form'; 4 | import NoteFormTopbar from '../notes/note_form_top_bar'; 5 | import HomeSidebar from '../home_sidebar/home_sidebar'; 6 | 7 | class Home extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | componentDidMount() { 14 | this.props.fetchAllTags(); 15 | } 16 | 17 | componentWillReceiveProps(newProps) { 18 | if (!newProps.currentUser) { this.props.router.push('/') } 19 | } 20 | 21 | render() { 22 | if (!this.props.currentUser) { return null }; 23 | 24 | //this code detects clicks outside of tag bar. 25 | //it caused problems when used in the tag bar componenent. 26 | //there, the click would fire hundreds of times. 27 | //here, there's no access to tag bar methods...so not sure what to do with this. 28 | // $(document).click(event => { 29 | // if(!$(event.target).closest('.entireTagBar').length) { 30 | // console.log('trip'); 31 | // } 32 | // }); 33 | 34 | return ( 35 |
36 | 37 |
38 | 39 | {this.props.children} 40 |
41 | 42 |
43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 |
51 | ); 52 | } 53 | 54 | } 55 | 56 | export default Home; 57 | 58 | //
59 | //
60 | 61 | //this is basically what you're looking to render 62 | // 63 | // 64 | // 65 | // 66 | // 67 | 68 | //
69 | //
70 | 71 | //experimentating with getting a initial currentNote laoded isn't working.. 72 | // componentWillReceiveProps(newProps) { 73 | // if (!this.props.currentNote) {return null} 74 | // 75 | // if (this.props.currentNote.id !== newProps.currentNote.id) { 76 | // this.props.fetchNote(newProps.currentNote.id); 77 | // } 78 | // } 79 | 80 | // componentDidMount() { 81 | // debugger 82 | // this.props.fetchNote(this.props.currentNote); 83 | // } 84 | // if (!this.props.currentNote) {return null} 85 | // 86 | // if (this.props.currentNote) { 87 | // if (this.props.currentNote.id !== newProps.currentNote.id) { 88 | // this.props.fetchNote(newProps.currentNote.id); 89 | // } 90 | // } 91 | -------------------------------------------------------------------------------- /frontend/components/home/home_container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Home from './home'; 3 | import { fetchAllTags } from '../../actions/tags_actions'; 4 | 5 | const mapStateToProps = (state) => { 6 | return { 7 | currentUser: state.session.currentUser 8 | }; 9 | }; 10 | 11 | const mapDispatchToProps = (dispatch) => { 12 | return { 13 | fetchAllTags: () => dispatch(fetchAllTags()), 14 | }; 15 | }; 16 | 17 | export default connect(mapStateToProps, mapDispatchToProps)(Home); 18 | 19 | //experimentation with getting initial currentNote to load isn't working 20 | 21 | // import { connect } from 'react-redux'; 22 | // import { fetchNote } from '../../actions/notes_actions'; 23 | // import { notesSelector } from '../notes/notes_to_array'; 24 | // import Home from './home'; 25 | // 26 | // const mapStateToProps = (state) => { 27 | // let mostRecentNote, mostRecentNotes; 28 | // 29 | // if (jQuery.isEmptyObject(state.currentNote)) { 30 | // mostRecentNotes = notesSelector(state.notes); 31 | // mostRecentNote = mostRecentNotes[0]; 32 | // 33 | // return { 34 | // currentNote: mostRecentNote, 35 | // currentUser: state.session.currentUser 36 | // }; 37 | // 38 | // } 39 | // 40 | // return { 41 | // currentNote: state.currentNote, 42 | // currentUser: state.session.currentUser 43 | // }; 44 | // }; 45 | // 46 | // const mapDispatchToProps = (dispatch) => { 47 | // return { 48 | // fetchNote: (id) => dispatch(fetchNote(id)) 49 | // }; 50 | // } 51 | // 52 | // export default connect(mapStateToProps, mapDispatchToProps)(Home); 53 | -------------------------------------------------------------------------------- /frontend/components/home_sidebar/home_modals/user_dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { connect } from 'react-redux'; 4 | import { logout } from '../../../actions/auth_actions'; 5 | import { resetCurrentNote } from '../../../actions/notes_actions'; 6 | import { resetCurrentNotebook } from '../../../actions/notebooks_actions'; 7 | 8 | class UserDashboard extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.clickHandler = this.clickHandler.bind(this); 12 | } 13 | 14 | // failed attempt to add transition to userDashboardModal 15 | // componentWillUnmount() { 16 | // document.getElementsByClassName('userDashboardModal')[0].setAttribute('id', 'modaltofade'); 17 | // debugger 18 | // } 19 | 20 | clickHandler() { 21 | this.props.resetCurrentNote(); 22 | this.props.resetCurrentNotebook(); 23 | this.props.logout(); 24 | } 25 | 26 | render() { 27 | 28 | return ( 29 |
30 | 31 |

{this.props.currentUser.email}

32 | 33 |
34 | ) 35 | } 36 | 37 | } 38 | 39 | const mapStateToProps = (state) => { 40 | return { 41 | currentUser: state.session.currentUser 42 | }; 43 | }; 44 | 45 | const mapDispatchToProps = (dispatch) => { 46 | return { 47 | logout: () => dispatch(logout()), 48 | resetCurrentNote: () => dispatch(resetCurrentNote()), 49 | resetCurrentNotebook: () => dispatch(resetCurrentNotebook()) 50 | }; 51 | }; 52 | 53 | export default connect(mapStateToProps, mapDispatchToProps)(UserDashboard); 54 | -------------------------------------------------------------------------------- /frontend/components/notebooks/notebook_index_container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchNotebooks } from '../../actions/notebooks_actions'; 3 | import NotebookIndex from './notebook_index'; 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return { 7 | notebooks: Object.values(state.notebooks), 8 | closeNotebookIndex: ownProps.closeNotebookIndex 9 | }; 10 | }; 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | fetchNotebooks: () => dispatch(fetchNotebooks()), 15 | }; 16 | }; 17 | 18 | export default connect(mapStateToProps, mapDispatchToProps)(NotebookIndex); 19 | -------------------------------------------------------------------------------- /frontend/components/notebooks/notebook_item.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router'; 3 | import { deleteNotebook, fetchNotebooks } from '../../actions/notebooks_actions'; 4 | import { fetchNotes } from '../../actions/notes_actions'; 5 | import { connect } from 'react-redux'; 6 | 7 | class NotebookItem extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.openNotebook = this.openNotebook.bind(this); 11 | this.deleteNotebook = this.deleteNotebook.bind(this); 12 | } 13 | 14 | openNotebook(e) { 15 | this.props.closeNotebookIndex(); 16 | this.props.router.push(`/home/notebooks/${e.currentTarget.value}`); 17 | } 18 | 19 | deleteNotebook(e) { 20 | this.props.deleteNotebook(e.currentTarget.getAttribute('value')) 21 | .then(this.props.fetchNotes) 22 | .then(this.props.fetchNotebooks); 23 | 24 | e.stopPropagation(); 25 | } 26 | 27 | render() { 28 | 29 | if (this.props.formType === "panel") { 30 | return ( 31 |
  • 32 | 33 | 34 | 35 | 36 | 37 | 38 |
    {this.props.notebook.title}
    39 |
    {this.props.notebook.note_count} notes
    40 |
  • 41 | ); 42 | } else { 43 | return ( 44 | 47 | ) 48 | 49 | } 50 | } 51 | } 52 | 53 | const mapDispatchToProps = (dispatch) => { 54 | return { 55 | deleteNotebook: (id) => dispatch(deleteNotebook(id)), 56 | fetchNotes: () => dispatch(fetchNotes()), 57 | fetchNotebooks: () => dispatch(fetchNotebooks()) 58 | }; 59 | }; 60 | 61 | export default withRouter(connect(null, mapDispatchToProps)(NotebookItem)); 62 | -------------------------------------------------------------------------------- /frontend/components/notes/note_form_top_bar.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import React from 'react'; 3 | import { notesSelector } from './notes_to_array'; 4 | import Modal from 'react-modal'; 5 | import NoteInfoPage from './note_info_page'; 6 | import { deleteNote } from '../../actions/notes_actions'; 7 | 8 | class NoteFormTopbar extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.deleteNote = this.deleteNote.bind(this); 12 | this.state = {modalIsOpen: false}; 13 | this.openModal = this.openModal.bind(this); 14 | this.closeModal = this.closeModal.bind(this); 15 | } 16 | 17 | openModal() { 18 | this.setState({modalIsOpen: true}); 19 | } 20 | 21 | closeModal() { 22 | this.setState({modalIsOpen: false}); 23 | } 24 | componentWillMount() { 25 | Modal.setAppElement('body'); 26 | } 27 | 28 | deleteNote(e) { 29 | this.props.removeNote(this.props.currentNote.id); 30 | } 31 | 32 | render () { 33 | return ( 34 |
    35 |
    36 |
    37 | 38 | 39 | 40 | 41 |
    42 | 43 |
    44 | 45 | 46 |
    47 |
    48 | 49 | 57 | 58 | 59 |
    60 | ); 61 | } 62 | } 63 | 64 | const mapStateToProps = (state) => { 65 | let mostRecentNote; 66 | 67 | if (jQuery.isEmptyObject(state.currentNote)) { 68 | mostRecentNote = notesSelector(state.notes)[0]; 69 | return { currentNote: mostRecentNote }; 70 | } 71 | 72 | return { currentNote: state.currentNote }; 73 | }; 74 | 75 | 76 | const mapDispatchToProps = (dispatch) => { 77 | return { 78 | removeNote: (id) => dispatch(deleteNote(id)) 79 | }; 80 | }; 81 | 82 | export default connect(mapStateToProps, mapDispatchToProps)(NoteFormTopbar); 83 | -------------------------------------------------------------------------------- /frontend/components/notes/note_indices_container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchNotes, fetchNote, startLoadingAllNotes } from '../../actions/notes_actions'; 3 | import NoteIndex from './note_index'; 4 | import { notesSelector } from './notes_to_array'; 5 | 6 | const mapStateToProps = (state, ownProps) => { 7 | let processed_notes = state.notes; 8 | let notebook = null; 9 | let tag = null; 10 | 11 | if (ownProps.location.pathname.match(/home\/notebooks\/\d+/)) { 12 | processed_notes = Object.values(state.notes).filter( 13 | noteObj => { 14 | return noteObj.notebook_id === parseInt(ownProps.params.notebookId) 15 | }); 16 | 17 | notebook = state.notebooks[ownProps.params.notebookId] 18 | } else if (ownProps.location.pathname.match(/home\/tags\/\d+/)) { 19 | processed_notes = Object.values(state.notes).filter( 20 | noteObj => { 21 | let result = false; 22 | let tags = noteObj.tags; 23 | 24 | for (let i = 0; i < tags.length; i++) { 25 | if (tags[i].id === parseInt(ownProps.params.tagId)) { 26 | result = true; 27 | break; 28 | } 29 | } 30 | return result ? true : false; 31 | }); 32 | tag = state.tags[ownProps.params.tagId]; 33 | } 34 | 35 | let sortedAndProcessedNotes = notesSelector(processed_notes); 36 | 37 | let mostRecentNoteId = null; 38 | let mostRecentNotebookId = null; 39 | if (sortedAndProcessedNotes[0]) { 40 | mostRecentNoteId = sortedAndProcessedNotes[0].id; 41 | mostRecentNotebookId = sortedAndProcessedNotes[0].notebook_id; 42 | } 43 | 44 | return { 45 | notes: sortedAndProcessedNotes, 46 | mostRecentNoteId: mostRecentNoteId, 47 | mostRecentNotebookId: mostRecentNotebookId, 48 | loading: state.loading, 49 | notebook: notebook, 50 | tag: tag 51 | }; 52 | }; 53 | 54 | const mapDispatchToProps = (dispatch) => { 55 | return { 56 | fetchNotes: () => dispatch(fetchNotes()), 57 | fetchNote: (id) => dispatch(fetchNote(id)) 58 | }; 59 | }; 60 | 61 | export default connect(mapStateToProps, mapDispatchToProps)(NoteIndex); 62 | -------------------------------------------------------------------------------- /frontend/components/notes/note_info_page.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | class NoteInfoPage extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.roughSizeOfObject = this.roughSizeOfObject.bind(this); 8 | } 9 | 10 | roughSizeOfObject( object ) { 11 | 12 | var objectList = []; 13 | 14 | var recurse = function( value ) 15 | { 16 | var bytes = 0; 17 | 18 | if ( typeof value === 'boolean' ) { 19 | bytes = 4; 20 | } 21 | else if ( typeof value === 'string' ) { 22 | bytes = value.length * 2; 23 | } 24 | else if ( typeof value === 'number' ) { 25 | bytes = 8; 26 | } 27 | else if 28 | ( 29 | typeof value === 'object' 30 | && objectList.indexOf( value ) === -1 31 | ) 32 | { 33 | objectList[ objectList.length ] = value; 34 | let i; 35 | for( i in value ) { 36 | bytes+= 8; // an assumed existence overhead 37 | bytes+= recurse( value[i] ) 38 | } 39 | } 40 | 41 | return bytes; 42 | } 43 | 44 | return recurse( object ); 45 | } 46 | 47 | render() { 48 | return ( 49 |
    50 | 51 |
    52 |
    53 | 54 | NOTE INFO 55 |
    56 |

    {this.props.note.title}

    57 |
    58 | 59 |
      60 |
    • Created: {(new Date(this.props.note.created_at).toLocaleString())}
    • 61 |
    • Updated: {(new Date(this.props.note.updated_at).toLocaleString())}
    • 62 |
    • Size: {this.roughSizeOfObject(this.props.note)} bytes
    • 63 |
    • Belongs to Notebook: {this.props.notebooksObj[this.props.note.notebook_id].title}
    • 64 |
    65 | 66 |
    67 | ); 68 | } 69 | } 70 | 71 | const mapStateToProps = (state) => { 72 | return { 73 | notebooksObj: state.notebooks 74 | } 75 | } 76 | 77 | export default connect(mapStateToProps, null)(NoteInfoPage); 78 | -------------------------------------------------------------------------------- /frontend/components/notes/note_item.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NoteForm from './note_form'; 3 | import { connect } from 'react-redux'; 4 | import { fetchNote, deleteNote } from '../../actions/notes_actions'; 5 | import htmlToText from 'html-to-text'; 6 | 7 | class NoteItem extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.showNote = this.showNote.bind(this); 11 | this.deleteNote = this.deleteNote.bind(this); 12 | } 13 | 14 | showNote(e) { 15 | this.props.fetchNote(e.currentTarget.value); 16 | } 17 | 18 | deleteNote(e) { 19 | this.props.removeNote(e.currentTarget.getAttribute('value')); 20 | e.stopPropagation(); 21 | } 22 | 23 | render() { 24 | let bodyPreview; 25 | if (this.props.noteItem.body) { 26 | bodyPreview = htmlToText.fromString(this.props.noteItem.body).slice(0, 135); 27 | } else { 28 | bodyPreview = ""; 29 | } 30 | 31 | return ( 32 |
    33 |
  • 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
    {this.props.noteItem.title}
    42 |
    {this.props.noteItem.time_passed}
    43 |
    {bodyPreview}
    44 |
  • 45 |
    46 | ); 47 | } 48 | 49 | } 50 | 51 | const mapDispatchToProps = (dispatch) => { 52 | return { 53 | fetchNote: (id) => dispatch(fetchNote(id)), 54 | removeNote: (id) => dispatch(deleteNote(id)) 55 | }; 56 | }; 57 | 58 | export default connect(null, mapDispatchToProps)(NoteItem); 59 | -------------------------------------------------------------------------------- /frontend/components/notes/notes_to_array.js: -------------------------------------------------------------------------------- 1 | // sort by most recently UPDATED 2 | const dateComparator = (objX, objY) => ( 3 | new Date(objY.updated_at) - new Date(objX.updated_at) 4 | ) 5 | 6 | export const notesSelector = (notesObject) => { 7 | //does any of the below mutate the notesObject? 8 | let sorted_arr = Object.values(notesObject).sort(dateComparator) 9 | 10 | sorted_arr.forEach(noteObj => { 11 | let seconds = Math.floor((Date.now() - new Date(noteObj['updated_at']))/1000), 12 | minutes = Math.floor(seconds/60), 13 | hours = Math.floor(minutes/60), 14 | days = Math.floor(hours/24) 15 | 16 | if (seconds < 2) { 17 | noteObj['time_passed'] = seconds + " second ago"; 18 | } else if (seconds < 60) { 19 | noteObj['time_passed'] = seconds + " seconds ago"; 20 | } else if (minutes < 2) { 21 | noteObj['time_passed'] = minutes + " minute ago"; 22 | } else if (minutes < 60) { 23 | noteObj['time_passed'] = minutes + " minutes ago"; 24 | } else if (hours < 2) { 25 | noteObj['time_passed'] = hours + " hour ago"; 26 | } else if (hours < 24) { 27 | noteObj['time_passed'] = hours + " hours ago"; 28 | } else if (days < 2) { 29 | noteObj['time_passed'] = "YESTERDAY"; 30 | } else if (days < 8) { 31 | noteObj['time_passed'] = days + " days ago"; 32 | } else if (days < 14) { 33 | noteObj['time_passed'] = "LAST WEEK"; 34 | } else { 35 | noteObj['time_passed'] = new Date(noteObj['updated_at']).toLocaleDateString(); 36 | } 37 | }); 38 | 39 | return sorted_arr; 40 | } 41 | 42 | 43 | // noteObj['created_at'] = new Date(noteObj['created_at']).toLocaleString(); 44 | // noteObj['updated_at'] = new Date(noteObj['updated_at']).toLocaleString(); 45 | -------------------------------------------------------------------------------- /frontend/components/root.jsx: -------------------------------------------------------------------------------- 1 | import { Router, Route, IndexRoute, hashHistory } from 'react-router'; 2 | import App from './app'; 3 | import React from 'react'; 4 | import { Provider } from 'react-redux'; 5 | import AuthFormContainer from './auth/auth_form_container'; 6 | import HomeContainer from './home/home_container'; 7 | import NoteIndicesContainer from './notes/note_indices_container'; 8 | import NewNote from './notes/new_note'; 9 | // import TagShowContainer from './tags/tag_show_container'; 10 | 11 | const Root = ({ store }) => { 12 | let _redirectIfLoggedIn = (nextState, replace) => { 13 | if (store.getState().session.currentUser) { 14 | replace('/home'); 15 | } 16 | } 17 | 18 | let _ensure_logged_in = (nextState, replace ) => { 19 | if (!store.getState().session.currentUser) { 20 | replace('/'); 21 | } 22 | } 23 | 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | 43 | // / 44 | 45 | export default Root; 46 | -------------------------------------------------------------------------------- /frontend/components/tags/tag_show.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class TagShow extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | render() { 9 |
    TAG SHOW PAGE
    10 | } 11 | } 12 | 13 | export default TagShow; 14 | -------------------------------------------------------------------------------- /frontend/components/tags/tag_show_container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import TagShow from './tag_show'; 3 | 4 | const mapStateToProps = (state) => { 5 | return { 6 | }; 7 | }; 8 | 9 | const mapDispatchToProps = (dispatch) => { 10 | return { 11 | }; 12 | }; 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(TagShow); 15 | -------------------------------------------------------------------------------- /frontend/niftynote.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import configureStore from './store/store'; 4 | import Root from './components/root'; 5 | 6 | document.addEventListener('DOMContentLoaded', () => { 7 | let store; 8 | 9 | if (window.currentUser) { 10 | const preloadedState = { session: { currentUser: window.currentUser } }; 11 | store = configureStore(preloadedState); 12 | } else { 13 | store = configureStore(); 14 | } 15 | 16 | const root = document.getElementById("root"); 17 | ReactDOM.render(, root); 18 | }); 19 | -------------------------------------------------------------------------------- /frontend/reducers/current_note_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_NOTE, REMOVE_NOTE, RESET_CURRENT_NOTE } from '../actions/notes_actions'; 2 | import { RECEIVE_INDIVIDUAL_NOTE_TAGS, REMOVE_TAG } from '../actions/tags_actions'; 3 | import merge from 'lodash/merge'; 4 | 5 | export const currentNoteReducer = (state = {}, action) => { 6 | Object.freeze(state); 7 | let copyState = merge({}, state); 8 | switch (action.type) { 9 | case RECEIVE_NOTE: 10 | return action.note; 11 | case REMOVE_NOTE: 12 | return {}; 13 | case RESET_CURRENT_NOTE: 14 | return {}; 15 | case RECEIVE_INDIVIDUAL_NOTE_TAGS: 16 | //this is assuming tag creation in the note form on homepage. 17 | //in that case, the selected note whose tags are being edited 18 | //will always be the current note. 19 | copyState.tags = Object.values(action.tags); 20 | return copyState 21 | case REMOVE_TAG: 22 | copyState.tags.forEach((tagsObj, idx) => { 23 | if (tagsObj.name === action.tag.name) { 24 | copyState.tags.splice(idx, 1); 25 | return; 26 | } 27 | }) 28 | return copyState 29 | default: 30 | return state; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/reducers/current_notebook_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_NOTEBOOK, REMOVE_NOTEBOOK, RESET_CURRENT_NOTEBOOK } from '../actions/notebooks_actions'; 2 | 3 | export const currentNotebookReducer = (state = {}, action) => { 4 | Object.freeze(state); 5 | switch (action.type) { 6 | case RECEIVE_NOTEBOOK: 7 | return action.notebook; 8 | case REMOVE_NOTEBOOK: 9 | return {}; 10 | case RESET_CURRENT_NOTEBOOK: 11 | return {}; 12 | default: 13 | return state; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/reducers/loading_reducer.js: -------------------------------------------------------------------------------- 1 | import {RECEIVE_NOTES, RECEIVE_NOTE, 2 | START_LOADING_ALL_NOTES, START_LOADING_NOTE} from 3 | '../actions/notes_actions'; 4 | 5 | const loadingReducer = (state = true, action) => { 6 | switch (action.type) { 7 | case START_LOADING_ALL_NOTES: 8 | case START_LOADING_NOTE: 9 | return true; 10 | case RECEIVE_NOTES: 11 | case RECEIVE_NOTE: 12 | return false; 13 | default: 14 | return state; 15 | } 16 | }; 17 | 18 | export default loadingReducer; 19 | -------------------------------------------------------------------------------- /frontend/reducers/notebooks_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_NOTEBOOKS, RECEIVE_NOTEBOOK, REMOVE_NOTEBOOK } from '../actions/notebooks_actions'; 2 | import merge from 'lodash/merge'; 3 | 4 | export const notebooksReducer = (state = {}, action) => { 5 | Object.freeze(state); 6 | switch (action.type) { 7 | case RECEIVE_NOTEBOOKS: 8 | return action.notebooks; 9 | case RECEIVE_NOTEBOOK: 10 | return merge({}, state, {[action.notebook.id]: action.notebook}); 11 | case REMOVE_NOTEBOOK: 12 | let copyState = merge({}, state); 13 | delete copyState[action.notebook.id]; 14 | return copyState; 15 | default: 16 | return state; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/reducers/notes_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_NOTES, RECEIVE_NOTE, REMOVE_NOTE } from '../actions/notes_actions'; 2 | // import { REMOVE_TAG, RECEIVE_INDIVIDUAL_NOTE_TAGS } from '../actions/tags_actions'; 3 | import merge from 'lodash/merge'; 4 | 5 | export const notesReducer = (state = {}, action) => { 6 | Object.freeze(state); 7 | let copyState = merge({}, state); 8 | switch (action.type) { 9 | case RECEIVE_NOTES: 10 | return action.notes; 11 | case RECEIVE_NOTE: 12 | return merge({}, state, { [action.note.id]: action.note }); 13 | case REMOVE_NOTE: 14 | delete copyState[action.note.id]; 15 | return copyState; 16 | // without the two case statements below, the notes index won't stay 17 | // up to date with the latest tag info changes on the page. 18 | // but the notes index isn't used to access that info anyway, 19 | // so the statements below aren't necessary. 20 | // 21 | // case RECEIVE_INDIVIDUAL_NOTE_TAGS: 22 | // a conditional would have to find which note is being edited. 23 | // it would then modify that note's tags status. 24 | // case REMOVE_TAG: 25 | // for (let key in copyState) { 26 | // let note = copyState[key]; 27 | // note.tags.forEach((tagsObj, idx) => { 28 | // if (tagsObj.name === action.tag.name) { 29 | // note.tags.splice(idx, 1); 30 | // return; 31 | // } 32 | // }) 33 | // } 34 | // return copyState 35 | default: 36 | return state; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/reducers/root_reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { sessionReducer } from './session_reducer'; 4 | import { notesReducer } from './notes_reducer'; 5 | import loadingReducer from './loading_reducer'; 6 | import { notebooksReducer } from './notebooks_reducer'; 7 | import { currentNoteReducer } from './current_note_reducer'; 8 | import { currentNotebookReducer } from './current_notebook_reducer'; 9 | import { tagsReducer } from './tags_reducer'; 10 | 11 | const rootReducer = combineReducers({ 12 | session: sessionReducer, 13 | notes: notesReducer, 14 | loading: loadingReducer, 15 | notebooks: notebooksReducer, 16 | currentNote: currentNoteReducer, 17 | currentNotebook: currentNotebookReducer, 18 | tags: tagsReducer 19 | }); 20 | 21 | export default rootReducer; 22 | -------------------------------------------------------------------------------- /frontend/reducers/session_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_ERRORS, RECEIVE_CURRENT_USER } from '../actions/auth_actions'; 2 | import merge from 'lodash/merge'; 3 | 4 | const _initialState = { 5 | currentUser: null, 6 | errors: [] 7 | }; 8 | 9 | export const sessionReducer = (state = _initialState, action) => { 10 | Object.freeze(state); 11 | switch (action.type) { 12 | case RECEIVE_CURRENT_USER: 13 | return merge({}, _initialState, { currentUser: action.user }); 14 | case RECEIVE_ERRORS: 15 | return merge({}, _initialState, { errors: action.errors }); 16 | default: 17 | return state; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/reducers/tags_reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_ALL_TAGS, RECEIVE_TAG, REMOVE_TAG, RECEIVE_INDIVIDUAL_NOTE_TAGS } from '../actions/tags_actions'; 2 | import merge from 'lodash/merge'; 3 | 4 | export const tagsReducer = (state = {}, action) => { 5 | Object.freeze(state); 6 | let copyState = merge({}, state); 7 | switch (action.type) { 8 | case RECEIVE_ALL_TAGS: 9 | return action.tags; 10 | case RECEIVE_INDIVIDUAL_NOTE_TAGS: 11 | let newAndDestroyedTags = merge({}, action.tags, action.tagsToUpdate) 12 | 13 | for (let key in action.tagsToUpdate) { 14 | copyState[key] = action.tagsToUpdate[key]; 15 | } 16 | 17 | return merge({}, copyState, newAndDestroyedTags); 18 | case RECEIVE_TAG: 19 | return merge({}, state, { [action.tag.id]: action.tag }); 20 | case REMOVE_TAG: 21 | delete copyState[action.tag.id]; 22 | return copyState; 23 | default: 24 | return state; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /frontend/store/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import rootReducer from '../reducers/root_reducer'; 4 | 5 | const middlewares = [thunk]; 6 | 7 | if (process.env.NODE_ENV !== 'production') { 8 | const { logger } = require('redux-logger'); 9 | middlewares.push(logger); 10 | } 11 | 12 | const configureStore = (preloadedState = {}) => ( 13 | createStore(rootReducer, preloadedState, applyMiddleware(...middlewares)) 14 | ); 15 | 16 | export default configureStore; 17 | -------------------------------------------------------------------------------- /frontend/util/notebooks_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchNotebooks = () => ( 2 | $.ajax({ 3 | method: 'GET', 4 | url: 'api/notebooks' 5 | }) 6 | ); 7 | 8 | export const fetchNotebook = (id) => ( 9 | $.ajax({ 10 | method: 'GET', 11 | url: `api/notebooks/${id}` 12 | }) 13 | ); 14 | 15 | 16 | export const createNotebook = (notebook) => ( 17 | $.ajax({ 18 | method: 'POST', 19 | url: `api/notebooks`, 20 | data: { notebook } 21 | }) 22 | ); 23 | 24 | export const updateNotebook = (notebook) => ( 25 | $.ajax({ 26 | method: 'PATCH', 27 | url: `api/notebooks/${notebook.id}`, 28 | data: { notebook } 29 | }) 30 | ); 31 | 32 | export const deleteNotebook = (id) => ( 33 | $.ajax({ 34 | method: 'DELETE', 35 | url: `api/notebooks/${id}` 36 | }) 37 | ); 38 | -------------------------------------------------------------------------------- /frontend/util/notes_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchNotes = () => ( 2 | $.ajax({ 3 | method: 'GET', 4 | url: 'api/notes' 5 | }) 6 | ); 7 | 8 | export const fetchNote = (id) => ( 9 | $.ajax({ 10 | method: 'GET', 11 | url: `api/notes/${id}` 12 | }) 13 | ); 14 | 15 | export const createNote = (note) => ( 16 | $.ajax({ 17 | method: 'POST', 18 | url: `api/notes`, 19 | data: { note } 20 | }) 21 | ); 22 | 23 | export const updateNote = (note) => ( 24 | $.ajax({ 25 | method: 'PATCH', 26 | url: `api/notes/${note.id}`, 27 | data: { note } 28 | }) 29 | ); 30 | 31 | export const deleteNote = (id) => ( 32 | $.ajax({ 33 | method: 'DELETE', 34 | url: `api/notes/${id}` 35 | }) 36 | ); 37 | -------------------------------------------------------------------------------- /frontend/util/session_api_util.js: -------------------------------------------------------------------------------- 1 | export const signup = (user) => { 2 | return $.ajax({ 3 | method: 'POST', 4 | url: 'api/users', 5 | data: {user} 6 | }); 7 | }; 8 | 9 | export const signin = (user) => { 10 | return $.ajax({ 11 | method: 'POST', 12 | url: 'api/session', 13 | data: {user} 14 | }); 15 | }; 16 | 17 | export const logout = () => { 18 | return $.ajax({ 19 | method: 'DELETE', 20 | url: 'api/session' 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/util/tags_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchAllTags = () => ( 2 | $.ajax({ 3 | method: 'GET', 4 | url: 'api/tags' 5 | }) 6 | ); 7 | 8 | //this should turn out unnecessary, because current note will have its tags 9 | // export const fetchIndividualNoteTags = (note_id) => ( 10 | // $.ajax({ 11 | // method: 'GET', 12 | // url: 'api/tags/1', 13 | // data: {tag: {note_id: note_id}} 14 | // }) 15 | // ); 16 | 17 | // $.ajax({method: 'GET', url: 'api/tags/1', data: {tag: {note_id: X}}}) 18 | 19 | export const createTags = (names, note_id) => ( 20 | $.ajax({ 21 | method: 'POST', 22 | url: `api/tags`, 23 | data: { tag: { 24 | names: JSON.stringify(names), 25 | note_id: note_id 26 | } 27 | } 28 | }) 29 | ); 30 | 31 | // $.ajax({ 32 | // method: 'POST', 33 | // url: 'api/tags', 34 | // data: {tag: {names: JSON.stringify( 35 | // ['tag1', 'tag2', 'tag3', 'yetanothernewtag'] 36 | // ), note_id: 1}} 37 | // }) 38 | 39 | export const updateTag = (tagId, names) => ( 40 | $.ajax({ 41 | method: 'PATCH', 42 | url: `api/tags/${tagId}`, 43 | data: {tag: {names: JSON.stringify(names)}} 44 | }) 45 | ); 46 | 47 | // $.ajax({method: 'PATCH', 48 | // url: 'api/tags/11', 49 | // data: {tag: {names: JSON.stringify(['tag1'])}} }) 50 | // .then(succ => console.log(succ), fail => console.log('fail')) 51 | // in react, you're going to want to GET INDEX to refresh after you update taggings 52 | 53 | export const deleteTag = (tagId) => ( 54 | $.ajax({ 55 | method: 'DELETE', 56 | url: `api/tags/${tagId}` 57 | }) 58 | ); 59 | 60 | // $.ajax({method: 'DELETE', 61 | // url: 'api/tags/2'}) 62 | // then(deleted_tag => console.log(deleted_tag)) 63 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Niftynote", 3 | "version": "1.0.0", 4 | "description": "An Evernote-inspired, single-page RESTful web app for note-taking", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "webpack": "webpack", 13 | "postinstall": "webpack" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/jclin2013/Niftynote.git" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/jclin2013/Niftynote/issues" 24 | }, 25 | "engines": { 26 | "node": "7.10.1", 27 | "npm": "4.1.2" 28 | }, 29 | "homepage": "https://github.com/jclin2013/Niftynote#readme", 30 | "dependencies": { 31 | "babel-core": "^6.24.1", 32 | "babel-loader": "^6.4.1", 33 | "babel-preset-es2015": "^6.24.1", 34 | "babel-preset-react": "^6.24.1", 35 | "html-to-text": "^3.2.0", 36 | "lodash": "^4.17.15", 37 | "react": "^15.0", 38 | "react-dom": "^15.5.4", 39 | "react-modal": "^1.7.7", 40 | "react-quill": "^1.0.0-rc.2", 41 | "react-redux": "^5.0.4", 42 | "react-router": "^3.0.5", 43 | "redux": "^3.6.0", 44 | "redux-thunk": "^2.2.0", 45 | "webpack": "^2.4.1", 46 | "webpack-dev-server": ">=3.1.11" 47 | }, 48 | "devDependencies": { 49 | "redux-logger": "^3.0.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The page you were looking for doesn't exist.

    62 |

    You may have mistyped the address or the page may have moved.

    63 |
    64 |

    If you are the application owner check the logs for more information.

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The change you wanted was rejected.

    62 |

    Maybe you tried to change something you didn't have access to.

    63 |
    64 |

    If you are the application owner check the logs for more information.

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    We're sorry, but something went wrong.

    62 |
    63 |

    If you are the application owner check the logs for more information.

    64 |
    65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/favicon.ico -------------------------------------------------------------------------------- /public/images/N.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/N.png -------------------------------------------------------------------------------- /public/images/NiftynoteHomePage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/NiftynoteHomePage.png -------------------------------------------------------------------------------- /public/images/NiftynoteNewNote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/NiftynoteNewNote.png -------------------------------------------------------------------------------- /public/images/NiftynoteNewNotebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/NiftynoteNewNotebook.png -------------------------------------------------------------------------------- /public/images/NiftynoteNotebookInfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/NiftynoteNotebookInfo.png -------------------------------------------------------------------------------- /public/images/NiftynoteSplashPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/NiftynoteSplashPage.png -------------------------------------------------------------------------------- /public/images/Niftynote_files/application.self-6a69034c30ceaed4553ecbd74160365019f3c570e0140f88078c496bf67720c1.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | 14 | 15 | *= require_session 16 | *= require_loading-icon 17 | *= require_home 18 | *= require_notebooks 19 | */ 20 | 21 | 22 | /* --- Reset user agent styles --- */ 23 | 24 | html, body, header, h1, h2, nav, ul, li, a, footer, small, button, submit { 25 | margin: 0; 26 | padding: 0; 27 | border: 0; 28 | font: inherit; 29 | outline: none; 30 | } 31 | 32 | #root { 33 | overflow: hidden; 34 | } 35 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/application.self-f571b9db02bf4ed04c38a6a29072ac382f7d4f47db1fab9817aeadb0d8665235.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/cable.self-6e0514260c1aa76eaf252412ce74e63f68819fd19bf740595f592c5ba4c36537.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | 5 | 6 | 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/edb8528b4a.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome v4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | @import url('//use.fontawesome.com/releases/v4.7.0/css/font-awesome-css.min.css'); 6 | /* FONT PATH 7 | * -------------------------- */ 8 | @font-face { 9 | font-family: 'FontAwesome'; 10 | src: url('//use.fontawesome.com/releases/v4.7.0/fonts/fontawesome-webfont.eot'); 11 | src: url('//use.fontawesome.com/releases/v4.7.0/fonts/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), 12 | url('//use.fontawesome.com/releases/v4.7.0/fonts/fontawesome-webfont.woff2') format('woff2'), 13 | url('//use.fontawesome.com/releases/v4.7.0/fonts/fontawesome-webfont.woff') format('woff'), 14 | url('//use.fontawesome.com/releases/v4.7.0/fonts/fontawesome-webfont.ttf') format('truetype'), 15 | url('//use.fontawesome.com/releases/v4.7.0/fonts/fontawesome-webfont.svg#fontawesomeregular') format('svg'); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | /* 20 | Embed code edb8528b4a 21 | */ 22 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/homeTotalLayout.self-8c727b1c78a0d50ce353b92c43cbffc8e470ad425ea972323c85652e3aaa3b0e.css: -------------------------------------------------------------------------------- 1 | .homeTotalLayout { 2 | display: flex; 3 | height: 100vh; 4 | font-family: 'Yantramanav', sans-serif; 5 | min-width: 800px; 6 | /*border: 2px solid red;*/ 7 | } 8 | 9 | /*#root { 10 | border: 5px solid orange; 11 | }*/ 12 | 13 | .homeTotalLayout .homeSidebar { 14 | width: 75px; 15 | } 16 | 17 | .homeTotalLayout .entireNotesIndexCol { 18 | width: 350px; 19 | } 20 | 21 | .homeLeftSide { 22 | display: flex; 23 | } 24 | 25 | .outerhomeRightSide { 26 | display: flex; 27 | /*width: calc(100% - 425px);*/ 28 | min-width: 360px; 29 | width: 100%; 30 | } 31 | 32 | .innerhomeRightSide { 33 | width: 100%; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | } 38 | 39 | .innerhomeRightSide .noteFormTopBarContainer { 40 | position: relative; 41 | width: 100%; 42 | height: 50px; 43 | } 44 | 45 | .innerhomeRightSide .noteFormTopBarContainer > * { 46 | position: absolute; 47 | } 48 | 49 | .quillContainer > * { 50 | /*border: 1px solid red;*/ 51 | } 52 | 53 | .quillContainer { 54 | display: flex; 55 | flex-direction: column; 56 | max-width: 960px; 57 | min-width: 300px; 58 | height: 100%; 59 | margin-left: 70px; 60 | } 61 | 62 | #root > div > div > div.outerhomeRightSide > div > div.quillContainer > div.quill > div.ql-container.ql-snow > div.ql-editor { 63 | margin-top: 60px; 64 | } 65 | 66 | #root > div > div > div.outerhomeRightSide > div > div.quillContainer > div.quill > div.ql-toolbar.ql-snow { 67 | min-width: 570px; 68 | border-top: none; 69 | border-left: none; 70 | border-right: none; 71 | margin-top: -10px; 72 | } 73 | 74 | .ql-editor { 75 | /*border: 1px solid red;*/ 76 | height: 70vh; 77 | } 78 | 79 | .ql-container.ql-snow { 80 | /*border: 1px solid red;*/ 81 | height: 70vh; 82 | } 83 | 84 | 85 | 86 | 87 | 88 | 89 | /*.entireNotesIndexCol { 90 | overflow: hidden; 91 | }*/ 92 | 93 | /*.innerhomeRightSide { 94 | display: flex; 95 | flex-direction: column; 96 | align-items: center; 97 | }*/ 98 | 99 | /*.homeRightSide .entireNotebookScrollMenu { 100 | margin-top: 50px; 101 | }*/ 102 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/home_note_form_topbar.self-1d87b37ed5c4348d1bd578a6f8b3a9c944fb04cbbba665b490666cd5b2b4cfbb.css: -------------------------------------------------------------------------------- 1 | .entireNoteformTopbar { 2 | height: 50px; 3 | position: fixed; 4 | display: flex; 5 | justify-content: space-between; 6 | z-index: 1; 7 | border-left: 1px solid rgb(212, 210, 210);; 8 | } 9 | 10 | .topbarRightSide { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | .topbarRightSide button { 17 | padding: 10px; 18 | } 19 | 20 | /*Don't display buttons on right side 21 | until features are added*/ 22 | .topbarRightSide { 23 | display: none; 24 | } 25 | 26 | /*Don't display reminder and shortcut 27 | buttons until they've been implemented*/ 28 | .topbarLeftSide button:first-child, 29 | .topbarLeftSide button:nth-child(2) { 30 | display: none; 31 | } 32 | 33 | .topbarLeftSide > * { 34 | outline: none; 35 | border-style: none; 36 | font-size: 20px; 37 | padding: 8px; 38 | background-color: transparent; 39 | color: gray; 40 | margin: 6px; 41 | cursor: pointer; 42 | margin-left: 16px 43 | } 44 | 45 | .topbarLeftSide > *:hover { 46 | color: #2DBE60; 47 | background-color: white; 48 | } 49 | 50 | /*53, 26 px*/ 51 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/home_note_index.self-8de44c309cfa64fcb1a3ee7441cb7b065729ffb7ab8eeff1050a49cd9a104c3e.css: -------------------------------------------------------------------------------- 1 | .entireNotesIndexCol { 2 | /*position: relative;*/ 3 | /*border: 1px solid red;*/ 4 | height: 100vh; 5 | width: 330px; 6 | min-width: 330px; 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | /*ReactModalPortal doesn't work*/ 12 | /*}*/ 13 | 14 | .entireNotesIndexCol .notesColTop { 15 | border-bottom: 1px solid rgb(212, 210, 210); 16 | position: absolute; 17 | z-index: 1; 18 | background-color: white; 19 | width: 315px; 20 | height: auto; 21 | color: gray; 22 | } 23 | 24 | .entireNotesIndexCol .notesIndexColHeaderPart { 25 | padding: 20px 20px 10px 20px; 26 | } 27 | 28 | /*this hides the options button until feature is implemented*/ 29 | .entireNotesIndexCol .notesColTop button { 30 | display: none; 31 | } 32 | 33 | .entireNotesIndexCol ul { 34 | position: relative; 35 | list-style: none; 36 | height: calc(100% - 100px); 37 | margin-top: 100px; 38 | overflow: auto; 39 | } 40 | 41 | .entireNotesIndexCol .notesColTop h1 { 42 | font-size: 25px; 43 | font-weight: 100; 44 | } 45 | 46 | .entireNotesIndexCol .notesColTop 47 | .notesTopBarSecondRow { 48 | display: flex; 49 | justify-content: space-between; 50 | margin: 10px 10px 5px 20px; 51 | } 52 | 53 | /*styling for the notebook show page*/ 54 | 55 | .entireNotesIndexCol .showNotebookNotesBlackBox { 56 | position: relative; 57 | background-color: #393D41; 58 | height: 150px; 59 | display: flex; 60 | width: 350px; 61 | flex-direction: column; 62 | align-items: center; 63 | color: white; 64 | margin-bottom: 44px; 65 | } 66 | 67 | .entireNotesIndexCol .showNotebookNotesBlackBox i { 68 | position: absolute; 69 | top: 0; 70 | right: 0; 71 | padding: 5px; 72 | color: white; 73 | cursor: pointer; 74 | font-size: 20px; 75 | padding: 10px; 76 | } 77 | 78 | .entireNotesIndexCol .showNotebookNotesBlackBox > *:not(i) { 79 | margin: 10px; 80 | } 81 | 82 | .entireNotesIndexCol .showNotebookNotesBlackBox h1 { 83 | margin-top: 30px; 84 | font-size: 35px; 85 | } 86 | 87 | /*only display the button after it works/does something*/ 88 | .entireNotesIndexCol .showNotebookNotesBlackBox button { 89 | display: none; 90 | } 91 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/home_note_item.self-473d6e07a5c8b8ce2cabe63717b3fd30e4ef5c58cead1ac99de9c7d60c8542b3.css: -------------------------------------------------------------------------------- 1 | /*hide this until feature is implemented*/ 2 | 3 | .wholeNoteItem .fa-star { 4 | display: none; 5 | } 6 | 7 | .wholeNoteItem { 8 | position: relative; 9 | width: 100%; 10 | overflow: hidden; 11 | } 12 | 13 | .noteItemButtons { 14 | display: none; 15 | margin-right: 5px; 16 | } 17 | 18 | .wholeNoteItem:hover .noteItemButtons { 19 | display: inherit; 20 | position: absolute; 21 | right: 0; 22 | top: 0; 23 | } 24 | 25 | .noteItemButtons > * { 26 | outline: none; 27 | border-style: none; 28 | font-size: 20px; 29 | padding: 8px; 30 | background-color: #2DBE60; 31 | } 32 | 33 | .noteItemButtons button .fa { 34 | cursor: pointer; 35 | } 36 | 37 | .noteItemBox { 38 | height: 120px; 39 | border-bottom: 1px solid lightgray; 40 | } 41 | 42 | .noteItemBox > * { 43 | padding-left: 20px; 44 | } 45 | 46 | .noteItemTitle { 47 | font-size: 18px; 48 | padding-top: 13px; 49 | } 50 | 51 | .timePassed { 52 | font-size: 13px; 53 | padding-bottom: 5px; 54 | text-transform: uppercase; 55 | } 56 | 57 | .bodySnippet { 58 | font-size: 13px; 59 | } 60 | 61 | .timePassed, .bodySnippet { 62 | color: gray; 63 | } 64 | 65 | .wholeNoteItem:hover div, 66 | .wholeNoteItem:hover button, 67 | .wholeNoteItem:hover { 68 | color: white; 69 | background-color: #2DBE60; 70 | } 71 | 72 | .wholeNoteItem .fa:hover { 73 | display: inherit; 74 | transform: scale(1.5); 75 | color: #E6E6E6; 76 | } 77 | 78 | /*only show these buttons when features are developed*/ 79 | .wholeNoteItem .fa-clock-o, 80 | .wholeNoteItem .fa-comments { 81 | display: none; 82 | } 83 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/home_show_note.self-ccf7d72f5798d978f0812eb523ff6a4fcb32fd8f52cf733e0aba671c539ded45.css: -------------------------------------------------------------------------------- 1 | /*.homeTotalLayout .showNote { 2 | height: 100vh; 3 | overflow: scroll; 4 | } 5 | 6 | .homeTotalLayout .showNote section:last-child { 7 | font-style: italic; 8 | }*/ 9 | 10 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/home_sidebar.self-16cd005b1092bb687eded750450a492b8d8f27a3624b053cfa47ad1ccac4141e.css: -------------------------------------------------------------------------------- 1 | /*take out all buttons that don't do anything*/ 2 | 3 | .homeSidebar .fa-star, 4 | .homeSidebar .slideWorkChat, 5 | .homeSidebar .fa-search, 6 | .homeSidebar .fa-tags, 7 | #root > div > div > div.homeLeftSide > div.homeSidebar > div.bottom4buttons > button.slideShortcuts { 8 | display: none; 9 | } 10 | 11 | .homeSidebar { 12 | position: relative; 13 | z-index: 4; 14 | display: flex; 15 | flex-direction: column; 16 | background-color: #F8F8F8; 17 | height: 100vh; 18 | min-height: 600px; 19 | border-right: 1px solid lightgray; 20 | } 21 | 22 | .homeSidebar .imgcontainer { 23 | flex: 1; 24 | display: flex; 25 | justify-content: center; 26 | } 27 | 28 | .homeSidebar img { 29 | height: 40px; 30 | width: auto; 31 | margin-top: 15px; 32 | cursor: pointer; 33 | } 34 | 35 | .homeSidebar button, 36 | .homeSidebar a { 37 | font-size: 20px; 38 | background-color: transparent; 39 | color: #2DBE60; 40 | border: none; 41 | border-radius: 50%; 42 | padding: 8px; 43 | cursor: pointer; 44 | outline: none; 45 | } 46 | 47 | .homeSidebar .fa-plus { 48 | border: 1px solid #2DBE60; 49 | border-radius: 50%; 50 | padding: 5px 7px 5px 7px; 51 | background-color: white; 52 | } 53 | 54 | .homeSidebar .fa-search, .fa-comment { 55 | border: 1px solid lightgray; 56 | border-radius: 50%; 57 | padding: 7px; 58 | background-color: white; 59 | } 60 | 61 | .homeSidebar .bottom4buttons button:hover, 62 | .homeSidebar .profilecontainer button:hover { 63 | background-color: #2DBE60; 64 | transition: all 0.4s ease 0s; 65 | color: white; 66 | } 67 | 68 | .homeSidebar a:hover .fa-plus, 69 | .homeSidebar a:hover .fa-search, 70 | .homeSidebar button:hover .fa-comment { 71 | background-color: #2DBE60; 72 | transition: all 0.4s ease 0s; 73 | color: white; 74 | border: none; 75 | } 76 | 77 | /*.homeSidebar .fa-plus:hover:after { 78 | content: "Create a note"; 79 | position: absolute; 80 | }*/ 81 | 82 | .homeSidebar .slideWorkChat { 83 | border: 0; 84 | padding: 0; 85 | } 86 | 87 | .homeSidebar .top3buttons, 88 | .homeSidebar .bottom4buttons, 89 | .homeSidebar .profilecontainer { 90 | display: flex; 91 | flex-direction: column; 92 | align-items: center; 93 | text-align: center; 94 | } 95 | 96 | .homeSidebar .top3buttons { 97 | margin-top: 15px; 98 | flex: 3; 99 | } 100 | 101 | .homeSidebar .bottom4buttons { 102 | flex: 6; 103 | } 104 | 105 | .homeSidebar .profilecontainer { 106 | flex: 1; 107 | justify-content: center; 108 | border-top: 1px solid rgb(212, 210, 210); 109 | } 110 | 111 | .homeSidebar .profile { 112 | font-size: 40px; 113 | } 114 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/home_user_dashboard.self-848e9fd1cc4095f8a81c3ff375b303757e49a2e31f11516f95cff429d53ea84f.css: -------------------------------------------------------------------------------- 1 | /*CSS below apparently necessary to target modal's z-index*/ 2 | 3 | body > div:nth-child(3) > div { 4 | z-index: 3; 5 | } 6 | 7 | .userDashboardModal { 8 | position: absolute; 9 | z-index: 3; 10 | bottom: 40px; 11 | left: 80px; 12 | background-color: white; 13 | font-family: 'Yantramanav', sans-serif; 14 | text-align: center; 15 | font-size: 20px; 16 | outline: none; 17 | border: 2px solid rgba(41, 179, 91, 0.36); 18 | -webkit-animation: fadein 0.5s; 19 | } 20 | 21 | .userDashboardModal button { 22 | background-color: #28A956; 23 | width: 160px; 24 | height: 40px; 25 | border-radius: 4px; 26 | border-style: none; 27 | font-size: 18px; 28 | color: white; 29 | cursor: pointer; 30 | transition: all 0.3s ease 0s; 31 | } 32 | 33 | .userDashboardModal > div { 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | justify-content: center; 38 | } 39 | 40 | .userDashboardModal > div > * { 41 | margin: 15px; 42 | } 43 | 44 | .userDashboardModal img { 45 | height: 80px; 46 | } 47 | 48 | @-webkit-keyframes fadein { 49 | from { opacity: 0 } 50 | to { opacity: 1 } 51 | }; 52 | 53 | /* 54 | 55 | #modaltofade { 56 | -webkit-animation: fadeout 2s; 57 | opacity: 0.5; 58 | } 59 | 60 | @-webkit-keyframes fadeout { 61 | from { opacity: 1 } 62 | to { opacity: 0 } 63 | };*/ 64 | 65 | /* 66 | .ReactModalPortal div { 67 | -webkit-animation: fadeout 2s; 68 | background-color: yellow; 69 | } 70 | 71 | */ 72 | 73 | /*ReactModal__Overlay ReactModal__Overlay--after-open" style="position: fixed; top: 0px; left: 0px; right: 0px; bottom: 0px; background-color: transparent;">

    Welcome guest@example.com!

    */ 74 | 75 | /*another way to have things slide in*/ 76 | 77 | /*@keyframes slidein { 78 | from { 79 | margin-left: 100%; 80 | width: 300%; 81 | } 82 | 83 | to { 84 | margin-left: 0%; 85 | width: 100%; 86 | } 87 | }*/ 88 | 89 | /*experiments trying to get modal to transition"*/ 90 | 91 | /*transition: all 1s ease 2s;*/ 92 | /*-webkit-transition: width 2s, height 2s, background-color 2s, -webkit-transform 2s;*/ 93 | /*transition-property: background-color, color; 94 | transition-duration: 1s; 95 | transition-timing-function: ease-out;*/ 96 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/loading-icon.self-eff88a3c18a9c8f10b3f5009d76ba3e80a9cdb3f841fa61a7483704300172c27.css: -------------------------------------------------------------------------------- 1 | .loadingContainer { 2 | display: flex; 3 | height: 100vh; 4 | justify-content: center; 5 | align-items: center; 6 | background: white; 7 | width: 350px; 8 | } 9 | 10 | /*.loadingContainer img { 11 | width: 50px; 12 | height: auto; 13 | }*/ 14 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/note_info_page_modal.self-76af19c385105da801ae1105072f58a5a72b2310c355b678fa87b99981d88f9b.css: -------------------------------------------------------------------------------- 1 | /*this grabs the modal when it appears, 2 | but it's fragile. selector taken from 3 | selector tool in chrome dev tools*/ 4 | 5 | 6 | body > div:nth-child(5) > div { 7 | z-index: 4; 8 | } 9 | 10 | .noteInfoSpread { 11 | outline: none; 12 | height: 100vh; 13 | display: flex; 14 | justify-content: center; 15 | padding-top: 10%; 16 | font-family: 'Yantramanav', sans-serif; 17 | -webkit-animation: fadein 0.5s; 18 | } 19 | 20 | .noteInfoSpread .noteInfoHeader3 { 21 | display: flex; 22 | flex-direction: column; 23 | align-items: center; 24 | color: gray; 25 | } 26 | 27 | .noteInfoSpread figure { 28 | display: flex; 29 | flex-direction: column; 30 | justify-content: center; 31 | align-items: center; 32 | padding-bottom: 20px; 33 | border-bottom: 1px solid rgb(212, 210, 210); 34 | 35 | } 36 | 37 | .noteInfoSpread figure > * { 38 | margin: 5px 39 | } 40 | 41 | .noteInfoSpread figure i { 42 | font-size: 40px; 43 | } 44 | 45 | .noteInfoSpread figure text { 46 | font-size: 15px; 47 | } 48 | 49 | .noteInfoSpread h1 { 50 | font-size: 50px; 51 | } 52 | 53 | .noteInfoSpread ul { 54 | margin-top: 30px; 55 | list-style: none; 56 | } 57 | 58 | .noteInfoSpread ul li { 59 | font-size: 20px; 60 | font-weight: 300; 61 | text-align: left; 62 | margin: 10px; 63 | } 64 | 65 | .noteInfoSpread button { 66 | background-color: #28A956; 67 | width: 100%; 68 | height: 40px; 69 | margin-top: 20px; 70 | margin-bottom: 5px; 71 | border-radius: 4px; 72 | border-style: none; 73 | font-size: 18px; 74 | color: white; 75 | cursor: pointer; 76 | transition: all 0.3s ease 0s; 77 | } 78 | 79 | .noteInfoSpread button:hover { 80 | background: #387a09; 81 | } 82 | 83 | @-webkit-keyframes fadein { 84 | from { opacity: 0 } 85 | to { opacity: 1 } 86 | }; 87 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/notebook_form.self-fa0188729f5b9634dd1168734c5905dd630cbeb6b6285be467385a9354d36d9f.css: -------------------------------------------------------------------------------- 1 | /*#boxOverTextEditor.present {*/ 2 | /*height: calc(100% - 130px);*/ 3 | /*width: calc(100% - 425px); 4 | bottom: 0; 5 | right: 0;*/ 6 | 7 | /*height: calc(100vh - 95px); 8 | width: 100%; 9 | position: absolute; 10 | z-index: 300; 11 | position: absolute; 12 | bottom: 0;*/ 13 | /*background-color: red;*/ 14 | /*}*/ 15 | /* 16 | #boxOverTextEditor.absent { 17 | display: none; 18 | width: 0; 19 | height: 0; 20 | }*/ 21 | 22 | /*#NoteFormBoxed > * { 23 | }*/ 24 | 25 | /*#NoteFormBoxed .quill { 26 | border: 1px solid red; 27 | position: absolute; 28 | bottom: 0; 29 | }*/ 30 | 31 | /*#NoteFormBoxed { 32 | position: relative; 33 | height: calc(100vh - 52px);*/ 34 | /*border: 1px solid red;*/ 35 | /*height: calc(100% - 10px);*/ 36 | /*bottom: 0; 37 | }*/ 38 | 39 | /*#NoteFormBoxed > div.quill > div.ql-container.ql-snow > div.ql-editor.ql-blank { 40 | height: 100%; 41 | }*/ 42 | 43 | /*div.ql-container.ql-snow { 44 | height: calc(100vh - 95px); 45 | }*/ 46 | 47 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/notebook_scrollbar.self-818dd773e0c83d69f90c5e2769177861ccfbf3b8d2dadd37d7f2aec60f337428.css: -------------------------------------------------------------------------------- 1 | /*remove this after done tesing with scrollbar*/ 2 | /*.ql-snow, .entireNotebookScrollMenu { 3 | display: none; 4 | }*/ 5 | 6 | /*.entireNoteformTopbar*/ 7 | 8 | /*.entireNotebookScrollMenu { 9 | display: absolute; 10 | z-index: 20; 11 | right: 0; 12 | width: 100%; 13 | border: 1px solid red; 14 | }*/ 15 | 16 | /*.entireNotebookScrollMenu selector { 17 | width: 800px; 18 | }*/ 19 | 20 | /* 21 | select#soflow-color { 22 | color: #fff; 23 | background-image: url(http://i62.tinypic.com/15xvbd5.png), -webkit-linear-gradient(#779126, #779126 40%, #779126); 24 | background-color: #2DBE60; 25 | -webkit-border-radius: 20px; 26 | -moz-border-radius: 20px; 27 | border-radius: 20px; 28 | padding-left: 15px; 29 | } 30 | */ 31 | 32 | 33 | .entireNotebookScrollMenu input { 34 | display: none; 35 | } 36 | 37 | #root > div > div > div.outerhomeRightSide > div > div.quillContainer > div.entireNotebookScrollMenu > form { 38 | margin: 0px 0px 0px 10px; 39 | } 40 | 41 | #soflow-color { 42 | outline: none; 43 | font-family: 'Yantramanav', sans-serif; 44 | /*background-color: #2DBE60;*/ 45 | background-color: transparent; 46 | border: 1px solid gray; 47 | font-size: 15px; 48 | margin: 0px 10px 10px 0px; 49 | width: 30%; 50 | } 51 | 52 | 53 | 54 | 55 | /*.entireNotebookScrollMenu { 56 | position: absolute; 57 | top: 150px; 58 | left: 635px; 59 | margin-right: 15px; 60 | background-color: transparent; 61 | padding: 0px; 62 | right: 0; 63 | z-index: 11; 64 | cursor: pointer; 65 | }*/ 66 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/notebooks.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05(1).js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 4 | }).call(this); 5 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/notebooks.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 4 | }).call(this); 5 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/notebooks.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/Niftynote_files/notebooks.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css -------------------------------------------------------------------------------- /public/images/Niftynote_files/notes.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 4 | }).call(this); 5 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/notes.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/Niftynote_files/notes.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css -------------------------------------------------------------------------------- /public/images/Niftynote_files/quill_customize.self-b96b21c4064fe7178e1cb734d7e363e38219bb891efb8abd5ff5ac595b665f77.css: -------------------------------------------------------------------------------- 1 | .quill { 2 | /*height: 80vh; 3 | max-width: 950px; 4 | min-width: 290px; 5 | display: flex;*/ 6 | 7 | /*border: 2px solid blue;*/ 8 | } 9 | 10 | .ql-editor { 11 | /*align-self: flex-start;*/ 12 | /*margin-top: 80px;*/ 13 | /*border: 2px solid blue;*/ 14 | /*height: calc(100%-300px)*/ 15 | } 16 | 17 | /* 18 | 19 | #root > div > div > div.outerhomeRightSide > div > div.quillContainer > div > div.ql-toolbar.ql-snow { 20 | position: absolute; 21 | left: 0; 22 | border: none; 23 | border-bottom: 1px solid lightgray; 24 | width: 935px; 25 | z-index: 10; 26 | border: 2px solid blue; 27 | } 28 | 29 | */ 30 | 31 | .quillContainer { 32 | position: relative; 33 | width: 100%; 34 | z-index: 0; 35 | } 36 | 37 | #root > div > div > div.outerhomeRightSide > div > div.quillContainer > input[type="text"] { 38 | width: 100%; 39 | outline: none; 40 | border: none; 41 | color: #2DBE60; 42 | font-size: 30px; 43 | font-weight: 200; 44 | padding: 0px 15px 0px 15px; 45 | overflow: hidden; 46 | position: absolute; 47 | top: 115px; 48 | width: 100%; 49 | } 50 | 51 | 52 | /*.homeRightSide { 53 | display: flex; 54 | flex-direction: column; 55 | justify-content: flex-start; 56 | align-items: flex-start;*/ 57 | /*position: absolute; 58 | right: 0;*/ 59 | 60 | /*align-items: flex-start;*/ 61 | /*}*/ 62 | 63 | /*div.quillOuterContainer > div > div > div.ql-toolbar.ql-snow {*/ 64 | /*min-width: 1492px;*/ 65 | /*}*/ 66 | 67 | /*.entire*/ 68 | 69 | /*.ql-blank { 70 | border-color: transparent; 71 | border: 1px solid red; 72 | }*/ 73 | 74 | /*.testquillclassUsertest { 75 | background: yellow; 76 | } 77 | 78 | #QUILLTEST { 79 | border: none; 80 | }*/ 81 | 82 | 83 | /*.homeTotalLayout .quill {*/ 84 | /*display: none;*/ 85 | /*width: calc(100% - 73px - 350px); 86 | margin: 0; 87 | border: 1px solid orange; 88 | }*/ 89 | 90 | 91 | /*.ql-editor { 92 | border: transparent; 93 | }*/ 94 | 95 | /*#root > div > div > div.noteTopbarANDnoteForm > div.quill > div.ql-toolbar.ql-snow*/ 96 | /*


    */ 97 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/splashSideoutModal.self-edafd66fbcd066f874db70a4225bb498a8d38c30b622c8b05430d2990b9903aa.css: -------------------------------------------------------------------------------- 1 | .splashSidebarContainer { 2 | font-family: 'Yantramanav', sans-serif; 3 | font-weight: 300; 4 | position: fixed; 5 | height: 100%; 6 | width: 15vh; 7 | min-width: 300px; 8 | background: rgba(0, 0, 0, 0.9); 9 | z-index: 1; 10 | right: 0; 11 | top: 0; 12 | margin: 0; 13 | -webkit-animation: slidein 0.5s; 14 | } 15 | 16 | .splashSidebarContainer .buttonContainer { 17 | display: flex; 18 | justify-content: flex-end; 19 | margin-right: 15px; 20 | margin-top: 15px; 21 | } 22 | 23 | .splashSidebarContainer button { 24 | color: white; 25 | border: none; 26 | background-color: transparent; 27 | padding: 18px; 28 | cursor: pointer; 29 | font-size: 16px; 30 | outline: none; 31 | } 32 | 33 | .splashSidebarLinks { 34 | margin-left: 30px; 35 | } 36 | 37 | .splashSidebarLinks div > * { 38 | color: white; 39 | text-decoration: none; 40 | } 41 | 42 | .splashSidebarLinks .linksgroup1, 43 | .splashSidebarLinks .linksgroup2 { 44 | display: flex; 45 | flex-direction: column; 46 | font-size: 20px; 47 | padding-bottom: 15px; 48 | border-bottom: 1px solid rgba(243, 241, 241, 0.47); 49 | } 50 | 51 | .splashSidebarLinks .linksgroup1 > * { 52 | margin-bottom: 10px; 53 | } 54 | 55 | .splashSidebarLinks .linksgroup2 { 56 | margin-top: 100px; 57 | padding-bottom: 0px; 58 | } 59 | 60 | .splashSidebarLinks .linksgroup2 > * { 61 | margin-bottom: 40px; 62 | } 63 | 64 | @-webkit-keyframes slidein { 65 | from { right: -25% } 66 | to { right: 0 } 67 | }; 68 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/users.self-877aef30ae1b040ab8a3aba4e3e309a11d7f2612f44dde450b5c157aa5f95c05.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 4 | }).call(this); 5 | -------------------------------------------------------------------------------- /public/images/Niftynote_files/users.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/Niftynote_files/users.self-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css -------------------------------------------------------------------------------- /public/images/darkspace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/darkspace.jpg -------------------------------------------------------------------------------- /public/images/earth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/earth.jpg -------------------------------------------------------------------------------- /public/images/landscape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/landscape.jpg -------------------------------------------------------------------------------- /public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/loading.gif -------------------------------------------------------------------------------- /public/images/nightsky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/public/images/nightsky.jpg -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/api/notebooks_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::NotebooksControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get api_notebooks_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get create" do 10 | get api_notebooks_create_url 11 | assert_response :success 12 | end 13 | 14 | test "should get show" do 15 | get api_notebooks_show_url 16 | assert_response :success 17 | end 18 | 19 | test "should get update" do 20 | get api_notebooks_update_url 21 | assert_response :success 22 | end 23 | 24 | test "should get destroy" do 25 | get api_notebooks_destroy_url 26 | assert_response :success 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/controllers/api/notes_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::NotesControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get api_notes_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get create" do 10 | get api_notes_create_url 11 | assert_response :success 12 | end 13 | 14 | test "should get update" do 15 | get api_notes_update_url 16 | assert_response :success 17 | end 18 | 19 | test "should get show" do 20 | get api_notes_show_url 21 | assert_response :success 22 | end 23 | 24 | test "should get destroy" do 25 | get api_notes_destroy_url 26 | assert_response :success 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/controllers/api/tags_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::TagsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get api_tags_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get create" do 10 | get api_tags_create_url 11 | assert_response :success 12 | end 13 | 14 | test "should get update" do 15 | get api_tags_update_url 16 | assert_response :success 17 | end 18 | 19 | test "should get destroy" do 20 | get api_tags_destroy_url 21 | assert_response :success 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /test/controllers/api/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::UsersControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/notebooks_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NotebooksControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get notebooks_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get create" do 10 | get notebooks_create_url 11 | assert_response :success 12 | end 13 | 14 | test "should get show" do 15 | get notebooks_show_url 16 | assert_response :success 17 | end 18 | 19 | test "should get update" do 20 | get notebooks_update_url 21 | assert_response :success 22 | end 23 | 24 | test "should get destroy" do 25 | get notebooks_destroy_url 26 | assert_response :success 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/notebooks.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: notebooks 4 | # 5 | # id :integer not null, primary key 6 | # author_id :integer not null 7 | # title :string not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # defaultNotebook :boolean default("false"), not null 11 | # 12 | 13 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 14 | 15 | one: 16 | author_id: 1 17 | title: MyString 18 | 19 | two: 20 | author_id: 1 21 | title: MyString 22 | -------------------------------------------------------------------------------- /test/fixtures/notes.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: notes 4 | # 5 | # id :integer not null, primary key 6 | # title :string not null 7 | # body :text 8 | # notebook_id :integer not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | 13 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 14 | 15 | one: 16 | title: MyString 17 | body: MyText 18 | author_id: 1 19 | notebook_id: 1 20 | 21 | two: 22 | title: MyString 23 | body: MyText 24 | author_id: 1 25 | notebook_id: 1 26 | -------------------------------------------------------------------------------- /test/fixtures/taggings.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: taggings 4 | # 5 | # id :integer not null, primary key 6 | # tag_id :integer not null 7 | # note_id :integer not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | 12 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 13 | 14 | one: 15 | tag_id: 1 16 | note_id: 1 17 | 18 | two: 19 | tag_id: 1 20 | note_id: 1 21 | -------------------------------------------------------------------------------- /test/fixtures/tags.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: tags 4 | # 5 | # id :integer not null, primary key 6 | # name :string not null 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 12 | 13 | one: 14 | name: MyString 15 | 16 | two: 17 | name: MyString 18 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # email :string not null 7 | # password_digest :string not null 8 | # session_token :string not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | 13 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 14 | 15 | # This model initially had no columns defined. If you add columns to the 16 | # model remove the '{}' from the fixture names and add the columns immediately 17 | # below each fixture, per the syntax in the comments below 18 | # 19 | one: {} 20 | # column: value 21 | # 22 | two: {} 23 | # column: value 24 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/test/models/.keep -------------------------------------------------------------------------------- /test/models/note_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: notes 4 | # 5 | # id :integer not null, primary key 6 | # title :string not null 7 | # body :text 8 | # notebook_id :integer not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | 13 | require 'test_helper' 14 | 15 | class NoteTest < ActiveSupport::TestCase 16 | # test "the truth" do 17 | # assert true 18 | # end 19 | end 20 | -------------------------------------------------------------------------------- /test/models/notebook_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: notebooks 4 | # 5 | # id :integer not null, primary key 6 | # author_id :integer not null 7 | # title :string not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # defaultNotebook :boolean default("false"), not null 11 | # 12 | 13 | require 'test_helper' 14 | 15 | class NotebookTest < ActiveSupport::TestCase 16 | # test "the truth" do 17 | # assert true 18 | # end 19 | end 20 | -------------------------------------------------------------------------------- /test/models/tag_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: tags 4 | # 5 | # id :integer not null, primary key 6 | # name :string not null 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | require 'test_helper' 12 | 13 | class TagTest < ActiveSupport::TestCase 14 | # test "the truth" do 15 | # assert true 16 | # end 17 | end 18 | -------------------------------------------------------------------------------- /test/models/tagging_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: taggings 4 | # 5 | # id :integer not null, primary key 6 | # tag_id :integer not null 7 | # note_id :integer not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | 12 | require 'test_helper' 13 | 14 | class TaggingTest < ActiveSupport::TestCase 15 | # test "the truth" do 16 | # assert true 17 | # end 18 | end 19 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # email :string not null 7 | # password_digest :string not null 8 | # session_token :string not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # 12 | 13 | require 'test_helper' 14 | 15 | class UserTest < ActiveSupport::TestCase 16 | # test "the truth" do 17 | # assert true 18 | # end 19 | end 20 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jon-lin/Niftynote/7c68e4b207948d7fa797006545ba29fff0f00015/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require("webpack"); 3 | 4 | var plugins = []; 5 | var devPlugins = []; 6 | 7 | var prodPlugins = [ 8 | new webpack.DefinePlugin({ 9 | 'process.env': { 10 | 'NODE_ENV': JSON.stringify('production') 11 | } 12 | }), 13 | new webpack.optimize.UglifyJsPlugin({ 14 | compress: { 15 | warnings: true 16 | } 17 | }) 18 | ]; 19 | 20 | plugins = plugins.concat( 21 | process.env.NODE_ENV === 'production' ? prodPlugins : devPlugins 22 | ) 23 | 24 | module.exports = { 25 | entry: './frontend/niftynote.jsx', 26 | output: { 27 | filename: 'app/assets/javascripts/bundle.js', 28 | }, 29 | plugins: plugins, 30 | module: { 31 | loaders: [ 32 | { 33 | test: [/\.jsx?$/], 34 | exclude: /(node_modules)/, 35 | loader: 'babel-loader', 36 | query: { 37 | presets: ['es2015', 'react'] 38 | } 39 | } 40 | ] 41 | }, 42 | node: {fs: 'empty'}, 43 | devtool: 'source-map', 44 | resolve: { 45 | extensions: ['.js', '.jsx', '*'] 46 | } 47 | }; 48 | --------------------------------------------------------------------------------