├── .gitignore ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── a-1.png │ │ ├── a-10.png │ │ ├── a-11.png │ │ ├── a-2.png │ │ ├── a-3.png │ │ ├── a-4.png │ │ ├── a-5.png │ │ ├── a-6.png │ │ ├── a-7.png │ │ ├── a-8.jpg │ │ ├── a-9.jpg │ │ ├── b-1.jpg │ │ ├── b-10.jpg │ │ ├── b-11.jpg │ │ ├── b-12.jpg │ │ ├── b-13.jpg │ │ ├── b-14.jpg │ │ ├── b-15.jpg │ │ ├── b-16.jpg │ │ ├── b-17.jpg │ │ ├── b-18.jpg │ │ ├── b-19.jpg │ │ ├── b-2.jpg │ │ ├── b-20.jpg │ │ ├── b-21.jpg │ │ ├── b-22.jpg │ │ ├── b-3.jpg │ │ ├── b-4.jpg │ │ ├── b-5.jpg │ │ ├── b-6.jpg │ │ ├── b-7.jpg │ │ ├── b-8.jpg │ │ ├── b-9.jpg │ │ ├── board-inline.png │ │ ├── board-tooltip.png │ │ ├── board.png │ │ ├── bulleted-list-tooltip.png │ │ ├── bulleted-list.png │ │ ├── c-1.jpg │ │ ├── c-2.jpg │ │ ├── c-3.jpg │ │ ├── c-4.jpg │ │ ├── callout-tooltip.png │ │ ├── callout.png │ │ ├── code-tooltip.png │ │ ├── code.png │ │ ├── d-1.jpg │ │ ├── d-10.jpg │ │ ├── d-11.jpg │ │ ├── d-2.jpg │ │ ├── d-3.jpg │ │ ├── d-4.jpg │ │ ├── d-5.jpg │ │ ├── d-6.jpg │ │ ├── d-7.jpg │ │ ├── d-8.jpg │ │ ├── d-9.jpg │ │ ├── design.png │ │ ├── divider-tooltip.png │ │ ├── divider.png │ │ ├── e-1.jpg │ │ ├── e-10.jpg │ │ ├── e-11.jpg │ │ ├── e-12.jpg │ │ ├── e-13.jpg │ │ ├── e-14.jpg │ │ ├── e-15.jpg │ │ ├── e-16.jpg │ │ ├── e-17.jpg │ │ ├── e-18.jpg │ │ ├── e-2.jpg │ │ ├── e-3.jpg │ │ ├── e-4.jpg │ │ ├── e-5.jpg │ │ ├── e-6.jpg │ │ ├── e-7.jpg │ │ ├── e-8.jpg │ │ ├── e-9.jpg │ │ ├── engineering.png │ │ ├── f-1.jpg │ │ ├── f-10.jpg │ │ ├── f-11.jpg │ │ ├── f-12.jpg │ │ ├── f-13.jpg │ │ ├── f-14.jpg │ │ ├── f-15.jpg │ │ ├── f-16.jpg │ │ ├── f-17.jpg │ │ ├── f-18.jpg │ │ ├── f-19.jpg │ │ ├── f-2.jpg │ │ ├── f-20.jpg │ │ ├── f-21.jpg │ │ ├── f-22.jpg │ │ ├── f-23.jpg │ │ ├── f-24.jpg │ │ ├── f-25.jpg │ │ ├── f-26.jpg │ │ ├── f-27.jpg │ │ ├── f-28.jpg │ │ ├── f-29.jpg │ │ ├── f-3.jpg │ │ ├── f-30.jpg │ │ ├── f-4.jpg │ │ ├── f-5.jpg │ │ ├── f-6.jpg │ │ ├── f-7.jpg │ │ ├── f-8.jpg │ │ ├── f-9.jpg │ │ ├── favicon.png │ │ ├── h1-tooltip.png │ │ ├── h1.png │ │ ├── h2-tooltip.png │ │ ├── h2.png │ │ ├── h3-tooltip.png │ │ ├── h3.png │ │ ├── hero-1.png │ │ ├── hero-2.png │ │ ├── hr.png │ │ ├── ibm-logo.png │ │ ├── image-tooltip.png │ │ ├── image.png │ │ ├── lilnotion-favicon-dark.svg │ │ ├── lilnotion-logo-dark.svg │ │ ├── link-tooltip.png │ │ ├── link.png │ │ ├── loom-logo.png │ │ ├── marketing.png │ │ ├── meta.png │ │ ├── nike-logo.png │ │ ├── numbered-list-tooltip.png │ │ ├── numbered-list.png │ │ ├── page-tooltip.png │ │ ├── page.png │ │ ├── pixar-logo.png │ │ ├── product.png │ │ ├── quote-tooltip.png │ │ ├── quote.png │ │ ├── readme-1.png │ │ ├── readme-2.png │ │ ├── readme-3.png │ │ ├── readme-4.png │ │ ├── sales.png │ │ ├── slack-logo.png │ │ ├── spotify-logo.png │ │ ├── square-logo.png │ │ ├── table-inline.png │ │ ├── table-tooltip.png │ │ ├── table.png │ │ ├── text-tooltip.png │ │ ├── text.png │ │ ├── to-do-tooltip.png │ │ ├── to-do.png │ │ ├── toggle-tooltip.png │ │ ├── toggle.png │ │ └── wiki.png │ ├── javascripts │ │ ├── api │ │ │ ├── blocks.coffee │ │ │ ├── pages.coffee │ │ │ ├── sessions.coffee │ │ │ └── users.coffee │ │ ├── application.js │ │ ├── cable.js │ │ ├── channels │ │ │ └── .keep │ │ └── static_pages.coffee │ └── stylesheets │ │ ├── application.scss │ │ ├── base │ │ ├── colors.scss │ │ ├── fonts.scss │ │ ├── mixins.scss │ │ ├── reset.scss │ │ └── styles.scss │ │ ├── components │ │ ├── auth.scss │ │ ├── blocks │ │ │ └── block.scss │ │ ├── editor │ │ │ ├── editor.scss │ │ │ └── sidebar.scss │ │ ├── forms.scss │ │ ├── menus │ │ │ └── block-menus.scss │ │ ├── navbar.scss │ │ └── page │ │ │ └── page.scss │ │ └── static_pages.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── api │ │ ├── blocks_controller.rb │ │ ├── pages_controller.rb │ │ ├── sessions_controller.rb │ │ └── users_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ └── static_pages_controller.rb ├── helpers │ ├── api │ │ ├── blocks_helper.rb │ │ ├── pages_helper.rb │ │ ├── sessions_helper.rb │ │ └── users_helper.rb │ ├── application_helper.rb │ └── static_pages_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── block.rb │ ├── concerns │ │ └── .keep │ ├── page.rb │ └── user.rb └── views │ ├── api │ ├── blocks │ │ ├── index.json.jbuilder │ │ └── show.json.jbuilder │ ├── pages │ │ ├── index.json.jbuilder │ │ └── show.json.jbuilder │ └── users │ │ ├── _user.json.jbuilder │ │ └── 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 └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── 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 │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── secrets.yml ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20211209235153_enable_uuid.rb │ ├── 20211209235259_create_users.rb │ ├── 20211209235656_create_active_storage_tables.active_storage.rb │ ├── 20211209235740_create_pages.rb │ ├── 20211210000613_create_blocks.rb │ └── 20211210115758_add_more_indices.rb ├── schema.rb └── seeds.rb ├── frontend ├── actions │ ├── block-actions.js │ ├── page-actions.js │ └── session-actions.js ├── components │ ├── App.jsx │ ├── PageNotFound.jsx │ ├── Root.jsx │ ├── auth │ │ ├── LoginForm.jsx │ │ ├── LoginFormContainer.js │ │ ├── SignupForm.jsx │ │ ├── SignupFormContainer.js │ │ └── SplashHome.jsx │ ├── blocks │ │ ├── Block copy.jsx │ │ ├── Block.jsx │ │ ├── BlockContainer.jsx │ │ ├── BulletedList.jsx │ │ ├── BulletedListContainer.jsx │ │ ├── Callout.jsx │ │ ├── CalloutContainer.jsx │ │ ├── Code.jsx │ │ ├── CodeContainer.jsx │ │ ├── Divider.jsx │ │ ├── DividerContainer.jsx │ │ ├── DragHandle.jsx │ │ ├── Heading1.jsx │ │ ├── Heading1Container.jsx │ │ ├── Heading2.jsx │ │ ├── Heading2Container.jsx │ │ ├── Heading3.jsx │ │ ├── Heading3Container.jsx │ │ ├── Image.jsx │ │ ├── ImageContainer.jsx │ │ ├── Link.jsx │ │ ├── LinkContainer.jsx │ │ ├── NumberedList.jsx │ │ ├── NumberedListContainer.jsx │ │ ├── Paragraph.jsx │ │ ├── ParagraphContainer.jsx │ │ ├── PlusHandle.jsx │ │ ├── Quote.jsx │ │ ├── QuoteContainer.jsx │ │ ├── RadixBlock.jsx │ │ ├── ToDo.jsx │ │ ├── ToDoContainer.jsx │ │ ├── Toggle.jsx │ │ └── ToggleContainer.jsx │ ├── editor │ │ ├── Editor.jsx │ │ ├── EditorContainer.js │ │ └── Loader.jsx │ ├── menus │ │ ├── BlockActionMenu.jsx │ │ ├── BlockActionMenuContainer.jsx │ │ ├── BlockSelectMenu.jsx │ │ ├── BlockSelectMenuContainer.jsx │ │ ├── BlockSlashMenu.jsx │ │ ├── BlockSlashMenuContainer.jsx │ │ ├── RadixSelectMenu.jsx │ │ ├── SelectMenuRow.jsx │ │ └── SlashMenuRow.jsx │ ├── navbar │ │ ├── AuthNavBar.jsx │ │ ├── AuthNavBarContainer.js │ │ ├── NavBar.jsx │ │ └── NavBarContainer.js │ ├── page │ │ ├── Page copy.jsx │ │ ├── Page.jsx │ │ ├── PageContainer.js │ │ └── coverData.js │ └── sidebar │ │ ├── OutlinerMenu.jsx │ │ ├── OutlinerRow.jsx │ │ ├── Sidebar.jsx │ │ └── SidebarContainer.js ├── entry.jsx ├── reducers │ ├── block-errors-reducer.js │ ├── blocks-reducer.js │ ├── entities-reducer.js │ ├── errors-reducer.js │ ├── page-errors-reducer.js │ ├── pages-reducer.js │ ├── root-reducer.js │ ├── selectors.js │ ├── session-errors-reducer.js │ ├── session-reducer.js │ ├── ui-reducer.js │ └── users-reducer.js ├── store │ └── store.js └── util │ ├── blocks-api-util.js │ ├── pages-api-util.js │ ├── route-util.jsx │ ├── session-api-util.js │ └── utils.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 └── robots.txt ├── test ├── application_system_test_case.rb ├── controllers │ ├── .keep │ ├── api │ │ ├── blocks_controller_test.rb │ │ ├── pages_controller_test.rb │ │ ├── sessions_controller_test.rb │ │ └── users_controller_test.rb │ └── static_pages_controller_test.rb ├── fixtures │ ├── .keep │ ├── blocks.yml │ ├── files │ │ └── .keep │ ├── pages.yml │ └── users.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── block_test.rb │ ├── page_test.rb │ └── user_test.rb ├── system │ └── .keep └── test_helper.rb ├── vendor └── .keep └── webpack.config.js /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.1 -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) do |repo_name| 4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 5 | "https://github.com/#{repo_name}.git" 6 | end 7 | 8 | ruby '2.5.1' 9 | 10 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 11 | gem 'rails', '~> 5.2.6' 12 | # Use postgresql as the database for Active Record 13 | gem 'pg', '>= 0.18', '< 2.0' 14 | # Use Puma as the app server 15 | gem 'puma', '~> 3.7' 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 | # See https://github.com/rails/execjs#readme for more supported runtimes 21 | # gem 'therubyracer', platforms: :ruby 22 | 23 | # Use CoffeeScript for .coffee assets and views 24 | gem 'coffee-rails', '~> 4.2' 25 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 26 | gem 'turbolinks', '~> 5' 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', '~> 4.0' 31 | # Use ActiveModel has_secure_password 32 | gem 'bcrypt', '~> 3.1.7' 33 | 34 | gem 'jquery-rails' 35 | gem 'aws-sdk-s3' 36 | gem 'terser' 37 | 38 | # Use Capistrano for deployment 39 | # gem 'capistrano-rails', group: :development 40 | 41 | group :development, :test do 42 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 43 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 44 | # Adds support for Capybara system testing and selenium driver 45 | gem 'capybara', '>= 2.15' 46 | gem 'selenium-webdriver' 47 | gem 'pry-rails' 48 | gem 'better_errors' 49 | gem 'binding_of_caller' 50 | gem 'annotate' 51 | end 52 | 53 | group :development do 54 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 55 | # gem 'web-console', '>= 3.3.0' 56 | gem 'listen', '>= 3.0.5', '< 3.2' 57 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 58 | gem 'spring' 59 | gem 'spring-watcher-listen', '~> 2.0.0' 60 | end 61 | 62 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 63 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 64 | -------------------------------------------------------------------------------- /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/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/a-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-1.png -------------------------------------------------------------------------------- /app/assets/images/a-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-10.png -------------------------------------------------------------------------------- /app/assets/images/a-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-11.png -------------------------------------------------------------------------------- /app/assets/images/a-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-2.png -------------------------------------------------------------------------------- /app/assets/images/a-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-3.png -------------------------------------------------------------------------------- /app/assets/images/a-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-4.png -------------------------------------------------------------------------------- /app/assets/images/a-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-5.png -------------------------------------------------------------------------------- /app/assets/images/a-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-6.png -------------------------------------------------------------------------------- /app/assets/images/a-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-7.png -------------------------------------------------------------------------------- /app/assets/images/a-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-8.jpg -------------------------------------------------------------------------------- /app/assets/images/a-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/a-9.jpg -------------------------------------------------------------------------------- /app/assets/images/b-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-1.jpg -------------------------------------------------------------------------------- /app/assets/images/b-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-10.jpg -------------------------------------------------------------------------------- /app/assets/images/b-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-11.jpg -------------------------------------------------------------------------------- /app/assets/images/b-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-12.jpg -------------------------------------------------------------------------------- /app/assets/images/b-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-13.jpg -------------------------------------------------------------------------------- /app/assets/images/b-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-14.jpg -------------------------------------------------------------------------------- /app/assets/images/b-15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-15.jpg -------------------------------------------------------------------------------- /app/assets/images/b-16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-16.jpg -------------------------------------------------------------------------------- /app/assets/images/b-17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-17.jpg -------------------------------------------------------------------------------- /app/assets/images/b-18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-18.jpg -------------------------------------------------------------------------------- /app/assets/images/b-19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-19.jpg -------------------------------------------------------------------------------- /app/assets/images/b-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-2.jpg -------------------------------------------------------------------------------- /app/assets/images/b-20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-20.jpg -------------------------------------------------------------------------------- /app/assets/images/b-21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-21.jpg -------------------------------------------------------------------------------- /app/assets/images/b-22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-22.jpg -------------------------------------------------------------------------------- /app/assets/images/b-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-3.jpg -------------------------------------------------------------------------------- /app/assets/images/b-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-4.jpg -------------------------------------------------------------------------------- /app/assets/images/b-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-5.jpg -------------------------------------------------------------------------------- /app/assets/images/b-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-6.jpg -------------------------------------------------------------------------------- /app/assets/images/b-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-7.jpg -------------------------------------------------------------------------------- /app/assets/images/b-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-8.jpg -------------------------------------------------------------------------------- /app/assets/images/b-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/b-9.jpg -------------------------------------------------------------------------------- /app/assets/images/board-inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/board-inline.png -------------------------------------------------------------------------------- /app/assets/images/board-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/board-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/board.png -------------------------------------------------------------------------------- /app/assets/images/bulleted-list-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/bulleted-list-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/bulleted-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/bulleted-list.png -------------------------------------------------------------------------------- /app/assets/images/c-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/c-1.jpg -------------------------------------------------------------------------------- /app/assets/images/c-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/c-2.jpg -------------------------------------------------------------------------------- /app/assets/images/c-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/c-3.jpg -------------------------------------------------------------------------------- /app/assets/images/c-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/c-4.jpg -------------------------------------------------------------------------------- /app/assets/images/callout-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/callout-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/callout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/callout.png -------------------------------------------------------------------------------- /app/assets/images/code-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/code-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/code.png -------------------------------------------------------------------------------- /app/assets/images/d-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-1.jpg -------------------------------------------------------------------------------- /app/assets/images/d-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-10.jpg -------------------------------------------------------------------------------- /app/assets/images/d-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-11.jpg -------------------------------------------------------------------------------- /app/assets/images/d-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-2.jpg -------------------------------------------------------------------------------- /app/assets/images/d-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-3.jpg -------------------------------------------------------------------------------- /app/assets/images/d-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-4.jpg -------------------------------------------------------------------------------- /app/assets/images/d-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-5.jpg -------------------------------------------------------------------------------- /app/assets/images/d-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-6.jpg -------------------------------------------------------------------------------- /app/assets/images/d-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-7.jpg -------------------------------------------------------------------------------- /app/assets/images/d-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-8.jpg -------------------------------------------------------------------------------- /app/assets/images/d-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/d-9.jpg -------------------------------------------------------------------------------- /app/assets/images/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/design.png -------------------------------------------------------------------------------- /app/assets/images/divider-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/divider-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/divider.png -------------------------------------------------------------------------------- /app/assets/images/e-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-1.jpg -------------------------------------------------------------------------------- /app/assets/images/e-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-10.jpg -------------------------------------------------------------------------------- /app/assets/images/e-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-11.jpg -------------------------------------------------------------------------------- /app/assets/images/e-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-12.jpg -------------------------------------------------------------------------------- /app/assets/images/e-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-13.jpg -------------------------------------------------------------------------------- /app/assets/images/e-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-14.jpg -------------------------------------------------------------------------------- /app/assets/images/e-15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-15.jpg -------------------------------------------------------------------------------- /app/assets/images/e-16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-16.jpg -------------------------------------------------------------------------------- /app/assets/images/e-17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-17.jpg -------------------------------------------------------------------------------- /app/assets/images/e-18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-18.jpg -------------------------------------------------------------------------------- /app/assets/images/e-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-2.jpg -------------------------------------------------------------------------------- /app/assets/images/e-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-3.jpg -------------------------------------------------------------------------------- /app/assets/images/e-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-4.jpg -------------------------------------------------------------------------------- /app/assets/images/e-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-5.jpg -------------------------------------------------------------------------------- /app/assets/images/e-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-6.jpg -------------------------------------------------------------------------------- /app/assets/images/e-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-7.jpg -------------------------------------------------------------------------------- /app/assets/images/e-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-8.jpg -------------------------------------------------------------------------------- /app/assets/images/e-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/e-9.jpg -------------------------------------------------------------------------------- /app/assets/images/engineering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/engineering.png -------------------------------------------------------------------------------- /app/assets/images/f-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-1.jpg -------------------------------------------------------------------------------- /app/assets/images/f-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-10.jpg -------------------------------------------------------------------------------- /app/assets/images/f-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-11.jpg -------------------------------------------------------------------------------- /app/assets/images/f-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-12.jpg -------------------------------------------------------------------------------- /app/assets/images/f-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-13.jpg -------------------------------------------------------------------------------- /app/assets/images/f-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-14.jpg -------------------------------------------------------------------------------- /app/assets/images/f-15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-15.jpg -------------------------------------------------------------------------------- /app/assets/images/f-16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-16.jpg -------------------------------------------------------------------------------- /app/assets/images/f-17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-17.jpg -------------------------------------------------------------------------------- /app/assets/images/f-18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-18.jpg -------------------------------------------------------------------------------- /app/assets/images/f-19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-19.jpg -------------------------------------------------------------------------------- /app/assets/images/f-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-2.jpg -------------------------------------------------------------------------------- /app/assets/images/f-20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-20.jpg -------------------------------------------------------------------------------- /app/assets/images/f-21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-21.jpg -------------------------------------------------------------------------------- /app/assets/images/f-22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-22.jpg -------------------------------------------------------------------------------- /app/assets/images/f-23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-23.jpg -------------------------------------------------------------------------------- /app/assets/images/f-24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-24.jpg -------------------------------------------------------------------------------- /app/assets/images/f-25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-25.jpg -------------------------------------------------------------------------------- /app/assets/images/f-26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-26.jpg -------------------------------------------------------------------------------- /app/assets/images/f-27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-27.jpg -------------------------------------------------------------------------------- /app/assets/images/f-28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-28.jpg -------------------------------------------------------------------------------- /app/assets/images/f-29.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-29.jpg -------------------------------------------------------------------------------- /app/assets/images/f-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-3.jpg -------------------------------------------------------------------------------- /app/assets/images/f-30.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-30.jpg -------------------------------------------------------------------------------- /app/assets/images/f-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-4.jpg -------------------------------------------------------------------------------- /app/assets/images/f-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-5.jpg -------------------------------------------------------------------------------- /app/assets/images/f-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-6.jpg -------------------------------------------------------------------------------- /app/assets/images/f-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-7.jpg -------------------------------------------------------------------------------- /app/assets/images/f-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-8.jpg -------------------------------------------------------------------------------- /app/assets/images/f-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/f-9.jpg -------------------------------------------------------------------------------- /app/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/favicon.png -------------------------------------------------------------------------------- /app/assets/images/h1-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/h1-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/h1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/h1.png -------------------------------------------------------------------------------- /app/assets/images/h2-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/h2-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/h2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/h2.png -------------------------------------------------------------------------------- /app/assets/images/h3-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/h3-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/h3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/h3.png -------------------------------------------------------------------------------- /app/assets/images/hero-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/hero-1.png -------------------------------------------------------------------------------- /app/assets/images/hero-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/hero-2.png -------------------------------------------------------------------------------- /app/assets/images/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/hr.png -------------------------------------------------------------------------------- /app/assets/images/ibm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/ibm-logo.png -------------------------------------------------------------------------------- /app/assets/images/image-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/image-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/image.png -------------------------------------------------------------------------------- /app/assets/images/lilnotion-favicon-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/lilnotion-logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/assets/images/link-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/link-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/link.png -------------------------------------------------------------------------------- /app/assets/images/loom-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/loom-logo.png -------------------------------------------------------------------------------- /app/assets/images/marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/marketing.png -------------------------------------------------------------------------------- /app/assets/images/meta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/meta.png -------------------------------------------------------------------------------- /app/assets/images/nike-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/nike-logo.png -------------------------------------------------------------------------------- /app/assets/images/numbered-list-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/numbered-list-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/numbered-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/numbered-list.png -------------------------------------------------------------------------------- /app/assets/images/page-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/page-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/page.png -------------------------------------------------------------------------------- /app/assets/images/pixar-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/pixar-logo.png -------------------------------------------------------------------------------- /app/assets/images/product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/product.png -------------------------------------------------------------------------------- /app/assets/images/quote-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/quote-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/quote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/quote.png -------------------------------------------------------------------------------- /app/assets/images/readme-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/readme-1.png -------------------------------------------------------------------------------- /app/assets/images/readme-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/readme-2.png -------------------------------------------------------------------------------- /app/assets/images/readme-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/readme-3.png -------------------------------------------------------------------------------- /app/assets/images/readme-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/readme-4.png -------------------------------------------------------------------------------- /app/assets/images/sales.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/sales.png -------------------------------------------------------------------------------- /app/assets/images/slack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/slack-logo.png -------------------------------------------------------------------------------- /app/assets/images/spotify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/spotify-logo.png -------------------------------------------------------------------------------- /app/assets/images/square-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/square-logo.png -------------------------------------------------------------------------------- /app/assets/images/table-inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/table-inline.png -------------------------------------------------------------------------------- /app/assets/images/table-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/table-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/table.png -------------------------------------------------------------------------------- /app/assets/images/text-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/text-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/text.png -------------------------------------------------------------------------------- /app/assets/images/to-do-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/to-do-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/to-do.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/to-do.png -------------------------------------------------------------------------------- /app/assets/images/toggle-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/toggle-tooltip.png -------------------------------------------------------------------------------- /app/assets/images/toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/toggle.png -------------------------------------------------------------------------------- /app/assets/images/wiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/images/wiki.png -------------------------------------------------------------------------------- /app/assets/javascripts/api/blocks.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/pages.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/sessions.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, or any plugin's 5 | // 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 rails-ujs 15 | //= require activestorage 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /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/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/static_pages.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/application.scss: -------------------------------------------------------------------------------- 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, or any plugin's 6 | * 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_self 14 | */ 15 | 16 | // Reset 17 | @import "base/reset.scss"; 18 | 19 | // Core 20 | @import "base/fonts.scss"; 21 | @import "base/colors.scss"; 22 | @import "base/styles.scss"; 23 | @import "base/mixins.scss"; 24 | 25 | // Components 26 | @import "components/*"; 27 | @import "components/editor/*"; 28 | @import "components/page/*"; 29 | @import "components/blocks/*"; 30 | @import "components/menus/*"; 31 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/colors.scss: -------------------------------------------------------------------------------- 1 | 2 | $nav-item-hover-background: rgb(249, 245, 241); 3 | 4 | $red-50: rgb(253, 245, 242); 5 | $red-100: rgb(251, 235, 233); 6 | $red-200: rgb(249, 229, 227); 7 | 8 | $gray-opacity-10: rgba(15, 15, 15, 0.1); 9 | $gray-opacity-15: rgba(15, 15, 15, 0.15); 10 | $gray-opacity-20: rgba(15, 15, 15, 0.2); 11 | $gray-opacity-50: rgba(15, 15, 15, 0.5); 12 | 13 | $light-gray: rgba(15, 15, 15, 0.1); 14 | $medium-light-gray: rgba(0, 0, 0, 0.4); 15 | 16 | $sidebar-hover: rgba(55, 53, 47, 0.08); 17 | $sidebar-page-block: rgba(25, 23, 17, 0.6); 18 | 19 | 20 | $sidebar-switch: rgba(55, 53, 47, 0.4); 21 | $sidebar-icon: rgba(55, 53, 47, 0.6); 22 | $switcher-label: rgba(55, 53, 47, 1); 23 | 24 | $gray-500: rgb(247, 246, 243); 25 | 26 | $blue-300: rgba(46, 170, 220, 1); 27 | $blue-500: rgba(43, 154, 199, 1); 28 | $blue-700: rgba(6, 156, 205, 1); 29 | 30 | $red-300: rgba(235, 87, 87, 30%); 31 | $red-400: #e16259; 32 | $red-500: #cf534a; 33 | $red-600: #be5643; 34 | $red-700: #bf4d45; 35 | 36 | $t-gray-50: hsl(0, 0%, 98%); 37 | $t-gray-100: hsl(240, 5%, 96%); 38 | $t-gray-200: hsl(240, 6%, 90%); 39 | $t-gray-300: hsl(240, 5%, 84%); 40 | $t-gray-400: hsl(240, 5%, 65%); 41 | $t-gray-500: hsl(240, 4%, 46%); 42 | $t-gray-600: hsl(240, 5%, 34%); 43 | $t-gray-700: hsl(240, 5%, 26%); 44 | $t-gray-800: hsl(240, 4%, 16%); 45 | $t-gray-900: hsl(240, 6%, 10%); 46 | 47 | $t-sky-50: hsl(204, 100%, 97%); 48 | $t-sky-100: hsl(204, 94%, 94%); 49 | $t-sky-200: hsl(201, 94%, 86%); 50 | $t-sky-300: hsl(199, 95%, 74%); 51 | $t-sky-400: hsl(198, 93%, 60%); 52 | $t-sky-500: hsl(199, 89%, 48%); 53 | $t-sky-600: hsl(200, 98%, 39%); 54 | $t-sky-700: hsl(201, 96%, 32%); 55 | $t-sky-800: hsl(201, 90%, 27%); 56 | $t-sky-900: hsl(202, 80%, 24%); 57 | -------------------------------------------------------------------------------- /app/assets/stylesheets/base/fonts.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); 2 | 3 | $font: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 4 | 5 | * { 6 | font-family: $font; 7 | } 8 | body { 9 | font-family: $font; 10 | line-height: 1.5; 11 | } 12 | 13 | h1 { 14 | font-size: 50px; 15 | font-weight: 700; 16 | margin-bottom: 16px; 17 | } 18 | 19 | h2 { 20 | font-size: 32px; 21 | font-weight: 600; 22 | margin-bottom: 12px; 23 | } 24 | 25 | h3 { 26 | font-size: 24px; 27 | font-weight: 600; 28 | margin-bottom: 8px; 29 | } 30 | 31 | h4 { 32 | font-size: 21px; 33 | font-weight: 600; 34 | margin-top: 10px; 35 | margin-bottom: 10px; 36 | } 37 | 38 | p { 39 | font-size: 16px; 40 | font-weight: 400; 41 | margin-bottom: 8px; 42 | } 43 | 44 | button { 45 | font-family: inherit; 46 | font-size: inherit; 47 | font-weight: inherit; 48 | line-height: inherit; 49 | } 50 | 51 | a { 52 | text-decoration: underline; 53 | } 54 | 55 | // tablet 56 | 57 | @media (max-width: 720px) { 58 | h1 { 59 | font-size: 50px; 60 | font-weight: 700; 61 | margin-bottom: 16px; 62 | } 63 | } 64 | 65 | 66 | // mobile 67 | 68 | @media (max-width: 360px) { 69 | .hero-title { 70 | font-size: 40px; 71 | } 72 | 73 | .hero-subtitle { 74 | font-size: 17px; 75 | } 76 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/base/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin font-stack-brand { 2 | font-family: sans-serif; 3 | } 4 | 5 | @mixin font-stack-header { 6 | font-family: sans-serif; 7 | } 8 | 9 | @mixin font-stack-body { 10 | font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 11 | } 12 | 13 | @mixin font-stack-code { 14 | font-family: Menlo, "Inconsolata", Monaco, "Courier New", Courier, monospace; 15 | } 16 | 17 | @mixin breakpoint($point) { 18 | @if $point == xl { 19 | @media (max-width: 1440px) { @content; } 20 | } 21 | 22 | @if $point == l { 23 | @media (max-width: 1080px) { @content; } 24 | } 25 | 26 | @if $point == m { 27 | @media (min-width: 720px) { @content; } 28 | } 29 | 30 | @if $point == s { 31 | @media (min-width: 360px) { @content; } 32 | } 33 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/base/reset.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video, 14 | svg, button, main, input, textarea { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | outline: 0; 19 | box-sizing: border-box; 20 | background: transparent; 21 | font: inherit; 22 | font-family: 'Inter', sans-serif; 23 | color: inherit; 24 | text-align: inherit; 25 | text-decoration: inherit; 26 | vertical-align: baseline; 27 | margin-block-start: 0; 28 | margin-block-end: 0; 29 | margin-inline-start: 0; 30 | margin-inline-end: 0; 31 | } 32 | 33 | article, aside, details, figcaption, figure, 34 | footer, header, hgroup, menu, nav, section { 35 | display: block; 36 | } 37 | 38 | body { 39 | line-height: 1; 40 | height: 100vh; 41 | } 42 | 43 | ol, ul { 44 | list-style: none; 45 | } 46 | 47 | img { 48 | display: block; 49 | width: 100%; 50 | height: auto; 51 | } 52 | 53 | blockquote, q { 54 | quotes: none; 55 | } 56 | 57 | blockquote:before, blockquote:after, 58 | q:before, q:after { 59 | content: ''; 60 | content: none; 61 | } 62 | 63 | table { 64 | border-collapse: collapse; 65 | border-spacing: 0; 66 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/base/styles.scss: -------------------------------------------------------------------------------- 1 | .global-contain { 2 | margin: 0 auto; 3 | } 4 | 5 | .user-content-wrapper { 6 | max-width: 1024px; 7 | margin: 48px auto; 8 | padding: 0 32px; 9 | display: flex; 10 | flex-direction: column; 11 | align-items: center; 12 | } 13 | 14 | a:focus { 15 | box-shadow: rgba(46, 170, 220, 70%) 0px 0px 0px 1px inset, rgba(46, 170, 220, 40%) 0px 0px 0px 2px; 16 | border-radius: 3px; 17 | } 18 | 19 | a:active { 20 | box-shadow: none; 21 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/components/editor/editor.scss: -------------------------------------------------------------------------------- 1 | // #root { 2 | // width: 100vw; 3 | // height: 100vh; 4 | // } 5 | 6 | #editor { 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | flex-direction: row; 11 | align-items: stretch; 12 | transition: 0.5s; 13 | } 14 | 15 | .loader { 16 | width: 100vw; 17 | height: 100vh; 18 | position: relative; 19 | display: grid; 20 | place-items: center; 21 | } 22 | 23 | .loader-text { 24 | font-size: 40px; 25 | font-weight: 600; 26 | color: rgb(192, 192, 192); 27 | } 28 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/navbar.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 64px; 3 | box-sizing: border-box; 4 | color: #111111; 5 | background: #fffefc; 6 | 7 | .outer { 8 | max-width: 1200px; 9 | height: 64px; 10 | margin: 0 auto; 11 | padding: 0 32px; 12 | } 13 | 14 | .inner { 15 | height: 64px; 16 | display: flex; 17 | flex-flow: row nowrap; 18 | justify-content: space-between; 19 | align-items: center; 20 | 21 | .logo-wrapper { 22 | height: 64px; 23 | display: flex; 24 | align-items: center; 25 | 26 | .logo { 27 | display: block; 28 | height: 32px; 29 | } 30 | 31 | .logo:focus { 32 | box-shadow: rgba(46, 170, 220, 70%) 0px 0px 0px 1px inset, 33 | rgba(46, 170, 220, 40%) 0px 0px 0px 2px; 34 | } 35 | 36 | .logo:active { 37 | box-shadow: none; 38 | } 39 | } 40 | 41 | .nav-wrapper { 42 | display: flex; 43 | flex-flow: row nowrap; 44 | 45 | .nav { 46 | height: 64px; 47 | display: flex; 48 | flex-flow: row nowrap; 49 | align-items: center; 50 | font-weight: 500; 51 | 52 | .nav-list { 53 | display: flex; 54 | flex-flow: row nowrap; 55 | align-items: center; 56 | 57 | .item { 58 | height: 32px; 59 | border-radius: 3px; 60 | display: flex; 61 | flex-flow: row nowrap; 62 | align-items: center; 63 | 64 | a, 65 | a:visited { 66 | color: inherit; 67 | text-decoration: none; 68 | line-height: 1; 69 | padding: 8px 12px; 70 | } 71 | 72 | .nav-logout { 73 | cursor: pointer; 74 | border-radius: 3px; 75 | } 76 | 77 | .nav-logout:focus { 78 | box-shadow: rgba(46, 170, 220, 70%) 0px 0px 0px 1px inset, 79 | rgba(46, 170, 220, 40%) 0px 0px 0px 2px; 80 | } 81 | 82 | .nav-logout:active { 83 | box-shadow: none; 84 | } 85 | } 86 | 87 | .item:hover { 88 | background-color: $nav-item-hover-background; 89 | } 90 | } 91 | 92 | .nav-divider { 93 | width: 1px; 94 | height: 20px; 95 | margin: 0 10px; 96 | background: $light-gray; 97 | } 98 | 99 | .nav-greeting { 100 | display: flex; 101 | align-items: center; 102 | font-weight: 400; 103 | padding: 0 12px; 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | .header.private { 111 | border-bottom: 1px solid $light-gray; 112 | } 113 | 114 | @media (max-width: 680px) { 115 | .nav-divider { 116 | display: none; 117 | } 118 | 119 | .nav-list.links { 120 | display: none !important; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static_pages.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the StaticPages controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /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/blocks_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::BlocksController < ApplicationController 2 | 3 | def index 4 | @blocks = Block.where(user_id: params[:user_id]) 5 | if @blocks 6 | render :index 7 | else 8 | render json: @blocks.errors.full_messages, status: 422 9 | end 10 | end 11 | 12 | def create 13 | @block = Block.new(block_params) 14 | if @block.save 15 | render :show 16 | else 17 | render json: @block.errors.full_messages, status: 422 18 | end 19 | end 20 | 21 | def show 22 | @block = Block.find_by(id: params[:id]) 23 | render :show 24 | end 25 | 26 | def update 27 | @block = Block.find_by(id: params[:id]) 28 | 29 | # check for image file and manually attach 30 | # do not allow attachment if image already exists 31 | image = params[:block][:image_url] 32 | if image && image != '' && !@block.photo.attached? 33 | p image 34 | p image.path 35 | p image.original_filename 36 | @block.photo.attach(io: File.open(image.path), filename: image.original_filename) 37 | end 38 | 39 | if @block.update(block_params) 40 | render :show 41 | else 42 | render json: @block.errors.full_messages, status: 422 43 | end 44 | end 45 | 46 | def destroy 47 | block = Block.find_by(id: params[:id]) 48 | if block 49 | block.destroy 50 | else 51 | render json: {:error => "Not found"}.to_json, status: 404 52 | end 53 | end 54 | 55 | private 56 | 57 | def block_params 58 | params.require(:block).permit(:id, :user_id, :page_id, :block_type, :text, :image_url, :image_caption, :checked, :expanded, :toggle_inner_text, :link_page_id,:order_index, :created_at, :updated_at, :format => [:color, :background_color], :icon => [:id, :name, :colons, :text, :unified, :skin, :native, :custom, :image_url, :short_names => [], :emoticons => [], ]) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /app/controllers/api/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::PagesController < ApplicationController 2 | 3 | def index 4 | @pages = current_user.pages 5 | render :index 6 | end 7 | 8 | def create 9 | @page = Page.new(page_params) 10 | if @page.save 11 | render :show 12 | else 13 | render json: @page.errors.full_messages, status: 422 14 | return 15 | end 16 | end 17 | 18 | def show 19 | @page = Page.find_by(id: params[:id]) 20 | if @page 21 | render :show 22 | else 23 | render json: {:error => "Not-found"}.to_json, status: 404 24 | end 25 | end 26 | 27 | def update 28 | @page = Page.find_by(id: params[:id]) 29 | # debugger 30 | # Check to see if block_ids are changing 31 | if @page.block_ids != page_params[:block_ids] 32 | p "block_ids conditional" 33 | # This method forces Active Record to recognize a change is coming 34 | @page.block_ids_will_change! 35 | # Find number of block_ids 36 | num_blocks = @page.block_ids.length 37 | # Pop all previous block_ids from @page 38 | num_blocks.times { @page.block_ids.pop } 39 | # Re-add all new block_ids 40 | # debugger 41 | page_params[:block_ids].each do |block_id| 42 | p 'shoveling a block id of ' + block_id 43 | @page.block_ids << block_id 44 | # debugger 45 | end 46 | # p "a", @page.block_ids 47 | # debugger 48 | if @page.save! 49 | # p 'controller updated and saved page' 50 | # p "b", @page.block_ids 51 | # debugger 52 | render :show 53 | # debugger 54 | # p "c", @page.block_ids 55 | else 56 | render json: @page.errors.full_messages, status: 422 57 | end 58 | else 59 | if @page.update(page_params) 60 | render :show 61 | else 62 | render json: @page.errors.full_messages, status: 422 63 | end 64 | end 65 | 66 | # if @page.update(page_params) 67 | # render :show 68 | # else 69 | # render json: @page.errors.full_messages, status: 422 70 | # end 71 | end 72 | 73 | def destroy 74 | page = Page.find_by(id: params[:id]) 75 | if page 76 | page.destroy 77 | else 78 | render json: {:error => "Not-found"}.to_json, status: 404 79 | end 80 | end 81 | 82 | private 83 | 84 | def format(hash) 85 | output = Hash.new 86 | hash.each do |key, value| 87 | output[key] = value 88 | end 89 | output 90 | end 91 | 92 | def page_params 93 | params[:page][:block_ids] = [] if params[:page][:block_ids] == nil 94 | params.require(:page).permit(:id, :user_id, :title, :gallery_image_url, :uploaded_image_url, :style, :created_at, :updated_at, :block_ids => [], :icon => [:id, :name, :colons, :text, :unified, :skin, :native, :custom, :image_url, :short_names => [], :emoticons => [], ]) 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /app/controllers/api/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::SessionsController < ApplicationController 2 | 3 | def create 4 | @user = User.find_by_credentials( 5 | params[:user][:email], 6 | params[:user][:password] 7 | ) 8 | if @user 9 | login(@user) 10 | render '/api/users/show' 11 | else 12 | render json: ['Invalid email/password combination'], status: 401 13 | end 14 | end 15 | 16 | def destroy 17 | user = current_user 18 | if user 19 | logout 20 | render json: { message: 'Logout successful.'} 21 | else 22 | render json: ['Not currently logged in'], status: 404 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::UsersController < ApplicationController 2 | def create 3 | @user = User.new(user_params) 4 | if @user.save 5 | login(@user) 6 | render :show 7 | else 8 | render json: @user.errors.full_messages, status: 422 9 | end 10 | end 11 | 12 | private 13 | 14 | def user_params 15 | params.require(:user).permit(:email, :first_name, :last_name, :password) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | 3 | before_action :underscore_params! 4 | helper_method :current_user, :logged_in? 5 | 6 | def current_user 7 | return nil unless session[:session_token] 8 | @current_user ||= User.find_by(session_token: session[:session_token]) 9 | end 10 | 11 | def logged_in? 12 | !!current_user 13 | end 14 | 15 | def login(user) 16 | session[:session_token] = user.reset_session_token! 17 | @current_user = user 18 | end 19 | 20 | def logout 21 | current_user.reset_session_token! if logged_in? 22 | session[:session_token] = nil 23 | @current_user = nil 24 | end 25 | 26 | def underscore_params! 27 | underscore_hash = -> (hash) do 28 | hash.transform_keys!(&:underscore) 29 | hash.each do |key, value| 30 | if value.is_a?(ActionController::Parameters) 31 | underscore_hash.call(value) 32 | elsif value.is_a?(Array) 33 | value.each do |item| 34 | next unless item.is_a?(ActionController::Parameters) 35 | underscore_hash.call(item) 36 | end 37 | end 38 | end 39 | end 40 | underscore_hash.call(params) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/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/blocks_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::BlocksHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/pages_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::PagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::SessionsHelper 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/helpers/static_pages_helper.rb: -------------------------------------------------------------------------------- 1 | module StaticPagesHelper 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/block.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: blocks 4 | # 5 | # id :uuid not null, primary key 6 | # user_id :uuid not null 7 | # page_id :uuid not null 8 | # block_type :string default("paragraph"), not null 9 | # text :text default("") 10 | # image_url :string default("") 11 | # image_caption :string default("") 12 | # checked :boolean default(FALSE) 13 | # expanded :boolean default(FALSE) 14 | # toggle_inner_text :string default("") 15 | # link_page_id :string default("") 16 | # format :jsonb 17 | # icon :jsonb 18 | # order_index :integer default(0) 19 | # created_at :datetime not null 20 | # updated_at :datetime not null 21 | # 22 | class Block < ApplicationRecord 23 | belongs_to :user, 24 | optional: true, 25 | foreign_key: :user, 26 | class_name: :User 27 | 28 | belongs_to :page, 29 | optional: true, 30 | foreign_key: :page_id, 31 | class_name: :Page 32 | 33 | has_one_attached :photo 34 | 35 | end 36 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/page.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: pages 4 | # 5 | # id :uuid not null, primary key 6 | # user_id :uuid not null 7 | # title :string default("") 8 | # block_ids :string default([]), is an Array 9 | # gallery_image_url :string default("") 10 | # uploaded_image_url :string default("") 11 | # icon :json 12 | # style :json 13 | # created_at :datetime not null 14 | # updated_at :datetime not null 15 | # 16 | class Page < ApplicationRecord 17 | belongs_to :user, 18 | optional: true, 19 | foreign_key: :user_id, 20 | class_name: :User 21 | 22 | has_one_attached :cover 23 | 24 | has_many :blocks 25 | end 26 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :uuid not null, primary key 6 | # email :string not null 7 | # first_name :string not null 8 | # last_name :string not null 9 | # password_digest :string not null 10 | # session_token :string not null 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | class User < ApplicationRecord 15 | validates :email, presence: true, uniqueness: true 16 | validates :first_name, :last_name, presence: true 17 | validates :password, length: { minimum: 6 }, allow_nil: true 18 | validates :password_digest, presence: true 19 | validates :session_token, presence: true, uniqueness: true 20 | 21 | after_initialize :ensure_session_token 22 | 23 | attr_reader :password 24 | 25 | has_many :pages 26 | has_many :blocks 27 | 28 | def self.generate_session_token 29 | SecureRandom::urlsafe_base64 30 | end 31 | 32 | def reset_session_token! 33 | self.session_token = User.generate_session_token 34 | self.save! 35 | self.session_token 36 | end 37 | 38 | def self.find_by_credentials(email, password) 39 | user = User.find_by(email: email) 40 | if user && user.is_password?(password) 41 | return user 42 | else 43 | nil 44 | end 45 | end 46 | 47 | def is_password?(password) 48 | password_object = BCrypt::Password.new(self.password_digest) 49 | password_object.is_password?(password) 50 | end 51 | 52 | def password=(password) 53 | @password = password 54 | self.password_digest = BCrypt::Password.create(password) 55 | end 56 | 57 | private 58 | 59 | def ensure_session_token 60 | self.session_token ||= User.generate_session_token 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/views/api/blocks/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @blocks.each do |block| 2 | json.set! block.id do 3 | json.extract! block, :id, :user_id, :page_id, :block_type, :text, :image_url, :image_caption, :checked, :expanded, :toggle_inner_text, :link_page_id, :format, :icon 4 | json.image_url url_for(block.photo) if (block.photo.attached?) 5 | end 6 | end -------------------------------------------------------------------------------- /app/views/api/blocks/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @block, :id, :user_id, :page_id, :block_type, :text, :image_url, :image_caption, :checked, :expanded, :toggle_inner_text, :link_page_id, :format, :icon 2 | json.image_url url_for(@block.photo) if (@block.photo.attached?) -------------------------------------------------------------------------------- /app/views/api/pages/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @pages.each do |page| 2 | json.set! page.id do 3 | json.extract! page, :id, :user_id, :title, :block_ids, :gallery_image_url, :uploaded_image_url, :icon, :created_at, :updated_at 4 | # json.uploaded_image_url url_for(@page.cover) if (@page.cover.attached?) 5 | end 6 | end -------------------------------------------------------------------------------- /app/views/api/pages/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @page, :id, :user_id, :title, :block_ids, :gallery_image_url, :uploaded_image_url, :icon, :created_at, :updated_at 2 | # json.uploaded_image_url url_for(@page.cover) if (@page.cover.attached?) -------------------------------------------------------------------------------- /app/views/api/users/_user.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! user, :id, :first_name, :last_name, :email 2 | -------------------------------------------------------------------------------- /app/views/api/users/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "api/users/user", user: @user 2 | -------------------------------------------------------------------------------- /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 | 2 |
React is broken
3 |
4 | <% if logged_in? %> 5 | <% end %> -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:setup' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /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 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /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 LilNotion 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.1 13 | 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /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 | channel_prefix: lilNotion_production 11 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | 37/10Wyp96MDb9mpsXGQe3R/xoZwpYnU5z6wCs1KeyPpbXqApBf+YpuCBUsL732pT11vqjLt1fqDSL0HVyuu78dM7hsooZTKdf9QIdNDBVF0NjpRcuep7SBh8h32TaADXvQKg4UYReMtkCt6Bsm4N2qvkSoJaVTTBJ5hH9G6oV9SjejBuGULifBfsf2yeuvKF+izl0EEiiwI7mqODUazdO8jsm0nIIAyQ1Mn7bLW/ik4EdlCJEb/8H3NcwEizuawB4dkcrnEev0XRRhUJenyyghT+Iw+nnv76ngJFmLawluusLKhldxMcdVDg8RoAHxLwQsQlMRO1iyq9ldmD3ORJjxdPUJhHtOy2ngswfArLABordIM2FQ33qnfhPtVrfaDEY9BBma69xX4qmhHqm2o8h8tgsPCGtgkoJ5XID6YFTWWLBLrbb8GSBUhopIotcZqxi0Wd9+pBRLdB9opXwJ//i1/+ewdgrBvYuC/EK99jh2pvWt6otoJykbFgERWaWWswDJwnWzKGPcxNOrL00HAPDg1k/oVW09LQIke4M3uRazTGtPx9VnVmYZMo/mTtCSadyEkDxVeR4UCVKnMnXu4eAWw5h8ih5k=--hOnRpPl6Zk67NbSx--K9/x5MM/S4l47pKoAyjS3A== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.1 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see Rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: lilNotion_development 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | #username: lilNotion 33 | 34 | # The password associated with the postgres role (username). 35 | #password: 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | #host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | #port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: lilNotion_test 61 | 62 | # As with config/secrets.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: lilNotion_production 84 | username: lilNotion 85 | password: <%= ENV['LILNOTION_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | 7 | Jbuilder.key_format camelize: :lower 8 | -------------------------------------------------------------------------------- /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 | 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=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | config.active_storage.service = :amazon_dev 30 | 31 | # Don't care if the mailer can't send. 32 | config.action_mailer.raise_delivery_errors = false 33 | 34 | config.action_mailer.perform_caching = false 35 | 36 | # Print deprecation notices to the Rails logger. 37 | config.active_support.deprecation = :log 38 | 39 | # Raise an error on page load if there are pending migrations. 40 | config.active_record.migration_error = false # :page_load 41 | 42 | # Debug mode disables concatenation and preprocessing of assets. 43 | # This option may cause significant delays in view rendering with a large 44 | # number of complex assets. 45 | config.assets.debug = true 46 | 47 | # Suppress logger output for asset requests. 48 | config.assets.quiet = true 49 | 50 | # Raises error for missing translations 51 | # config.action_view.raise_on_missing_translations = true 52 | 53 | # Use an evented file watcher to asynchronously detect changes in source code, 54 | # routes, locales, etc. This feature depends on the listen gem. 55 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 56 | end 57 | -------------------------------------------------------------------------------- /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=#{1.hour.seconds.to_i}" 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 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /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 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /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/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 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /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 } 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 | # If you are preloading your application and using Active Record, it's 36 | # recommended that you close any connections to the database before workers 37 | # are forked to prevent connection leakage. 38 | # 39 | # before_fork do 40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 41 | # end 42 | 43 | # The code in the `on_worker_boot` will be called if you are using 44 | # clustered mode by specifying a number of `workers`. After each worker 45 | # process is booted, this block will be run. If you are using the `preload_app!` 46 | # option, you will want to use this block to reconnect to any threads 47 | # or connections that may have been created at application boot, as Ruby 48 | # cannot share connections between processes. 49 | # 50 | # on_worker_boot do 51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 52 | # end 53 | # 54 | 55 | # Allow puma to be restarted by `rails restart` command. 56 | plugin :tmp_restart 57 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html 3 | 4 | namespace :api, defaults: { format: :json } do 5 | resources :users, only: [:create, :show, :update, :destroy] do 6 | resources :blocks, only: [:index] 7 | end 8 | resource :session, only: [:create, :destroy] 9 | resources :blocks, only: [:index, :create, :show, :update, :destroy] 10 | resources :pages, only: [:create, :index, :show, :update, :destroy] do 11 | resources :blocks, only: [:index] 12 | end 13 | end 14 | 15 | root to: "static_pages#root" 16 | end -------------------------------------------------------------------------------- /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 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: 8022d95ae33fa3397def9dcb5395e6b7aa1bfece6c2121dd4dc6466f1896716c65922ab01a991e279601e8994060c24cfdb7f6717df767c0a450426d2e5bbe05 22 | 23 | test: 24 | secret_key_base: 8512bdc86da18cd104b04ec84aa5b0eb596a66f600e209ac5b4d3854283dbbb753d511b796e09ccbff8d8b86b37e122a00db350db9e88fb9286cde8c6e9e5c6a 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | amazon_dev: 18 | service: S3 19 | access_key_id: <%= Rails.application.credentials.aws[:access_key_id] %> 20 | secret_access_key: 21 | <%= Rails.application.credentials.aws[:secret_access_key] %> 22 | region: <%= Rails.application.credentials.aws[:region] %> 23 | bucket: <%= Rails.application.credentials.aws[:dev][:bucket] %> 24 | 25 | amazon_prod: 26 | service: S3 27 | access_key_id: <%= Rails.application.credentials.aws[:access_key_id] %> 28 | secret_access_key: 29 | <%= Rails.application.credentials.aws[:secret_access_key] %> 30 | region: <%= Rails.application.credentials.aws[:region] %> 31 | bucket: <%= Rails.application.credentials.aws[:prod][:bucket] %> 32 | 33 | # Remember not to checkin your GCS keyfile to a repository 34 | # google: 35 | # service: GCS 36 | # project: your_project 37 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 38 | # bucket: your_own_bucket 39 | 40 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 41 | # microsoft: 42 | # service: AzureStorage 43 | # storage_account_name: your_account_name 44 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 45 | # container: your_container_name 46 | 47 | # mirror: 48 | # service: Mirror 49 | # primary: local 50 | # mirrors: [ amazon, google, microsoft ] 51 | -------------------------------------------------------------------------------- /db/migrate/20211209235153_enable_uuid.rb: -------------------------------------------------------------------------------- 1 | class EnableUuid < ActiveRecord::Migration[5.2] 2 | def change 3 | enable_extension 'pgcrypto' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20211209235259_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :users, id: :uuid do |t| 4 | t.string :email, null: false, unique: true, index: true 5 | t.string :first_name, null: false 6 | t.string :last_name, null: false 7 | t.string :password_digest, null: false 8 | t.string :session_token, null: false, unique: true, index: true 9 | t.timestamps 10 | end 11 | 12 | add_index :users, [:first_name, :last_name] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20211209235656_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 3 | def change 4 | create_table :active_storage_blobs do |t| 5 | t.string :key, null: false 6 | t.string :filename, null: false 7 | t.string :content_type 8 | t.text :metadata 9 | t.bigint :byte_size, null: false 10 | t.string :checksum, null: false 11 | t.datetime :created_at, null: false 12 | 13 | t.index [ :key ], unique: true 14 | end 15 | 16 | create_table :active_storage_attachments do |t| 17 | t.string :name, null: false 18 | t.references :record, null: false, polymorphic: true, index: false 19 | t.references :blob, null: false 20 | 21 | t.datetime :created_at, null: false 22 | 23 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true 24 | t.foreign_key :active_storage_blobs, column: :blob_id 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20211209235740_create_pages.rb: -------------------------------------------------------------------------------- 1 | class CreatePages < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :pages, id: :uuid do |t| 4 | t.uuid :user_id, null: false, index: true 5 | t.string :title, default: '', index: true 6 | t.string :block_ids, default: [], array: true, index: true 7 | t.string :gallery_image_url, default: '' 8 | t.string :uploaded_image_url, default: '' 9 | t.json :icon, default: {} 10 | t.json :style, default: {} 11 | t.timestamps 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20211210000613_create_blocks.rb: -------------------------------------------------------------------------------- 1 | class CreateBlocks < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :blocks, id: :uuid do |t| 4 | t.uuid :user_id, null: false, index: true 5 | t.uuid :page_id, null: false, index: true 6 | t.string :block_type, default: 'paragraph', null: false, index: true 7 | t.text :text, default: '', index: true 8 | t.string :image_url, default: '', index: true 9 | t.string :image_caption, default: '' 10 | t.boolean :checked, default: false 11 | t.boolean :expanded, default: false 12 | t.string :toggle_inner_text, default: '' 13 | t.string :link_page_id, default: '' 14 | t.jsonb :format, default: {} 15 | t.jsonb :icon, default: {} 16 | t.integer :order_index, default: 0 17 | t.timestamps 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20211210115758_add_more_indices.rb: -------------------------------------------------------------------------------- 1 | class AddMoreIndices < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index :blocks, :image_caption 4 | add_index :blocks, :checked 5 | add_index :blocks, :expanded 6 | add_index :blocks, :toggle_inner_text 7 | add_index :blocks, :link_page_id 8 | add_index :blocks, :format 9 | add_index :blocks, :icon 10 | add_index :blocks, :order_index 11 | add_index :blocks, :created_at 12 | add_index :blocks, :updated_at 13 | 14 | change_column :pages, :icon, :jsonb 15 | change_column :pages, :style, :jsonb 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /frontend/actions/block-actions.js: -------------------------------------------------------------------------------- 1 | import * as APIUtil from '../util/blocks-api-util'; 2 | 3 | export const RECEIVE_BLOCKS = 'RECEIVE_BLOCKS'; 4 | export const RECEIVE_BLOCK = 'RECEIVE_BLOCK'; 5 | export const REMOVE_BLOCK = 'REMOVE_BLOCK'; 6 | export const RECEIVE_BLOCK_ERRORS = 'RECEIVE_BLOCK_ERRORS'; 7 | export const REMOVE_ERRORS = 'REMOVE_ERRORS'; 8 | 9 | export const receiveBlocks = (blocks) => ({ 10 | type: RECEIVE_BLOCKS, 11 | blocks 12 | }); 13 | 14 | export const receiveBlock = (block) => ({ 15 | type: RECEIVE_BLOCK, 16 | block 17 | }); 18 | 19 | export const removeBlock = (blockId) => ({ 20 | type: REMOVE_BLOCK, 21 | blockId 22 | }); 23 | 24 | export const receiveErrors = (errors) => ({ 25 | type: RECEIVE_BLOCK_ERRORS, 26 | errors 27 | }); 28 | 29 | export const removeErrors = () => ({ 30 | type: REMOVE_ERRORS 31 | }); 32 | 33 | export const fetchBlocks = (userId) => (dispatch) => ( 34 | APIUtil.fetchBlocks(userId).then( 35 | (blocks) => dispatch(receiveBlocks(blocks)), 36 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 37 | ) 38 | ); 39 | 40 | export const createBlock = (block) => (dispatch) => ( 41 | APIUtil.createBlock(block).then( 42 | (block) => dispatch(receiveBlock(block)), 43 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 44 | ) 45 | ); 46 | 47 | export const fetchBlock = (blockId) => (dispatch) => ( 48 | APIUtil.fetchBlock(blockId).then( 49 | (block) => dispatch(receiveBlock(block)), 50 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 51 | ) 52 | ); 53 | 54 | export const updateBlock = (block) => (dispatch) => ( 55 | APIUtil.updateBlock(block).then( 56 | (block) => dispatch(receiveBlock(block)), 57 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 58 | ) 59 | ); 60 | 61 | export const deleteBlock = (blockId) => (dispatch) => ( 62 | APIUtil.deleteBlock(blockId).then( 63 | () => dispatch(removeBlock(blockId)), 64 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 65 | ) 66 | ); 67 | -------------------------------------------------------------------------------- /frontend/actions/page-actions.js: -------------------------------------------------------------------------------- 1 | import * as APIUtil from '../util/pages-api-util'; 2 | 3 | export const RECEIVE_PAGES = 'RECEIVE_PAGES'; 4 | export const RECEIVE_PAGE = 'RECEIVE_PAGE'; 5 | export const REMOVE_PAGE = 'REMOVE_PAGE'; 6 | export const RECEIVE_PAGE_ERRORS = 'RECEIVE_PAGE_ERRORS'; 7 | export const REMOVE_ERRORS = 'REMOVE_ERRORS'; 8 | 9 | export const receivePages = (pages) => ({ 10 | type: RECEIVE_PAGES, 11 | pages 12 | }); 13 | 14 | export const receivePage = (page) => ({ 15 | type: RECEIVE_PAGE, 16 | page 17 | }); 18 | 19 | export const removePage = (pageId) => ({ 20 | type: REMOVE_PAGE, 21 | pageId 22 | }); 23 | 24 | export const receiveErrors = (errors) => ({ 25 | type: RECEIVE_PAGE_ERRORS, 26 | errors 27 | }); 28 | 29 | export const removeErrors = () => ({ 30 | type: REMOVE_ERRORS 31 | }); 32 | 33 | export const fetchPages = (userId) => (dispatch) => ( 34 | APIUtil.fetchPages(userId).then( 35 | (pages) => dispatch(receivePages(pages)), 36 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 37 | ) 38 | ); 39 | 40 | export const createPage = (page) => (dispatch) => ( 41 | APIUtil.createPage(page).then( 42 | (page) => dispatch(receivePage(page)), 43 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 44 | ) 45 | ); 46 | 47 | export const fetchPage = (pageId) => (dispatch) => ( 48 | APIUtil.fetchPage(pageId).then( 49 | (page) => dispatch(receivePage(page)), 50 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 51 | ) 52 | ); 53 | 54 | export const updatePage = (page) => (dispatch) => ( 55 | APIUtil.updatePage(page).then( 56 | (page) => dispatch(receivePage(page)), 57 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 58 | ) 59 | ); 60 | 61 | export const deletePage = (pageId) => (dispatch) => ( 62 | APIUtil.deletePage(pageId).then( 63 | () => dispatch(removePage(pageId)), 64 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 65 | ) 66 | ); 67 | -------------------------------------------------------------------------------- /frontend/actions/session-actions.js: -------------------------------------------------------------------------------- 1 | import * as APIUtil from '../util/session-api-util'; 2 | 3 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER'; 4 | export const LOGOUT_CURRENT_USER = 'LOGOUT_CURRENT_USER'; 5 | export const RECEIVE_SESSION_ERRORS = 'RECEIVE_SESSION_ERRORS'; 6 | export const REMOVE_ERRORS = 'REMOVE_ERRORS'; 7 | 8 | export const receiveCurrentUser = (currentUser) => ({ 9 | type: RECEIVE_CURRENT_USER, 10 | currentUser 11 | }); 12 | 13 | export const logoutCurrentUser = () => ({ 14 | type: LOGOUT_CURRENT_USER, 15 | }); 16 | 17 | export const receiveErrors = (errors) => ({ 18 | type: RECEIVE_SESSION_ERRORS, 19 | errors 20 | }); 21 | 22 | export const removeErrors = () => ({ 23 | type: REMOVE_ERRORS 24 | }); 25 | 26 | export const signup = (user) => (dispatch) => ( 27 | APIUtil.signup(user).then( 28 | (user) => dispatch(receiveCurrentUser(user)), 29 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 30 | ) 31 | ); 32 | 33 | export const login = (user) => (dispatch) => ( 34 | APIUtil.login(user).then( 35 | (user) => dispatch(receiveCurrentUser(user)), 36 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 37 | ) 38 | ); 39 | 40 | export const logout = () => (dispatch) => ( 41 | APIUtil.logout().then( 42 | (user) => (dispatch(logoutCurrentUser())), 43 | (errors) => dispatch(receiveErrors(errors.responseJSON)) 44 | ) 45 | ); 46 | -------------------------------------------------------------------------------- /frontend/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Switch } from 'react-router-dom' 3 | import { AuthRoute, ProtectedRoute, HomeRoute } from '../util/route-util' 4 | import LoginFormContainer from './auth/LoginFormContainer' 5 | import SignupFormContainer from './auth/SignupFormContainer' 6 | import EditorContainer from './editor/EditorContainer' 7 | import PageNotFound from './PageNotFound' 8 | 9 | function App() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | 26 | export default App 27 | -------------------------------------------------------------------------------- /frontend/components/PageNotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { Link } from 'react-router-dom' 4 | import NavBarContainer from './navbar/NavBarContainer' 5 | 6 | const PageNotFound = () => { 7 | return ( 8 | <> 9 | 10 |
11 |
12 |
13 |

404: Page Not Found

14 | 15 | Home Page 16 | 17 |
18 |
19 |
20 | 21 | ) 22 | } 23 | 24 | const mapStateToProps = (state) => ({ 25 | currentUser: state.entities.users[state.session.currentUserId], 26 | }) 27 | 28 | const mapDispatchToProps = (dispatch) => ({ 29 | logout: () => dispatch(logout()), 30 | }) 31 | 32 | export default connect(mapStateToProps, mapDispatchToProps)(PageNotFound) 33 | -------------------------------------------------------------------------------- /frontend/components/Root.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import { HashRouter } from 'react-router-dom' 4 | import App from './App' 5 | 6 | const Root = ({ store }) => ( 7 | 8 | 9 | 10 | 11 | 12 | ) 13 | 14 | export default Root 15 | -------------------------------------------------------------------------------- /frontend/components/auth/LoginFormContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { login, removeErrors } from '../../actions/session-actions'; 3 | import LoginForm from './LoginForm'; 4 | 5 | const mapStateToProps = (state) => ({ 6 | errors: state.errors.session, 7 | }); 8 | 9 | const mapDispatchToProps = (dispatch) => ({ 10 | login: (user) => dispatch(login(user)), 11 | removeErrors: () => dispatch(removeErrors()), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(LoginForm); 15 | -------------------------------------------------------------------------------- /frontend/components/auth/SignupFormContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { signup, login, receiveErrors, removeErrors } from '../../actions/session-actions'; 3 | import { createPage } from '../../actions/page-actions'; 4 | import { createBlock } from '../../actions/block-actions'; 5 | import SignupForm from './SignupForm'; 6 | 7 | const mapStateToProps = (state, ownProps) => ({ 8 | errors: state.errors.session, 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => { 12 | return { 13 | signup: (user) => dispatch(signup(user)), 14 | loginDemo: (user) => dispatch(login(user)), 15 | createPage: (page) => dispatch(createPage(page)), 16 | createBlock: (block) => dispatch(createBlock(block)), 17 | receiveErrors: (errors) => dispatch(receiveErrors(errors)), 18 | removeErrors: () => dispatch(removeErrors()), 19 | }; 20 | }; 21 | 22 | export default connect(mapStateToProps, mapDispatchToProps)(SignupForm); 23 | -------------------------------------------------------------------------------- /frontend/components/blocks/BlockContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Block from './Block'; 3 | import { 4 | fetchBlock, 5 | fetchBlocks, 6 | createBlock, 7 | updateBlock, 8 | deleteBlock, 9 | } from '../../actions/block-actions'; 10 | import { 11 | fetchPage, 12 | createPage, 13 | updatePage, 14 | deletePage, 15 | } from '../../actions/page-actions'; 16 | 17 | const mapStateToProps = (state, ownProps) => ({ 18 | currentUser: state.entities.users[state.session.currentUserId], 19 | block: ownProps.block, 20 | }); 21 | 22 | const mapDispatchToProps = (dispatch, ownProps) => ({ 23 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 24 | fetchBlocks: (userId) => dispatch(fetchBlocks(userId)), 25 | createBlock: (block) => dispatch(createBlock(block)), 26 | updateBlock: (block) => dispatch(updateBlock(block)), 27 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 28 | updatePage: (page) => dispatch(updatePage(page)), 29 | }); 30 | 31 | export default connect(mapStateToProps, mapDispatchToProps)(Block); 32 | -------------------------------------------------------------------------------- /frontend/components/blocks/BulletedList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class BulletedList extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 |
28 |
29 | 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default BulletedList; 43 | -------------------------------------------------------------------------------- /frontend/components/blocks/BulletedListContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import BulletedList from './BulletedList'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(BulletedList); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Callout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class Callout extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 | {/* add callout styling */} 28 | 36 |
37 | ); 38 | } 39 | } 40 | 41 | export default Callout; 42 | -------------------------------------------------------------------------------- /frontend/components/blocks/CalloutContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Callout from './Callout'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Callout); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Code.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class Code extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 |
28 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | export default Code; 44 | -------------------------------------------------------------------------------- /frontend/components/blocks/CodeContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Code from './Code'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Code); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Divider.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Divider extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | render() { 9 | return ( 10 |
11 |
12 |
13 |
14 |
15 | ); 16 | } 17 | } 18 | 19 | export default Divider; 20 | -------------------------------------------------------------------------------- /frontend/components/blocks/DividerContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Divider from './Divider'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Divider); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/DragHandle.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const DragHandle = (props) => { 4 | // calculate responsive tooltip position 5 | const [show, setShow] = useState(false); 6 | 7 | return ( 8 |
setShow(false)} 10 | onClick={props.dragHandleClick} 11 | {...props.dragHandleProps} 12 | className={props.hover ? 'drag-handle visible' : 'drag-handle'} 13 | role="button" 14 | tabIndex="0" 15 | onMouseEnter={() => setShow(true)} 16 | onMouseLeave={() => setShow(false)} 17 | > 18 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {/*
37 |
38 | Drag to move 39 |
40 | Click to open menu 41 |
42 |
*/} 43 |
44 | ); 45 | }; 46 | 47 | export default DragHandle; 48 | -------------------------------------------------------------------------------- /frontend/components/blocks/Heading1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import { debounce } from '../../util/utils'; 4 | 5 | class Heading1 extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.contentEditable = React.createRef(); 9 | this.handleChange = this.handleChange.bind(this); 10 | 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 | 35 |
36 | ); 37 | } 38 | } 39 | 40 | export default Heading1; 41 | -------------------------------------------------------------------------------- /frontend/components/blocks/Heading1Container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Heading1 from './Heading1'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Heading1); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Heading2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class Heading2 extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 | 35 |
36 | ); 37 | } 38 | } 39 | 40 | export default Heading2; 41 | -------------------------------------------------------------------------------- /frontend/components/blocks/Heading2Container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Heading2 from './Heading2'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Heading2); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Heading3.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class Heading3 extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 | 35 |
36 | ); 37 | } 38 | } 39 | 40 | export default Heading3; 41 | -------------------------------------------------------------------------------- /frontend/components/blocks/Heading3Container.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Heading3 from './Heading3'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Heading3); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Image.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { BiImage } from 'react-icons/bi' 3 | 4 | function Image({ block, updateBlock }) { 5 | const [photoUrl, setPhotoUrl] = useState(null) 6 | 7 | function handleUpload(e) { 8 | const file = e.target.files[0] 9 | if (file) { 10 | const fileReader = new FileReader() 11 | fileReader.readAsDataURL(file) 12 | 13 | fileReader.onloadend = () => { 14 | const formData = new FormData() 15 | formData.append('block[imageUrl]', file) 16 | 17 | $.ajax({ 18 | url: `/api/blocks/${block.id}`, 19 | method: 'PATCH', 20 | data: formData, 21 | contentType: false, 22 | processData: false, 23 | }).then( 24 | (res) => updateBlock(res), 25 | (err) => console.log('error: ', err) 26 | ) 27 | } 28 | } 29 | } 30 | 31 | const isImageUploaded = block.imageUrl && block.imageUrl.length > 0 32 | const imageBody = isImageUploaded ? ( 33 | 34 | ) : ( 35 | 46 | ) 47 | 48 | return ( 49 |
50 |
{imageBody}
51 |
52 | ) 53 | } 54 | 55 | export default Image 56 | -------------------------------------------------------------------------------- /frontend/components/blocks/ImageContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Image from './Image'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Image); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Link.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Link extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.contentEditable = React.createRef(); 7 | this.handleChange = this.handleChange.bind(this); 8 | 9 | this.state = { 10 | placeholder: '', 11 | }; 12 | } 13 | 14 | render() { 15 | const linkBody = null; 16 | 17 | // open menu to choose page 18 | return ( 19 |
20 | {linkBody} 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default Link; 27 | -------------------------------------------------------------------------------- /frontend/components/blocks/LinkContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Link from './Link'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Link); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/NumberedList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class NumberedList extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 |
28 | 36 |
37 | ); 38 | } 39 | } 40 | 41 | export default NumberedList; 42 | -------------------------------------------------------------------------------- /frontend/components/blocks/NumberedListContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import NumberedList from './NumberedList'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(NumberedList); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Paragraph.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce, pasteAsPlainText } from '../../util/utils'; 5 | import BlockSlashMenu from '../menus/BlockSlashMenu'; 6 | 7 | class Paragraph extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.contentEditable = React.createRef(); 11 | this.handleChange = this.handleChange.bind(this); 12 | this.handleKeyDown = this.handleKeyDown.bind(this); 13 | this.handleFocus = this.handleFocus.bind(this); 14 | this.handleBlur = this.handleBlur.bind(this); 15 | this.state = { 16 | html: this.props.block.text, 17 | placeholder: '', 18 | }; 19 | this.keysPressed = {}; 20 | } 21 | 22 | handleChange(e) { 23 | this.setState({ html: e.target.value }, () => { 24 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 25 | this.props.updateBlock(newBlock); 26 | }); 27 | } 28 | 29 | handleKeyDown(e) { 30 | if (e.key === '/') { 31 | e.preventDefault(); 32 | // console.log('/ pressed'); 33 | // open slash command menu 34 | 35 | } else if (e.key === 'Backspace' && !this.state.html) { 36 | this.props.deleteBlock(this.props.block.id); 37 | } else if (e.key === 'Enter') { 38 | e.preventDefault(); 39 | // console.log('break up this block'); 40 | // this.contentEditable.current.blur(); 41 | 42 | } 43 | } 44 | 45 | handleFocus(e) {} 46 | 47 | handleBlur(e) {} 48 | 49 | render() { 50 | // sanitizeHtml example: https://codesandbox.io/s/simple-rich-text-editor-in-react-forked-295gc 51 | // const html = 'hello world'; 52 | // console.log(sanitizeHtml(html)); 53 | // console.log(sanitizeHtml("").length); 54 | // console.log(sanitizeHtml("console.log('hello world')")); 55 | // console.log(sanitizeHtml("").length); 56 | 57 | return ( 58 |
59 | 71 |
72 | ); 73 | } 74 | } 75 | 76 | export default Paragraph; 77 | -------------------------------------------------------------------------------- /frontend/components/blocks/ParagraphContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Paragraph from './Paragraph'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | }); 12 | 13 | export default connect(mapStateToProps, mapDispatchToProps)(Paragraph); 14 | -------------------------------------------------------------------------------- /frontend/components/blocks/PlusHandle.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const PlusHandle = (props) => { 4 | // calculate responsive tooltip position 5 | const [show, setShow] = useState(false); 6 | 7 | return ( 8 |
setShow(true)} 14 | onMouseLeave={() => setShow(false)} 15 | > 16 | 27 | 28 | 29 | 30 | {/*
31 |
32 | Click to add a block below 33 |
34 |
*/} 35 |
36 | ); 37 | }; 38 | 39 | export default PlusHandle; 40 | -------------------------------------------------------------------------------- /frontend/components/blocks/Quote.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class Quote extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | placeholder: '', 14 | }; 15 | } 16 | 17 | handleChange(e) { 18 | this.setState({ html: e.target.value }, () => { 19 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 20 | this.props.updateBlock(newBlock); 21 | }); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 |
28 | 36 |
37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Quote; 43 | -------------------------------------------------------------------------------- /frontend/components/blocks/QuoteContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Quote from './Quote'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Quote); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/ToDo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class ToDo extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.toggleChecked = this.toggleChecked.bind(this); 11 | this.handleChange = this.handleChange.bind(this); 12 | this.state = { 13 | html: this.props.block.text, 14 | checked: this.props.block.checked, 15 | placeholder: '', 16 | }; 17 | } 18 | 19 | toggleChecked() { 20 | this.setState({ checked: !this.state.checked }, () => { 21 | const newBlock = Object.assign(this.props.block, { checked: this.state.checked }); 22 | this.props.updateBlock(newBlock); 23 | }); 24 | } 25 | 26 | handleChange(e) { 27 | this.setState({ html: e.target.value }, () => { 28 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 29 | this.props.updateBlock(newBlock); 30 | }); 31 | } 32 | 33 | render() { 34 | const checkbox = ( 35 |
36 | 47 | 48 | 49 |
50 | ); 51 | 52 | const check = ( 53 |
54 | 65 | 66 | 67 |
68 | ); 69 | 70 | return ( 71 |
72 |
73 |
{this.state.checked ? check : checkbox}
74 |
75 | 83 |
84 | ); 85 | } 86 | } 87 | 88 | export default ToDo; 89 | -------------------------------------------------------------------------------- /frontend/components/blocks/ToDoContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import ToDo from './ToDo'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(ToDo); 15 | -------------------------------------------------------------------------------- /frontend/components/blocks/Toggle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentEditable from 'react-contenteditable'; 3 | import sanitizeHtml from 'sanitize-html'; 4 | import { debounce } from '../../util/utils'; 5 | 6 | class Toggle extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.contentEditable = React.createRef(); 10 | this.handleChange = this.handleChange.bind(this); 11 | this.state = { 12 | html: this.props.block.text, 13 | checked: this.props.block.checked, 14 | expanded: this.props.block.expanded, 15 | // toggleInnerText 16 | placeholder: '', 17 | }; 18 | } 19 | 20 | handleChange(e) { 21 | this.setState({ html: e.target.value }, () => { 22 | const newBlock = Object.assign(this.props.block, { text: this.state.html }); 23 | this.props.updateBlock(newBlock); 24 | }); 25 | } 26 | 27 | render() { 28 | return ( 29 |
30 | {/* add input or custom checkbox */} 31 | 39 | {/* innerPlaceholder="Empty toggle. Click or drag blocks inside" */} 40 | {/* innerPlaceholder2="Empty toggle. Click to start writing inside" */} 41 |
42 | ); 43 | } 44 | } 45 | 46 | export default Toggle; 47 | -------------------------------------------------------------------------------- /frontend/components/blocks/ToggleContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchBlock, createBlock, updateBlock, deleteBlock } from '../../actions/block-actions'; 3 | import Toggle from './Toggle'; 4 | 5 | const mapStateToProps = (state) => ({}); 6 | 7 | const mapDispatchToProps = (dispatch, ownProps) => ({ 8 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 9 | createBlock: (block) => dispatch(createBlock(block)), 10 | updateBlock: (block) => dispatch(updateBlock(block)), 11 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 12 | }); 13 | 14 | export default connect(mapStateToProps, mapDispatchToProps)(Toggle); 15 | -------------------------------------------------------------------------------- /frontend/components/editor/Editor.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import SidebarContainer from '../sidebar/SidebarContainer' 3 | import PageContainer from '../page/PageContainer' 4 | import Loader from './Loader' 5 | 6 | function Editor({ currentUser, pages, blocks, fetchPages, fetchBlocks }) { 7 | const [isSidebarOpen, setIsSidebarOpen] = useState(true) 8 | 9 | useEffect(() => { 10 | fetchPages(currentUser.id) 11 | fetchBlocks(currentUser.id) 12 | }, []) 13 | 14 | const toggleSidebar = () => { 15 | setIsSidebarOpen(!isSidebarOpen) 16 | console.log('toggleSidebar()') 17 | } 18 | 19 | const isDataValid = 20 | pages && 21 | blocks && 22 | Object.keys(pages).length > 0 && 23 | Object.keys(blocks).length > 0 && 24 | Object.values(blocks)[0].id 25 | 26 | return isDataValid ? ( 27 |
28 | 29 | 35 |
36 | ) : ( 37 | 38 | ) 39 | } 40 | 41 | export default Editor 42 | -------------------------------------------------------------------------------- /frontend/components/editor/EditorContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { withRouter } from 'react-router-dom' 3 | import { fetchPages } from '../../actions/page-actions' 4 | import { fetchBlocks } from '../../actions/block-actions' 5 | import Editor from './Editor' 6 | 7 | const mapStateToProps = (state, ownProps) => ({ 8 | errors: state.errors.session, 9 | currentUser: state.entities.users[state.session.currentUserId], 10 | pages: state.entities.pages, 11 | blocks: state.entities.blocks, 12 | }) 13 | 14 | const mapDispatchToProps = (dispatch, ownProps) => ({ 15 | fetchPages: (userId) => dispatch(fetchPages(userId)), 16 | fetchBlocks: (userId) => dispatch(fetchBlocks(userId)), 17 | }) 18 | 19 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Editor)) 20 | -------------------------------------------------------------------------------- /frontend/components/editor/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loader = () => { 4 | return ( 5 |
6 |
Loading...
7 |
8 | ); 9 | }; 10 | 11 | export default Loader; 12 | -------------------------------------------------------------------------------- /frontend/components/menus/BlockActionMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { FiRepeat, FiTrash2, } from 'react-icons/fi'; 3 | import { AiOutlineCaretRight, AiOutlineFormatPainter } from 'react-icons/ai'; 4 | import { BiPaintRoll } from 'react-icons/bi'; 5 | 6 | const BlockActionMenu = ({ 7 | position, 8 | turnInto, 9 | openSelectMenu, 10 | closeSelectMenu, 11 | deleteBlock, 12 | toggleMouseOverTurnInto, 13 | }) => { 14 | const { x, y } = position; 15 | // console.log(position); 16 | 17 | return ( 18 |
19 |
20 |
Actions
21 | {/* filter actions typeahead text input */} 22 | {/* close select menu if */} 23 |
toggleMouseOverTurnInto()} 27 | // onMouseLeave={() => toggleMouseOverTurnInto()} 28 | role="button" 29 | tabIndex="0" 30 | > 31 |
32 | 33 |
34 |
Turn into
35 |
36 | 37 |
38 |
39 | 40 |
47 |
48 | 49 |
50 |
Delete
51 |
Del
52 |
53 | 54 |
59 |
60 | 61 |
62 |
Color
63 |
64 | 65 |
66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default BlockActionMenu; 73 | -------------------------------------------------------------------------------- /frontend/components/menus/BlockActionMenuContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { withRouter } from 'react-router-dom'; 3 | import BlockActionMenu from './BlockActionMenu'; 4 | import { 5 | createBlock, 6 | fetchBlock, 7 | updateBlock, 8 | deleteBlock, 9 | } from '../../actions/block-actions'; 10 | 11 | const mapStateToProps = (state, ownProps) => ({ 12 | errors: state.errors.session, 13 | blocks: state.entities.blocks, 14 | }); 15 | 16 | const mapDispatchToProps = (dispatch, ownProps) => ({ 17 | createBlock: (block) => dispatch(createBlock(block)), 18 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 19 | updateBlock: (block) => dispatch(updateBlock(block)), 20 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 21 | }); 22 | 23 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BlockActionMenu)); 24 | -------------------------------------------------------------------------------- /frontend/components/menus/BlockSelectMenuContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { withRouter } from 'react-router-dom'; 3 | import BlockSelectMenu from './BlockSelectMenu'; 4 | import { 5 | createBlock, 6 | fetchBlock, 7 | updateBlock, 8 | deleteBlock, 9 | } from '../../actions/block-actions'; 10 | 11 | const mapStateToProps = (state, ownProps) => ({ 12 | errors: state.errors.session, 13 | blocks: state.entities.blocks, 14 | }); 15 | 16 | const mapDispatchToProps = (dispatch, ownProps) => ({ 17 | createBlock: (block) => dispatch(createBlock(block)), 18 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 19 | updateBlock: (block) => dispatch(updateBlock(block)), 20 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 21 | }); 22 | 23 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BlockSelectMenu)); 24 | -------------------------------------------------------------------------------- /frontend/components/menus/BlockSlashMenuContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { withRouter } from 'react-router-dom'; 3 | import BlockSlashMenu from './BlockSlashMenu'; 4 | import { 5 | createBlock, 6 | fetchBlock, 7 | updateBlock, 8 | deleteBlock, 9 | } from '../../actions/block-actions'; 10 | 11 | const mapStateToProps = (state, ownProps) => ({ 12 | errors: state.errors.session, 13 | blocks: state.entities.blocks, 14 | }); 15 | 16 | const mapDispatchToProps = (dispatch, ownProps) => ({ 17 | createBlock: (block) => dispatch(createBlock(block)), 18 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 19 | updateBlock: (block) => dispatch(updateBlock(block)), 20 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 21 | }); 22 | 23 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BlockSlashMenu)); 24 | -------------------------------------------------------------------------------- /frontend/components/menus/RadixSelectMenu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SelectMenuRow from './SelectMenuRow'; 3 | import * as Tooltip from '@radix-ui/react-tooltip'; 4 | 5 | const RadixSelectMenu = () => { 6 | const menuData = [ 7 | { 8 | name: 'Text', 9 | description: 'Just start writing with plain text.', 10 | blockType: 'paragraph', 11 | thumbnail: window.text, 12 | tooltip: window.textTooltip, 13 | }, 14 | { 15 | name: 'Heading 1', 16 | description: 'Big section heading.', 17 | blockType: 'h1', 18 | thumbnail: window.h1, 19 | tooltip: window.h1Tooltip, 20 | }, 21 | { 22 | name: 'Heading 2', 23 | description: 'Medium section heading.', 24 | blockType: 'h2', 25 | thumbnail: window.h2, 26 | tooltip: window.h2Tooltip, 27 | }, 28 | { 29 | name: 'Heading 3', 30 | description: 'Small section heading.', 31 | blockType: 'h3', 32 | thumbnail: window.h3, 33 | tooltip: window.h3Tooltip, 34 | }, 35 | { 36 | name: 'To-do list', 37 | description: 'Track tasks with a to-do list.', 38 | blockType: 'todo', 39 | thumbnail: window.toDo, 40 | tooltip: window.toDoTooltip, 41 | }, 42 | { 43 | name: 'Bulleted list', 44 | description: 'Create a simple bulleted list.', 45 | blockType: 'bulletedList', 46 | thumbnail: window.bulletedList, 47 | tooltip: window.bulletedListTooltip, 48 | }, 49 | { 50 | name: 'Numbered list', 51 | description: 'Create a list with numbering.', 52 | blockType: 'numberedList', 53 | thumbnail: window.numberedList, 54 | tooltip: window.numberedListTooltip, 55 | }, 56 | // { 57 | // name: 'Toggle list', 58 | // description: 'Toggles can hide and show content inside.', 59 | // blockType: 'toggle', 60 | // thumbnail: window.toggle, 61 | // tooltip: window.toggleTooltip, 62 | // }, 63 | // { 64 | // name: 'Code', 65 | // description: 'Capture a code snippet.', 66 | // blockType: 'code', 67 | // thumbnail: window.code, 68 | // tooltip: window.codeTooltip, 69 | // }, 70 | { 71 | name: 'Quote', 72 | description: 'Capture a quote.', 73 | blockType: 'quote', 74 | thumbnail: window.quote, 75 | tooltip: window.quoteTooltip, 76 | }, 77 | { 78 | name: 'Callout', 79 | description: 'Make writing stand out.', 80 | blockType: 'callout', 81 | thumbnail: window.callout, 82 | tooltip: window.calloutTooltip, 83 | }, 84 | { 85 | name: 'Divider', 86 | description: 'Visually divide blocks.', 87 | blockType: 'divider', 88 | thumbnail: window.divider, 89 | tooltip: window.dividerTooltip, 90 | }, 91 | { 92 | name: 'Image', 93 | description: 'Upload or embed with a link.', 94 | blockType: 'image', 95 | thumbnail: window.image, 96 | tooltip: window.imageTooltip, 97 | }, 98 | ]; 99 | 100 | return ( 101 |
104 |
105 | {menuData.map((item, i) => ( 106 | 113 | ))} 114 |
115 |
116 | ); 117 | }; 118 | 119 | export default RadixSelectMenu; 120 | -------------------------------------------------------------------------------- /frontend/components/menus/SelectMenuRow.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const SelectMenuRow = ({ item, position, handleBlockSelect, block }) => { 4 | // calculate responsive tooltip position 5 | const { tooltipX, tooltipY } = position; 6 | const [show, setShow] = useState(false); 7 | 8 | return ( 9 |
setShow(true)} 12 | onMouseLeave={() => setShow(false)} 13 | onClick={() => handleBlockSelect(item.blockType, block)} 14 | > 15 |
16 | 17 |
18 |
19 |
{item.name}
20 |
21 |
22 |
23 | 24 |
{item.description}
25 |
26 |
27 |
28 | ); 29 | }; 30 | 31 | export default SelectMenuRow; 32 | -------------------------------------------------------------------------------- /frontend/components/menus/SlashMenuRow.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const SlashMenuRow = ({ item, handleBlockSelect, block }) => { 4 | // calculate responsive tooltip position 5 | const { tooltipX, tooltipY } = position; 6 | const [show, setShow] = useState(false); 7 | 8 | return ( 9 |
setShow(true)} 12 | onMouseLeave={() => setShow(false)} 13 | onClick={() => handleBlockSelect(item.blockType, block)} 14 | > 15 |
16 | 17 |
18 |
19 |
{item.name}
20 |
{item.description}
21 |
22 |
23 |
24 | 25 |
{item.description}
26 |
27 |
28 |
29 | ); 30 | }; 31 | 32 | export default SlashMenuRow; 33 | -------------------------------------------------------------------------------- /frontend/components/navbar/AuthNavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const AuthNavbar = (props) => { 5 | return ( 6 |
7 |
8 |
9 |
10 | 11 | lilNotion logo 16 | 17 |
18 |
19 |
20 |
21 | ); 22 | }; 23 | 24 | export default AuthNavbar; 25 | -------------------------------------------------------------------------------- /frontend/components/navbar/AuthNavBarContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { logout } from '../../actions/session-actions'; 3 | import AuthNavbar from './AuthNavBar'; 4 | 5 | const mapStateToProps = (state) => ({ 6 | currentUser: state.entities.users[state.session.currentUserId], 7 | }); 8 | 9 | const mapDispatchToProps = (dispatch) => ({ 10 | logout: () => dispatch(logout()), 11 | }); 12 | 13 | export default connect(mapStateToProps, mapDispatchToProps)(AuthNavbar); 14 | -------------------------------------------------------------------------------- /frontend/components/navbar/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const Navbar = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 | 11 | lilNotion logo 12 | 13 |
14 | 15 |
16 | 63 |
64 |
65 |
66 |
67 | ); 68 | }; 69 | 70 | export default Navbar; 71 | -------------------------------------------------------------------------------- /frontend/components/navbar/NavBarContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { logout } from '../../actions/session-actions'; 3 | import Navbar from './NavBar'; 4 | 5 | const mapStateToProps = (state) => ({ 6 | currentUser: state.entities.users[state.session.currentUserId], 7 | }); 8 | 9 | const mapDispatchToProps = (dispatch) => ({ 10 | logout: () => dispatch(logout()), 11 | }); 12 | 13 | export default connect(mapStateToProps, mapDispatchToProps)(Navbar); 14 | -------------------------------------------------------------------------------- /frontend/components/page/PageContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { withRouter } from 'react-router-dom' 3 | import { fetchPage, createPage, updatePage, deletePage } from '../../actions/page-actions' 4 | import { 5 | createBlock, 6 | fetchBlock, 7 | fetchBlocks, 8 | updateBlock, 9 | deleteBlock, 10 | } from '../../actions/block-actions' 11 | import Page from './Page' 12 | 13 | const mapStateToProps = (state, ownProps) => ({ 14 | errors: state.errors.session, 15 | currentUser: state.entities.users[state.session.currentUserId], 16 | pages: ownProps.pages, 17 | blocks: ownProps.blocks, 18 | // pages: state.entities.pages, 19 | // blocks: state.entities.blocks, 20 | // page: state.entities.pages[ownProps.location.pathname.slice(3)], 21 | // pageId: ownProps.location.pathname.slice(3), 22 | }) 23 | 24 | const mapDispatchToProps = (dispatch, ownProps) => ({ 25 | fetchPage: (pageId) => dispatch(fetchPage(pageId)), 26 | createPage: (page) => dispatch(createPage(page)), 27 | updatePage: (page) => dispatch(updatePage(page)), 28 | deletePage: (pageId) => dispatch(deletePage(pageId)), 29 | createBlock: (block) => dispatch(createBlock(block)), 30 | fetchBlock: (blockId) => dispatch(fetchBlock(blockId)), 31 | fetchBlocks: (userId) => dispatch(fetchBlocks(userId)), 32 | updateBlock: (block) => dispatch(updateBlock(block)), 33 | deleteBlock: (blockId) => dispatch(deleteBlock(blockId)), 34 | }) 35 | 36 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Page)) 37 | -------------------------------------------------------------------------------- /frontend/components/sidebar/OutlinerMenu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { FiTrash2, FiEdit } from 'react-icons/fi'; 4 | 5 | export default function OutlinerMenu({ position, deletePage }) { 6 | const [thing, setThing] = React.useState(null); 7 | 8 | return ReactDOM.createPortal( 9 | <> 10 |
deletePage(page.id)} 13 | role="button" 14 | tabIndex="0" 15 | style={{left: position.x, top: position.y}} 16 | > 17 |
18 |
19 | 20 |
21 |
Delete
22 |
23 |
24 | , 25 | document.getElementById('portal') 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /frontend/components/sidebar/OutlinerRow.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { withRouter, useParams } from 'react-router-dom' 3 | import { Emoji } from 'emoji-mart' 4 | import { FiTrash2 } from 'react-icons/fi' 5 | import * as DropdownMenu from '@radix-ui/react-dropdown-menu' 6 | import * as Tooltip from '@radix-ui/react-tooltip' 7 | 8 | function OutlinerRow({ page, goToPage, handleDeletePage }) { 9 | const [hover, setHover] = useState(false) 10 | const [showTooltip, setShowTooltip] = useState(false) 11 | const { id } = useParams() 12 | const pageId = page.id 13 | const isActive = pageId === id 14 | const pageTitle = page.title.length > 0 ? page.title : 'Untitled' 15 | 16 | return ( 17 |
setHover(true)} 20 | onMouseLeave={() => setHover(false)} 21 | onClick={() => goToPage(page.id, page.title)} 22 | > 23 | 24 |
25 | {/* caret to show nested pages */} 26 | {/*
*/} 27 |
28 | 29 |
30 | 31 |
{pageTitle}
32 | 33 | 34 |
setShowTooltip(true)} 37 | onMouseLeave={() => setShowTooltip(false)} 38 | > 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
48 | 49 | 50 | handleDeletePage(pageId)} 53 | > 54 |
55 | 56 |
57 |
Delete
58 |
59 |
60 |
61 |
62 |
63 | ) 64 | } 65 | 66 | export default withRouter(OutlinerRow) 67 | -------------------------------------------------------------------------------- /frontend/components/sidebar/SidebarContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { withRouter } from 'react-router-dom' 3 | import Sidebar from './Sidebar' 4 | import { 5 | createPage, 6 | fetchPage, 7 | fetchPages, 8 | updatePage, 9 | deletePage, 10 | } from '../../actions/page-actions' 11 | import { createBlock } from '../../actions/block-actions' 12 | import { logout } from '../../actions/session-actions' 13 | 14 | const mapStateToProps = (state, ownProps) => ({ 15 | errors: state.errors.session, 16 | currentUser: state.entities.users[state.session.currentUserId], 17 | }) 18 | 19 | const mapDispatchToProps = (dispatch, ownProps) => ({ 20 | logout: () => dispatch(logout()), 21 | createPage: (page) => dispatch(createPage(page)), 22 | fetchPage: (pageId) => dispatch(fetchPage(pageId)), 23 | fetchPages: (userId) => dispatch(fetchPages(userId)), 24 | updatePage: (page) => dispatch(updatePage(page)), 25 | deletePage: (pageId) => dispatch(deletePage(pageId)), 26 | createBlock: (block) => dispatch(createBlock(block)), 27 | }) 28 | 29 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Sidebar)) 30 | -------------------------------------------------------------------------------- /frontend/entry.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Root from './components/Root' 4 | import configureStore from './store/store' 5 | 6 | document.addEventListener('DOMContentLoaded', () => { 7 | let store 8 | if (window.currentUser) { 9 | const { currentUser } = window 10 | const preloadedState = { 11 | entities: { 12 | users: { 13 | [currentUser.id]: currentUser, 14 | }, 15 | }, 16 | session: { 17 | currentUserId: currentUser.id, 18 | }, 19 | } 20 | store = configureStore(preloadedState) 21 | delete window.currentUser 22 | } else { 23 | store = configureStore() 24 | } 25 | const elem = document.getElementById('userScript') 26 | if (elem) document.body.removeChild(elem) 27 | 28 | const root = document.getElementById('root') 29 | ReactDOM.render(, root) 30 | }) 31 | -------------------------------------------------------------------------------- /frontend/reducers/block-errors-reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_BLOCK_ERRORS } from '../actions/block-actions'; 2 | 3 | const blockErrorsReducer = (state = [], action) => { 4 | Object.freeze(state); 5 | switch (action.type) { 6 | case RECEIVE_BLOCK_ERRORS: 7 | // temp fix 8 | if (action.errors) { 9 | return action.errors 10 | } else { 11 | return state; 12 | } 13 | default: 14 | return state; 15 | } 16 | }; 17 | 18 | export default blockErrorsReducer; 19 | -------------------------------------------------------------------------------- /frontend/reducers/blocks-reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_BLOCKS, RECEIVE_BLOCK, REMOVE_BLOCK } from '../actions/block-actions'; 2 | 3 | const blocksReducer = (state = {}, action) => { 4 | Object.freeze(state); 5 | switch (action.type) { 6 | case RECEIVE_BLOCKS: 7 | return Object.assign({}, state, action.blocks); 8 | case RECEIVE_BLOCK: 9 | return Object.assign({}, state, { [action.block.id]: action.block }); 10 | case REMOVE_BLOCK: 11 | let newState = Object.assign({}, state); 12 | delete newState[action.blockId]; 13 | return newState; 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | export default blocksReducer; 20 | -------------------------------------------------------------------------------- /frontend/reducers/entities-reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import usersReducer from './users-reducer'; 3 | import blocksReducer from './blocks-reducer'; 4 | import pagesReducer from './pages-reducer'; 5 | 6 | export default combineReducers({ 7 | users: usersReducer, 8 | blocks: blocksReducer, 9 | pages: pagesReducer 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/reducers/errors-reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import sessionErrorsReducer from './session-errors-reducer'; 3 | import blockErrorsReducer from './block-errors-reducer'; 4 | import pageErrorsReducer from './page-errors-reducer'; 5 | 6 | export default combineReducers({ 7 | session: sessionErrorsReducer, 8 | blocks: blockErrorsReducer, 9 | pages: pageErrorsReducer 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/reducers/page-errors-reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_PAGE_ERRORS } from '../actions/page-actions'; 2 | 3 | const pageErrorsReducer = (state = [], action) => { 4 | Object.freeze(state); 5 | switch (action.type) { 6 | case RECEIVE_PAGE_ERRORS: 7 | // temp fix 8 | if (action.errors) { 9 | return action.errors 10 | } else { 11 | return state; 12 | } 13 | default: 14 | return state; 15 | } 16 | }; 17 | 18 | export default pageErrorsReducer; 19 | -------------------------------------------------------------------------------- /frontend/reducers/pages-reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_PAGE, RECEIVE_PAGES, REMOVE_PAGE } from '../actions/page-actions'; 2 | 3 | const pagesReducer = (state = {}, action) => { 4 | Object.freeze(state); 5 | switch (action.type) { 6 | case RECEIVE_PAGE: 7 | return Object.assign({}, state, { [action.page.id]: action.page }); 8 | case RECEIVE_PAGES: 9 | return Object.assign({}, action.pages); 10 | case REMOVE_PAGE: 11 | let newState = Object.assign({}, state); 12 | delete newState[action.pageId]; 13 | return newState; 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | export default pagesReducer; 20 | -------------------------------------------------------------------------------- /frontend/reducers/root-reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import entitiesReducer from './entities-reducer'; 3 | import sessionReducer from './session-reducer'; 4 | import errorsReducer from './errors-reducer'; 5 | import uiReducer from './ui-reducer'; 6 | 7 | const rootReducer = combineReducers({ 8 | entities: entitiesReducer, 9 | session: sessionReducer, 10 | errors: errorsReducer, 11 | ui: uiReducer, 12 | }); 13 | 14 | export default rootReducer; 15 | -------------------------------------------------------------------------------- /frontend/reducers/selectors.js: -------------------------------------------------------------------------------- 1 | export const getAllPages = ({ entities: { pages } }) => { 2 | return Object.keys(pages).map((id) => pages(id)); 3 | }; 4 | 5 | export const getAllBlocks = ({ entities: { blocks } }) => { 6 | return Object.keys(pages).map((id) => pages(id)); 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/reducers/session-errors-reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | RECEIVE_CURRENT_USER, 3 | RECEIVE_SESSION_ERRORS, 4 | REMOVE_ERRORS 5 | } from '../actions/session-actions'; 6 | 7 | const sessionErrorsReducer = (state = [], action) => { 8 | Object.freeze(state); 9 | switch (action.type) { 10 | case RECEIVE_CURRENT_USER: 11 | return []; 12 | case RECEIVE_SESSION_ERRORS: 13 | return action.errors; 14 | case REMOVE_ERRORS: 15 | return []; 16 | default: 17 | return state; 18 | }; 19 | }; 20 | 21 | export default sessionErrorsReducer; 22 | -------------------------------------------------------------------------------- /frontend/reducers/session-reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | RECEIVE_CURRENT_USER, 3 | LOGOUT_CURRENT_USER, 4 | } from '../actions/session-actions'; 5 | 6 | const _nullUser = Object.freeze({ 7 | currentUserId: null 8 | }); 9 | 10 | const sessionReducer = (state = _nullUser, action) => { 11 | Object.freeze(state); 12 | switch(action.type) { 13 | case RECEIVE_CURRENT_USER: 14 | return { currentUserId: action.currentUser.id }; 15 | case LOGOUT_CURRENT_USER: 16 | return _nullUser; 17 | default: 18 | return state; 19 | } 20 | }; 21 | 22 | export default sessionReducer; 23 | -------------------------------------------------------------------------------- /frontend/reducers/ui-reducer.js: -------------------------------------------------------------------------------- 1 | const uiReducer = (state = {}, action) => { 2 | Object.freeze(state); 3 | switch (action.type) { 4 | default: 5 | return state; 6 | } 7 | }; 8 | 9 | export default uiReducer; 10 | -------------------------------------------------------------------------------- /frontend/reducers/users-reducer.js: -------------------------------------------------------------------------------- 1 | import { RECEIVE_CURRENT_USER } from '../actions/session-actions' 2 | 3 | const usersReducer = (state = {}, action) => { 4 | Object.freeze(state); 5 | switch (action.type) { 6 | case RECEIVE_CURRENT_USER: 7 | return Object.assign({}, state, { [action.currentUser.id]: action.currentUser }); 8 | default: 9 | return state; 10 | } 11 | } 12 | 13 | export default usersReducer; -------------------------------------------------------------------------------- /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 middleware = [thunk]; 6 | 7 | if (process.env.NODE_ENV !== 'production') { 8 | const { logger } = require('redux-logger'); 9 | middleware.push(logger); 10 | } 11 | 12 | const configureStore = (preloadedState = {}) => ( 13 | createStore(rootReducer, preloadedState, applyMiddleware(...middleware)) 14 | ); 15 | 16 | export default configureStore; 17 | -------------------------------------------------------------------------------- /frontend/util/blocks-api-util.js: -------------------------------------------------------------------------------- 1 | export const createBlock = (block) => ( 2 | $.ajax({ 3 | url: '/api/blocks', 4 | method: 'POST', 5 | data: { block } 6 | }) 7 | ); 8 | 9 | export const fetchBlocks = (userId) => ( 10 | $.ajax({ 11 | url: `api/users/${userId}/blocks`, 12 | method: 'GET', 13 | }) 14 | ); 15 | 16 | // export const fetchBlocks = (pageId) => ( 17 | // $.ajax({ 18 | // url: `api/pages/${pageId}/blocks`, 19 | // method: 'GET', 20 | // }) 21 | // ); 22 | 23 | export const fetchBlock = (blockId) => ( 24 | $.ajax({ 25 | url: `api/blocks/${blockId}`, 26 | method: 'GET' 27 | }) 28 | ); 29 | 30 | export const updateBlock = (block) => ( 31 | $.ajax({ 32 | url: `/api/blocks/${block.id}`, 33 | method: 'PATCH', 34 | data: { block } 35 | }) 36 | ); 37 | 38 | export const deleteBlock = (blockId) => ( 39 | $.ajax({ 40 | url: `/api/blocks/${blockId}`, 41 | method: 'DELETE' 42 | }) 43 | ); 44 | -------------------------------------------------------------------------------- /frontend/util/pages-api-util.js: -------------------------------------------------------------------------------- 1 | export const createPage = (page) => ( 2 | $.ajax({ 3 | url: '/api/pages', 4 | method: 'POST', 5 | data: { page }, 6 | }) 7 | ); 8 | 9 | export const fetchPages= (userId) => ( 10 | $.ajax({ 11 | url: 'api/pages', 12 | method: 'GET', 13 | data: { userId } 14 | }) 15 | ) 16 | 17 | export const fetchPage = (pageId) => ( 18 | $.ajax({ 19 | url: `api/pages/${pageId}`, 20 | method: 'GET', 21 | }) 22 | ); 23 | 24 | export const updatePage = (page) => ( 25 | $.ajax({ 26 | url: `/api/pages/${page.id}`, 27 | method: 'PATCH', 28 | data: { page }, 29 | }) 30 | ); 31 | 32 | export const deletePage = (pageId) => ( 33 | $.ajax({ 34 | url: `/api/pages/${pageId}`, 35 | method: 'DELETE', 36 | }) 37 | ); 38 | -------------------------------------------------------------------------------- /frontend/util/route-util.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Route, Redirect, withRouter } from 'react-router-dom'; 4 | import SplashHome from '../components/auth/SplashHome'; 5 | import EditorContainer from '../components/editor/EditorContainer'; 6 | 7 | // Components only seen by logged out users 8 | const Auth = ({ component: Component, path, loggedIn, exact }) => ( 9 | { 13 | return ( 14 | !loggedIn ? : 15 | ); 16 | }} 17 | /> 18 | ); 19 | 20 | // Components only seen by logged in users 21 | const Protected = ({ component: Component, path, loggedIn, exact }) => ( 22 | ( 26 | loggedIn ? : 27 | )} 28 | /> 29 | ); 30 | 31 | const Home = ({ path, loggedIn, exact }) => ( 32 | ( 36 | loggedIn ? : 37 | )} 38 | /> 39 | ); 40 | 41 | const mapStateToProps = state => ({ 42 | loggedIn: Boolean(state.session.currentUserId) 43 | }); 44 | 45 | export const AuthRoute = withRouter(connect(mapStateToProps)(Auth)); 46 | export const ProtectedRoute = withRouter(connect(mapStateToProps)(Protected)); 47 | export const HomeRoute = withRouter(connect(mapStateToProps)(Home)); 48 | -------------------------------------------------------------------------------- /frontend/util/session-api-util.js: -------------------------------------------------------------------------------- 1 | export const login = (user) => ( 2 | $.ajax({ 3 | url: '/api/session', 4 | method: 'POST', 5 | data: { user } 6 | }) 7 | ); 8 | 9 | export const logout = () => ( 10 | $.ajax({ 11 | url: '/api/session', 12 | method: 'DELETE' 13 | }) 14 | ); 15 | 16 | export const signup = (user) => ( 17 | $.ajax({ 18 | url: '/api/users', 19 | method: 'POST', 20 | data: { user } 21 | }) 22 | ); 23 | -------------------------------------------------------------------------------- /frontend/util/utils.js: -------------------------------------------------------------------------------- 1 | export function debounce(func, wait, immediate) { 2 | var timeout; 3 | return function () { 4 | var context = this, 5 | args = arguments; 6 | var later = function () { 7 | timeout = null; 8 | if (!immediate) func.apply(context, args); 9 | }; 10 | var callNow = immediate && !timeout; 11 | clearTimeout(timeout); 12 | timeout = setTimeout(later, wait); 13 | if (callNow) func.apply(context, args); 14 | }; 15 | } 16 | 17 | export function pasteAsPlainText(e) { 18 | e.preventDefault(); 19 | const text = (e.originalEvent || e).clipboardData.getData('text/plain'); 20 | document.execCommand('insertHTML', false, text); 21 | } 22 | 23 | export function getCaretPosition() {} 24 | 25 | export function setCaretToEnd() {} 26 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lilnotion", 3 | "private": true, 4 | "description": "A fullstack Notion clone built with React, Redux, Ruby on Rails, and PostgreSQL", 5 | "version": "1.0.0", 6 | "main": "index.js", 7 | "directories": { 8 | "lib": "lib", 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "webpack --watch --mode=development", 14 | "postinstall": "webpack" 15 | }, 16 | "engines": { 17 | "node": "10.13.0", 18 | "npm": "6.4.1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/brandonfang/lilnotion.git" 23 | }, 24 | "keywords": [], 25 | "author": "Brandon Fang", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/brandonfang/lilnotion/issues" 29 | }, 30 | "homepage": "https://github.com/brandonfang/lilnotion#readme", 31 | "dependencies": { 32 | "@babel/runtime": "^7.16.3", 33 | "@fontsource/inter": "^4.5.0", 34 | "@radix-ui/react-context-menu": "^0.1.6", 35 | "@radix-ui/react-dialog": "^0.1.4", 36 | "@radix-ui/react-dropdown-menu": "^0.1.1", 37 | "@radix-ui/react-popover": "^0.1.6", 38 | "@radix-ui/react-scroll-area": "^0.1.1", 39 | "@radix-ui/react-tooltip": "^0.1.1", 40 | "babel-loader": "^8.2.2", 41 | "emoji-mart": "^3.0.1", 42 | "fast-deep-equal": "^3.1.3", 43 | "match-sorter": "^6.3.0", 44 | "node-emoji": "^1.11.0", 45 | "react": "^17.0.2", 46 | "react-beautiful-dnd": "^13.1.0", 47 | "react-contenteditable": "^3.3.5", 48 | "react-dom": "^17.0.2", 49 | "react-hotkeys-hook": "^3.4.4", 50 | "react-icons": "^4.2.0", 51 | "react-redux": "^7.2.4", 52 | "react-router-dom": "^5.2.0", 53 | "react-tooltip": "^4.2.21", 54 | "redux": "^4.1.0", 55 | "redux-thunk": "^2.3.0", 56 | "sanitize-html": "^2.6.0", 57 | "webpack": "^4.46.0" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "^7.16.0", 61 | "@babel/plugin-transform-runtime": "^7.16.4", 62 | "@babel/preset-env": "^7.16.4", 63 | "@babel/preset-react": "^7.16.0", 64 | "css-loader": "^5.2.7", 65 | "file-loader": "^6.2.0", 66 | "redux-logger": "^3.0.6", 67 | "style-loader": "^2.0.0", 68 | "webpack-cli": "^3.3.12", 69 | "webpack-dev-server": "^3" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /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/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/api/blocks_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::BlocksControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/api/pages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::PagesControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/api/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::SessionsControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /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/static_pages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class StaticPagesControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/blocks.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: blocks 4 | # 5 | # id :uuid not null, primary key 6 | # user_id :uuid not null 7 | # page_id :uuid not null 8 | # block_type :string default("paragraph"), not null 9 | # text :text default("") 10 | # image_url :string default("") 11 | # image_caption :string default("") 12 | # checked :boolean default(FALSE) 13 | # expanded :boolean default(FALSE) 14 | # toggle_inner_text :string default("") 15 | # link_page_id :string default("") 16 | # format :jsonb 17 | # icon :jsonb 18 | # order_index :integer default(0) 19 | # created_at :datetime not null 20 | # updated_at :datetime not null 21 | # 22 | 23 | # This model initially had no columns defined. If you add columns to the 24 | # model remove the '{}' from the fixture names and add the columns immediately 25 | # below each fixture, per the syntax in the comments below 26 | # 27 | one: {} 28 | # column: value 29 | # 30 | two: {} 31 | # column: value 32 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/pages.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: pages 4 | # 5 | # id :uuid not null, primary key 6 | # user_id :uuid not null 7 | # title :string default("") 8 | # block_ids :string default([]), is an Array 9 | # gallery_image_url :string default("") 10 | # uploaded_image_url :string default("") 11 | # icon :json 12 | # style :json 13 | # created_at :datetime not null 14 | # updated_at :datetime not null 15 | # 16 | 17 | # This model initially had no columns defined. If you add columns to the 18 | # model remove the '{}' from the fixture names and add the columns immediately 19 | # below each fixture, per the syntax in the comments below 20 | # 21 | one: {} 22 | # column: value 23 | # 24 | two: {} 25 | # column: value 26 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :uuid not null, primary key 6 | # email :string not null 7 | # first_name :string not null 8 | # last_name :string not null 9 | # password_digest :string not null 10 | # session_token :string not null 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 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/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/models/.keep -------------------------------------------------------------------------------- /test/models/block_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: blocks 4 | # 5 | # id :uuid not null, primary key 6 | # user_id :uuid not null 7 | # page_id :uuid not null 8 | # block_type :string default("paragraph"), not null 9 | # text :text default("") 10 | # image_url :string default("") 11 | # image_caption :string default("") 12 | # checked :boolean default(FALSE) 13 | # expanded :boolean default(FALSE) 14 | # toggle_inner_text :string default("") 15 | # link_page_id :string default("") 16 | # format :jsonb 17 | # icon :jsonb 18 | # order_index :integer default(0) 19 | # created_at :datetime not null 20 | # updated_at :datetime not null 21 | # 22 | require 'test_helper' 23 | 24 | class BlockTest < ActiveSupport::TestCase 25 | # test "the truth" do 26 | # assert true 27 | # end 28 | end 29 | -------------------------------------------------------------------------------- /test/models/page_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: pages 4 | # 5 | # id :uuid not null, primary key 6 | # user_id :uuid not null 7 | # title :string default("") 8 | # block_ids :string default([]), is an Array 9 | # gallery_image_url :string default("") 10 | # uploaded_image_url :string default("") 11 | # icon :json 12 | # style :json 13 | # created_at :datetime not null 14 | # updated_at :datetime not null 15 | # 16 | require 'test_helper' 17 | 18 | class PageTest < ActiveSupport::TestCase 19 | # test "the truth" do 20 | # assert true 21 | # end 22 | end 23 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :uuid not null, primary key 6 | # email :string not null 7 | # first_name :string not null 8 | # last_name :string not null 9 | # password_digest :string not null 10 | # session_token :string not null 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | require 'test_helper' 15 | 16 | class UserTest < ActiveSupport::TestCase 17 | # test "the truth" do 18 | # assert true 19 | # end 20 | end 21 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/test/system/.keep -------------------------------------------------------------------------------- /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/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonfang/lilnotion/d0ef4e43a13690f6dcf1f29fba593d9c322c0209/vendor/.keep -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './frontend/entry.jsx', 5 | output: { 6 | path: path.resolve(__dirname, 'app', 'assets', 'javascripts'), 7 | filename: 'bundle.js', 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: [/\.jsx?$/], 13 | exclude: /node_modules/, 14 | loader: 'babel-loader', 15 | query: { 16 | presets: ['@babel/env', '@babel/react'], 17 | plugins: [['@babel/transform-runtime']], 18 | }, 19 | }, 20 | { 21 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 22 | use: [ 23 | { 24 | loader: 'file-loader', 25 | }, 26 | ], 27 | }, 28 | { 29 | test: /\.css$/, 30 | use: ['style-loader', 'css-loader'], 31 | }, 32 | ], 33 | }, 34 | devtool: 'source-map', 35 | resolve: { 36 | extensions: ['.js', '.jsx', '*'], 37 | }, 38 | }; 39 | --------------------------------------------------------------------------------