├── .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 |
5 |
--------------------------------------------------------------------------------
/app/assets/images/lilnotion-logo-dark.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
50 | );
51 |
52 | const check = (
53 |
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 |
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 |
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 |
38 |
39 |
40 |
47 |
48 |
49 |
50 |
Delete
51 |
Del
52 |
53 |
54 |
59 |
60 |
61 |
62 |
Color
63 |
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 |
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 |

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 |

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 |
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 |
--------------------------------------------------------------------------------