├── .babelrc ├── .gitignore ├── Procfile ├── README.md ├── backend ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20220617_0414.py │ ├── 0003_remove_page_groups.py │ ├── 0004_page_closed.py │ ├── 0005_alter_page_closed.py │ └── __init__.py ├── models.py ├── serializers.py ├── templates │ └── backend │ │ ├── index.html │ │ └── layout.html ├── tests.py ├── urls.py └── views.py ├── frontend ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── src │ ├── actions │ │ ├── LogIn_out_register.js │ │ ├── image.js │ │ ├── index.js │ │ ├── kanban.js │ │ ├── page_menu.js │ │ ├── table.js │ │ ├── templates.js │ │ └── to_do.js │ ├── components │ │ ├── app.js │ │ ├── home │ │ │ ├── body_content │ │ │ │ ├── body_header.js │ │ │ │ ├── cover_photo │ │ │ │ │ ├── images │ │ │ │ │ │ ├── earth_1.jpg │ │ │ │ │ │ ├── earth_2.jpg │ │ │ │ │ │ ├── earth_3.jpeg │ │ │ │ │ │ ├── earth_4.jpeg │ │ │ │ │ │ ├── earth_5.jpg │ │ │ │ │ │ ├── earth_6.jpeg │ │ │ │ │ │ ├── earth_7.jpg │ │ │ │ │ │ ├── earth_8.jpeg │ │ │ │ │ │ ├── gradient_1.png │ │ │ │ │ │ ├── gradient_2.png │ │ │ │ │ │ ├── gradient_3.png │ │ │ │ │ │ ├── gradient_4.png │ │ │ │ │ │ ├── met_silk_kashan_carpet.jpg │ │ │ │ │ │ ├── met_william_morris_1875.jpg │ │ │ │ │ │ ├── met_william_morris_1877_willow.jpg │ │ │ │ │ │ ├── met_william_morris_1878.jpg │ │ │ │ │ │ ├── nasa_carina_nebula.jpg │ │ │ │ │ │ ├── nasa_earth_grid.jpg │ │ │ │ │ │ ├── nasa_great_sandy_desert_australia.jpg │ │ │ │ │ │ ├── nasa_new_york_city_grid.jpg │ │ │ │ │ │ ├── nasa_orion_nebula.jpg │ │ │ │ │ │ ├── nasa_robert_stewart_spacewalk.jpg │ │ │ │ │ │ ├── nasa_robert_stewart_spacewalk_2.jpg │ │ │ │ │ │ ├── nasa_tim_peake_spacewalk.jpg │ │ │ │ │ │ ├── solid_beige.png │ │ │ │ │ │ ├── solid_blue.png │ │ │ │ │ │ ├── solid_red.png │ │ │ │ │ │ ├── solid_yellow.png │ │ │ │ │ │ ├── woodcuts_1.jpg │ │ │ │ │ │ ├── woodcuts_10.jpg │ │ │ │ │ │ ├── woodcuts_15.jpg │ │ │ │ │ │ └── woodcuts_3.jpg │ │ │ │ │ ├── index.js │ │ │ │ │ ├── photo_dropdown.js │ │ │ │ │ └── photos_list.js │ │ │ │ ├── dropdowns │ │ │ │ │ ├── color_dropdown.js │ │ │ │ │ ├── dropdown_container.js │ │ │ │ │ ├── element_menu │ │ │ │ │ │ ├── createElement_dropdown │ │ │ │ │ │ │ ├── dropdown_content.js │ │ │ │ │ │ │ ├── dropdown_icons │ │ │ │ │ │ │ │ ├── board.png │ │ │ │ │ │ │ │ ├── heading_1.png │ │ │ │ │ │ │ │ ├── heading_2.png │ │ │ │ │ │ │ │ ├── link-to-page.png │ │ │ │ │ │ │ │ ├── table.png │ │ │ │ │ │ │ │ ├── text.png │ │ │ │ │ │ │ │ └── to-do.png │ │ │ │ │ │ │ ├── dropdown_option.js │ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ │ └── listOfPages.js │ │ │ │ │ │ ├── elementSettings_dropdown │ │ │ │ │ │ │ ├── dropdown_content.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── index.js │ │ │ │ │ └── toggle_button.js │ │ │ │ ├── index.js │ │ │ │ ├── other │ │ │ │ │ └── deleteConfirmationModal.js │ │ │ │ ├── page_elements │ │ │ │ │ ├── elements_list.js │ │ │ │ │ ├── headings │ │ │ │ │ │ ├── heading_1.js │ │ │ │ │ │ └── heading_2.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── kanban │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── kanbanCard.js │ │ │ │ │ │ ├── kanbanGroup.js │ │ │ │ │ │ └── kanban_dropdowns │ │ │ │ │ │ │ ├── dropdown_content.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── page_link.js │ │ │ │ │ ├── table │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── table-dropdowns │ │ │ │ │ │ │ ├── column_options │ │ │ │ │ │ │ │ ├── dropdown_content.js │ │ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ │ │ ├── number-icon.js │ │ │ │ │ │ │ │ └── property-type-dropdown.js │ │ │ │ │ │ │ ├── multi_select │ │ │ │ │ │ │ │ ├── multi-select-popup.js │ │ │ │ │ │ │ │ └── tag_options │ │ │ │ │ │ │ │ │ ├── dropdown_content.js │ │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ │ └── row_options │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── table_data │ │ │ │ │ │ │ ├── checkbox.js │ │ │ │ │ │ │ ├── date.js │ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ │ ├── multi_select.js │ │ │ │ │ │ │ ├── number.js │ │ │ │ │ │ │ ├── text.js │ │ │ │ │ │ │ └── url.js │ │ │ │ │ │ ├── table_header.js │ │ │ │ │ │ └── table_row.js │ │ │ │ │ ├── text.js │ │ │ │ │ └── to_do.js │ │ │ │ └── templates │ │ │ │ │ ├── index.js │ │ │ │ │ └── templates_content.js │ │ │ ├── home.js │ │ │ ├── loader.js │ │ │ └── menu │ │ │ │ ├── index.js │ │ │ │ ├── list_of_pages.js │ │ │ │ ├── menu_dropdown.js │ │ │ │ ├── menu_header.js │ │ │ │ ├── page-nav.js │ │ │ │ └── quick_find │ │ │ │ ├── index.js │ │ │ │ └── quick_find_pages.js │ │ ├── login.js │ │ ├── notion-logo.js │ │ └── register.js │ ├── index.js │ └── reducers │ │ ├── index.js │ │ ├── kanban.js │ │ └── table.js ├── static │ └── frontend │ │ ├── 0dcd3c3b86c8a552563e30fcd91f7a53.jpg │ │ ├── 0fd2ca50c6317bb99d04953eca1d21b8.png │ │ ├── 14744af249126b8822bbbc973a763a3b.jpg │ │ ├── 1956a50ca86ebc95c55a17d1897a9b28.jpg │ │ ├── 228d99e89139cf4e0e17ab41848beb6c.jpeg │ │ ├── 231105f2c0dddc5d1bd3895d1f17ed06.jpg │ │ ├── 243da70ef07be091abdb33333f93afc7.png │ │ ├── 2e9bcdcacceac7178acf756ade92407f.png │ │ ├── 40030e653dff791483f1abc2145834fd.jpg │ │ ├── 4c4460cd9678d6361066f496c2c80e89.jpg │ │ ├── 568c5fce08dbfa6cc8ca8d7a46eafbdb.png │ │ ├── 58380460e7ba563fdcd2c05beea7b352.jpg │ │ ├── 5d2abc6dcf2399fc533d805a85220b2d.png │ │ ├── 5f0a79e93e318d2b26ed5d16073af4d2.jpeg │ │ ├── 639ebd5fa707a5501c425f0385c9f9bf.jpg │ │ ├── 768fb4a529f09ac24c2230b9af8f558a.jpg │ │ ├── 7a749025257f71b71d74c60ff96efcaf.png │ │ ├── 912362a80d9876a73d33519c1c02df06.png │ │ ├── 92dbf76c6412df27deb22d35aa1bc094.jpeg │ │ ├── 9a9d59537cb2804844d1bde43d15c495.jpg │ │ ├── 9ba05ee9b948b065df17ace50aee1b91.jpg │ │ ├── Notion_app_logo.png │ │ ├── ad79d5554cb4fffa621090af22212075.jpg │ │ ├── ae8757bfaa998d7bc3a0f6b7c8616561.png │ │ ├── b03be388890362d40475184694c6f2ce.jpeg │ │ ├── b341a0d37c39f79c8edc9cb845d6fb2b.png │ │ ├── bd146962a6e7c17a036ffa3e205ab09c.png │ │ ├── c04c9389e02bfdf3afc161814b1faa04.png │ │ ├── c2ca688cb638be86d436e3f7a3c7a64f.jpg │ │ ├── c63230bed9c4f3a49ec1629c02e1313e.jpg │ │ ├── c707f24d228eba64614c14d631496449.png │ │ ├── cc15727d2cbf639130786b464c0b0e4a.png │ │ ├── e12afe5e859c951cc3f326bf7265fbdf.js │ │ ├── e1b20381ebe5bafedffa21d3286c4a91.jpg │ │ ├── e1f1833f9274cf1de61d02a96b49acdb.jpg │ │ ├── e308503ff876ee4861ee7e89a8099552.png │ │ ├── e7cff9e8f7a5fd7b393929b5ab2e6814.jpg │ │ ├── e882f14370d623461cb660616e142e1b.jpg │ │ ├── edb408ce1bc76f1fc1c0ec2820f13c59.png │ │ ├── f0d16b0ac2b43aa2e5ced84704b16b4b.jpg │ │ ├── f440e065e6f7ab4666106fe627891f0b.jpg │ │ ├── f61e1d19dba0ddd469683d92ea390f3a.jpg │ │ ├── f832b178dfdfbb0377a9f64a10b440e4.jpg │ │ ├── fb1165729217462067c4b78c8806fcc5.png │ │ ├── fonts │ │ ├── SF-Pro-Text-Bold.otf │ │ ├── SF-Pro-Text-Light.otf │ │ ├── SF-Pro-Text-Medium.otf │ │ ├── SF-Pro-Text-Semibold.otf │ │ └── SF-Pro.ttf │ │ ├── main.js │ │ ├── main.js.LICENSE.txt │ │ └── styles │ │ ├── css │ │ └── index.css │ │ └── sass │ │ ├── base │ │ ├── _layout.scss │ │ ├── _media_queries.scss │ │ ├── _mixins.scss │ │ └── _variables.scss │ │ ├── components │ │ ├── _dropdown.scss │ │ ├── _home.scss │ │ ├── _loader.scss │ │ ├── _login-register.scss │ │ ├── _menu.scss │ │ ├── _modal.scss │ │ └── body │ │ │ ├── _body-header.scss │ │ │ ├── _colors.scss │ │ │ ├── _cover_photo.scss │ │ │ ├── _element_options.scss │ │ │ ├── _general.scss │ │ │ ├── _headers.scss │ │ │ ├── _kanban.scss │ │ │ ├── _link_to_page.scss │ │ │ ├── _table.scss │ │ │ ├── _templates.scss │ │ │ ├── _text.scss │ │ │ └── _to_do.scss │ │ └── index.scss ├── templates │ └── frontend │ │ └── index.html ├── tests.py ├── urls.py └── views.py ├── manage.py ├── notionClone ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py ├── utils.py └── wsgi.py ├── package-lock.json ├── package.json ├── requirements.txt ├── runtime.txt └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["transform-class-properties"] 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/react,vscode,django,python 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=react,vscode,django,python 4 | 5 | ### Django ### 6 | *.log 7 | *.pot 8 | *.pyc 9 | __pycache__/ 10 | local_settings.py 11 | db.sqlite3 12 | db.sqlite3-journal 13 | media 14 | .env 15 | 16 | # If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/ 17 | # in your Git repository. Update and uncomment the following line accordingly. 18 | # /staticfiles/ 19 | 20 | ### Django.Python Stack ### 21 | # Byte-compiled / optimized / DLL files 22 | *.py[cod] 23 | *$py.class 24 | 25 | # C extensions 26 | *.so 27 | 28 | # Distribution / packaging 29 | .Python 30 | build/ 31 | develop-eggs/ 32 | dist/ 33 | downloads/ 34 | eggs/ 35 | .eggs/ 36 | lib/ 37 | lib64/ 38 | parts/ 39 | sdist/ 40 | var/ 41 | wheels/ 42 | pip-wheel-metadata/ 43 | share/python-wheels/ 44 | *.egg-info/ 45 | .installed.cfg 46 | *.egg 47 | MANIFEST 48 | 49 | # PyInstaller 50 | # Usually these files are written by a python script from a template 51 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 52 | *.manifest 53 | *.spec 54 | 55 | # Installer logs 56 | pip-log.txt 57 | pip-delete-this-directory.txt 58 | 59 | # Unit test / coverage reports 60 | htmlcov/ 61 | .tox/ 62 | .nox/ 63 | .coverage 64 | .coverage.* 65 | .cache 66 | nosetests.xml 67 | coverage.xml 68 | *.cover 69 | *.py,cover 70 | .hypothesis/ 71 | .pytest_cache/ 72 | pytestdebug.log 73 | 74 | # Translations 75 | *.mo 76 | 77 | # Django stuff: 78 | 79 | # Flask stuff: 80 | instance/ 81 | .webassets-cache 82 | 83 | # Scrapy stuff: 84 | .scrapy 85 | 86 | # Sphinx documentation 87 | docs/_build/ 88 | doc/_build/ 89 | 90 | # PyBuilder 91 | target/ 92 | 93 | # Jupyter Notebook 94 | .ipynb_checkpoints 95 | 96 | # IPython 97 | profile_default/ 98 | ipython_config.py 99 | 100 | # pyenv 101 | .python-version 102 | 103 | # pipenv 104 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 105 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 106 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 107 | # install all needed dependencies. 108 | #Pipfile.lock 109 | 110 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 111 | __pypackages__/ 112 | 113 | # Celery stuff 114 | celerybeat-schedule 115 | celerybeat.pid 116 | 117 | # SageMath parsed files 118 | *.sage.py 119 | 120 | # Environments 121 | .env 122 | .venv 123 | env/ 124 | venv/ 125 | ENV/ 126 | env.bak/ 127 | venv.bak/ 128 | pythonenv* 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # profiling data 152 | .prof 153 | 154 | ### Python ### 155 | # Byte-compiled / optimized / DLL files 156 | 157 | # C extensions 158 | 159 | # Distribution / packaging 160 | 161 | # PyInstaller 162 | # Usually these files are written by a python script from a template 163 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 164 | 165 | # Installer logs 166 | 167 | # Unit test / coverage reports 168 | 169 | # Translations 170 | 171 | # Django stuff: 172 | 173 | # Flask stuff: 174 | 175 | # Scrapy stuff: 176 | 177 | # Sphinx documentation 178 | 179 | # PyBuilder 180 | 181 | # Jupyter Notebook 182 | 183 | # IPython 184 | 185 | # pyenv 186 | 187 | # pipenv 188 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 189 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 190 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 191 | # install all needed dependencies. 192 | 193 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 194 | 195 | # Celery stuff 196 | 197 | # SageMath parsed files 198 | 199 | # Environments 200 | 201 | # Spyder project settings 202 | 203 | # Rope project settings 204 | 205 | # mkdocs documentation 206 | 207 | # mypy 208 | 209 | # Pyre type checker 210 | 211 | # pytype static type analyzer 212 | 213 | # profiling data 214 | 215 | ### react ### 216 | .DS_* 217 | logs 218 | **/*.backup.* 219 | **/*.back.* 220 | 221 | node_modules 222 | bower_components 223 | 224 | *.sublime* 225 | 226 | psd 227 | thumb 228 | sketch 229 | 230 | ### vscode ### 231 | .vscode/* 232 | !.vscode/settings.json 233 | !.vscode/tasks.json 234 | !.vscode/launch.json 235 | !.vscode/extensions.json 236 | *.code-workspace 237 | 238 | # End of https://www.toptal.com/developers/gitignore/api/react,vscode,django,python 239 | .env 240 | .env 241 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn notionClone.wsgi --log-file - -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | For this project I created a clone for the popular site, Notion, on a smaller scale. This website allows users to create pages, add notes to those pages, add to-do's, link to other pages and more! 2 | 3 | Live site: https://notion-app-clone.herokuapp.com/ 4 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/backend/__init__.py -------------------------------------------------------------------------------- /backend/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | from .models import * 4 | from django import forms 5 | from django.contrib.auth.forms import ReadOnlyPasswordHashField 6 | from django.contrib.auth.models import Group 7 | 8 | class UserCreationForm(forms.ModelForm): 9 | """A form for creating new users. Includes all the required 10 | fields, plus a repeated password.""" 11 | password1 = forms.CharField(label='Password', widget=forms.PasswordInput) 12 | password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) 13 | 14 | class Meta: 15 | model = User 16 | fields = ('email', 'first_name', 'last_name') 17 | 18 | def clean_password2(self): 19 | # Check that the two password entries match 20 | password1 = self.cleaned_data.get("password1") 21 | password2 = self.cleaned_data.get("password2") 22 | if password1 and password2 and password1 != password2: 23 | raise forms.ValidationError("Passwords don't match") 24 | return password2 25 | 26 | def save(self, commit=True): 27 | # Save the provided password in hashed format 28 | user = super().save(commit=False) 29 | user.set_password(self.cleaned_data["password1"]) 30 | if commit: 31 | user.save() 32 | return user 33 | 34 | class UserChangeForm(forms.ModelForm): 35 | """A form for updating users. Includes all the fields on 36 | the user, but replaces the password field with admin's 37 | password hash display field. 38 | """ 39 | password = ReadOnlyPasswordHashField() 40 | 41 | class Meta: 42 | model = User 43 | fields = ('email', 'password', 'first_name', 'last_name', 'is_active', 'is_admin', 'is_staff', 'is_superuser') 44 | 45 | def clean_password(self): 46 | # Regardless of what the user provides, return the initial value. 47 | # This is done here, rather than on the field, because the 48 | # field does not have access to the initial value 49 | return self.initial["password"] 50 | 51 | class AccountAdmin(UserAdmin): 52 | form = UserChangeForm 53 | add_form = UserCreationForm 54 | 55 | ordering = ('email',) 56 | list_display = ['email', 'date_joined', 'last_login', 'is_admin'] 57 | search_fields = ['email'] 58 | readonly_fields = ('date_joined', 'last_login') 59 | 60 | filter_horizontal = () 61 | list_filter = () 62 | fieldsets = () 63 | 64 | add_fieldsets = ( 65 | (None, { 66 | 'classes': ('wide',), 67 | 'fields': ('email', 'first_name', 'last_name', 'password1', 'password2'), 68 | }), 69 | ) 70 | 71 | admin.site.register(User, AccountAdmin) 72 | admin.site.unregister(Group) 73 | admin.site.register(Page) 74 | admin.site.register(Page_element) 75 | admin.site.register(Heading_1) 76 | admin.site.register(Heading_2) 77 | admin.site.register(Kanban) 78 | admin.site.register(Kanban_Group) 79 | admin.site.register(Kanban_Card) 80 | 81 | -------------------------------------------------------------------------------- /backend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ChatAppConfig(AppConfig): 5 | name = 'backend' 6 | -------------------------------------------------------------------------------- /backend/migrations/0002_auto_20220617_0414.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.11 on 2022-06-17 04:14 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('backend', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='page_element', 15 | name='column', 16 | ), 17 | migrations.RemoveField( 18 | model_name='page_element', 19 | name='group', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/migrations/0003_remove_page_groups.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.11 on 2022-06-27 01:31 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('backend', '0002_auto_20220617_0414'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='page', 15 | name='groups', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/migrations/0004_page_closed.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.11 on 2022-08-10 01:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('backend', '0003_remove_page_groups'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='page', 15 | name='closed', 16 | field=models.BooleanField(null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/migrations/0005_alter_page_closed.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.11 on 2022-08-10 01:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('backend', '0004_page_closed'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='page', 15 | name='closed', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/backend/migrations/__init__.py -------------------------------------------------------------------------------- /backend/templates/backend/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'chat_app/layout.html' %} 2 | 3 | {% block body %} 4 |

Hello World

5 | {% endblock %} -------------------------------------------------------------------------------- /backend/templates/backend/layout.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block body %} 10 | {% endblock %} 11 | 12 | -------------------------------------------------------------------------------- /backend/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | from rest_framework import routers 3 | from . import views 4 | from knox import views as knox_views 5 | from .views import RegisterAPI, LoginAPI, UserAPI 6 | 7 | router = routers.DefaultRouter() 8 | router.register(r'pages', views.PageViewSet, basename='Pages') 9 | router.register(r'add_pages', views.AddPageViewSet) 10 | router.register(r'page_elements', views.Page_elementViewSet) 11 | router.register(r'Heading_1s', views.Heading_1ViewSet) 12 | router.register(r'Heading_2s', views.Heading_2ViewSet) 13 | router.register(r'Texts', views.TextViewSet) 14 | router.register(r'pageLinks', views.PageLinkViewSet) 15 | router.register(r'to_dos', views.ToDoViewSet) 16 | 17 | router.register(r'kanbans', views.KanbanViewSet) 18 | router.register(r'kanban_groups', views.Kanban_GroupViewSet) 19 | router.register(r'kanban_cards', views.Kanban_CardViewSet) 20 | 21 | router.register(r'tables', views.TableViewSet) 22 | router.register(r'table_rows', views.TableRowViewSet) 23 | router.register(r'table_data', views.TableDataViewSet) 24 | router.register(r'tags', views.TagViewSet) 25 | 26 | urlpatterns = [ 27 | path("api_", include(router.urls)), 28 | path("registerAPI", RegisterAPI.as_view()), 29 | path("loginAPI", LoginAPI.as_view()), 30 | path("userAPI", UserAPI.as_view()), 31 | path("logoutAPI", knox_views.LogoutView.as_view(), name='knox_logout'), 32 | path("auth", include('knox.urls')) 33 | ] -------------------------------------------------------------------------------- /backend/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponseRedirect 3 | from .models import * 4 | 5 | from rest_framework import viewsets, permissions, generics, status 6 | from rest_framework.response import Response 7 | from rest_framework.decorators import api_view 8 | from rest_framework.response import Response 9 | from rest_framework.views import APIView 10 | from .serializers import * 11 | 12 | from knox.models import AuthToken 13 | from django.conf import settings 14 | 15 | #https://medium.com/a-layman/build-single-page-application-with-react-and-django-part-1-connect-react-app-with-django-app-dbf6b5ec52f4 16 | 17 | # Page API 18 | class PageViewSet(viewsets.ModelViewSet): 19 | def get_queryset(self): 20 | user_id = self.request.query_params.get('user_id') 21 | orphan = self.request.query_params.get('orphan') 22 | 23 | if user_id and orphan: 24 | queryset = Page.objects.filter(creator=user_id, parent=None) 25 | elif user_id: 26 | queryset = Page.objects.filter(creator=user_id) 27 | elif orphan: 28 | queryset = Page.objects.filter(parent=None) 29 | else: 30 | queryset = Page.objects.all() 31 | return queryset.order_by('id') 32 | serializer_class = PageSerializer 33 | 34 | # Add Page API 35 | class AddPageViewSet(viewsets.ModelViewSet): 36 | queryset = Page.objects.all() 37 | serializer_class = AddPageSerializer 38 | 39 | # Page_element API 40 | class Page_elementViewSet(viewsets.ModelViewSet): 41 | queryset = Page_element.objects.all().order_by('order_on_page') 42 | serializer_class = Page_elementSerializer 43 | 44 | # Heading_1 API 45 | class Heading_1ViewSet(viewsets.ModelViewSet): 46 | queryset = Heading_1.objects.all() 47 | serializer_class = Heading_1Serializer 48 | 49 | # Heading_2 API 50 | class Heading_2ViewSet(viewsets.ModelViewSet): 51 | queryset = Heading_2.objects.all() 52 | serializer_class = Heading_2Serializer 53 | 54 | # Text API 55 | class TextViewSet(viewsets.ModelViewSet): 56 | queryset = Text.objects.all() 57 | serializer_class = TextSerializer 58 | 59 | # Kanban API 60 | class KanbanViewSet(viewsets.ModelViewSet): 61 | queryset = Kanban.objects.all() 62 | serializer_class = KanbanSerializer 63 | 64 | # Kanban_Group API 65 | class Kanban_GroupViewSet(viewsets.ModelViewSet): 66 | queryset = Kanban_Group.objects.all() 67 | serializer_class = Kanban_GroupSerializer 68 | 69 | # Kanban_Card API 70 | class Kanban_CardViewSet(viewsets.ModelViewSet): 71 | queryset = Kanban_Card.objects.all().order_by('order_on_group') 72 | serializer_class = Kanban_CardSerializer 73 | 74 | # PageLink API 75 | class PageLinkViewSet(viewsets.ModelViewSet): 76 | queryset = PageLink.objects.all() 77 | serializer_class = PageLinkSerializer 78 | 79 | # PageLink API 80 | class ToDoViewSet(viewsets.ModelViewSet): 81 | queryset = To_do.objects.all() 82 | serializer_class = ToDoSerializer 83 | 84 | # Table API 85 | class TableViewSet(viewsets.ModelViewSet): 86 | queryset = Table.objects.all() 87 | serializer_class = TableSerializer 88 | 89 | # Table Row API 90 | class TableRowViewSet(viewsets.ModelViewSet): 91 | queryset = Table_row.objects.all() 92 | serializer_class = TableRowSerializer 93 | 94 | # Table Data API 95 | class TableDataViewSet(viewsets.ModelViewSet): 96 | queryset = Table_data.objects.all() 97 | serializer_class = TableDataSerializer 98 | 99 | # Tag API 100 | class TagViewSet(viewsets.ModelViewSet): 101 | queryset = Tag.objects.all() 102 | serializer_class = TagSerializer 103 | 104 | #Register API 105 | class RegisterAPI(generics.GenericAPIView): 106 | serializer_class = RegisterSerializer 107 | 108 | def post(self, request, *args, **kwargs): 109 | serializer = self.get_serializer(data=request.data) 110 | serializer.is_valid(raise_exception=True) 111 | user = serializer.save() 112 | return Response({ 113 | "user": UserSerializer(user, context=self.get_serializer_context()).data, 114 | "token": AuthToken.objects.create(user)[1] 115 | }) 116 | 117 | #Login API 118 | class LoginAPI(generics.GenericAPIView): 119 | serializer_class = LoginSerializer 120 | 121 | def post(self, request, *args, **kwargs): 122 | serializer = self.get_serializer(data=request.data) 123 | serializer.is_valid(raise_exception=True) 124 | user = serializer.validated_data 125 | return Response({ 126 | "user": UserSerializer(user, context=self.get_serializer_context()).data, 127 | "token": AuthToken.objects.create(user)[1] 128 | }) 129 | 130 | #Get User API 131 | class UserAPI(generics.RetrieveAPIView): 132 | permission_classes = [ 133 | permissions.IsAuthenticated, 134 | ] 135 | serializer_class = UserSerializer 136 | 137 | def get_object(self): 138 | return self.request.user -------------------------------------------------------------------------------- /frontend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/__init__.py -------------------------------------------------------------------------------- /frontend/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /frontend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FrontendConfig(AppConfig): 5 | name = 'frontend' 6 | -------------------------------------------------------------------------------- /frontend/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/migrations/__init__.py -------------------------------------------------------------------------------- /frontend/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /frontend/src/actions/LogIn_out_register.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const regeneratorRuntime = require("regenerator-runtime"); 3 | import { getCookie } from './index' 4 | 5 | // Configure axios to accept the CSRF Token 6 | const headers = { 7 | 'X-CSRFToken': getCookie('csrftoken') 8 | } 9 | 10 | axios.defaults.xsrfCookieName = 'csrftoken' 11 | axios.defaults.xsrfHeaderName = "X-CSRFTOKEN" 12 | 13 | // Get user data from token 14 | export const get_user_data = () => 15 | async (dispatch) => { 16 | const response = await axios.get("/userAPI", {headers: { 17 | Authorization: `Token ${localStorage.getItem('token')}` 18 | }}) 19 | 20 | dispatch({ type: 'GET_USER_DATA', payload: response }); 21 | 22 | return response.data 23 | }; 24 | 25 | // Register 26 | export const register = (first_name, last_name, email, password) => 27 | async () => { 28 | let error = ""; 29 | const response = await axios.post('/registerAPI', { 30 | first_name: first_name, 31 | last_name: last_name, 32 | email: email, 33 | password: password, 34 | }, {headers: headers}) 35 | .catch((err) => { 36 | error = err.response.data.email[0] 37 | }); 38 | 39 | if (error) { 40 | return error 41 | } 42 | 43 | localStorage.setItem('token', response.data.token); 44 | return response.data 45 | }; 46 | 47 | // Login 48 | export const login = (email, password) => 49 | async () => { 50 | let error = ""; 51 | const response = await axios.post('/loginAPI', { 52 | email: email, 53 | password: password, 54 | }, {headers: headers}) 55 | .catch((err) => { 56 | error = err.response.data.non_field_errors[0] 57 | }); 58 | 59 | if (error) { 60 | return error 61 | } 62 | 63 | localStorage.setItem('token', response.data.token); 64 | }; 65 | 66 | // Logout 67 | export const logout = () => 68 | async (dispatch) => { 69 | 70 | // Delete token from server 71 | const new_headers = { 72 | 'X-CSRFToken': getCookie('csrftoken'), 73 | 'Authorization': `Token ${localStorage.getItem('token')}` 74 | }; 75 | 76 | await axios.post('/logoutAPI', { 77 | }, {headers: new_headers}); 78 | 79 | // Remove token from local storage 80 | localStorage.removeItem('token'); 81 | 82 | dispatch({ type: 'LOGOUT', payload: "" }); 83 | }; -------------------------------------------------------------------------------- /frontend/src/actions/image.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const regeneratorRuntime = require("regenerator-runtime"); 3 | import { getCookie } from './index' 4 | 5 | // Configure axios to accept the CSRF Token 6 | const headers = { 7 | 'X-CSRFToken': getCookie('csrftoken') 8 | } 9 | 10 | axios.defaults.xsrfCookieName = 'csrftoken' 11 | axios.defaults.xsrfHeaderName = "X-CSRFTOKEN" 12 | 13 | // Add cover image to page 14 | export const add_cover_image = (page_id, image) => 15 | async (dispatch) => { 16 | await axios.patch(`api_pages/${page_id}/`, { 17 | photo: image 18 | }, {headers: headers}) 19 | 20 | dispatch({ type: 'CHANGE_COVER_IMAGE', payload: image }) 21 | } 22 | 23 | // Remove cover image 24 | export const remove_cover_image = (page_id) => 25 | async (dispatch) => { 26 | await axios.patch(`api_pages/${page_id}/`, { 27 | photo: null 28 | }, {headers: headers}) 29 | 30 | dispatch({ type: 'CHANGE_COVER_IMAGE', payload: null }) 31 | } -------------------------------------------------------------------------------- /frontend/src/actions/page_menu.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const regeneratorRuntime = require("regenerator-runtime"); 3 | import { getCookie } from './index' 4 | 5 | // Configure axios to accept the CSRF Token 6 | const headers = { 7 | 'X-CSRFToken': getCookie('csrftoken') 8 | } 9 | 10 | axios.defaults.xsrfCookieName = 'csrftoken' 11 | axios.defaults.xsrfHeaderName = "X-CSRFTOKEN" 12 | 13 | // Add a page 14 | export const add_page = (parent, creator) => 15 | async (dispatch) => { 16 | 17 | // Check if parent 18 | if (!parent) { 19 | parent = null 20 | } 21 | 22 | const response = await axios.post(`/api_add_pages/`, { 23 | name: "Untitled", 24 | parent: parent, 25 | creator: creator, 26 | children: [], 27 | page_elements: [], 28 | groups: 1, 29 | }, {headers: headers}); 30 | 31 | // Add blank children array to new page 32 | response.data.children = [] 33 | 34 | dispatch({ type: 'ADD_PAGE', payload: response.data }); 35 | 36 | return response.data 37 | }; 38 | 39 | // Edit page name 40 | export const edit_page_name = (page_id, page_name) => 41 | async (dispatch) => { 42 | 43 | const response = await axios.patch(`/api_add_pages/${page_id}/`, { 44 | name: page_name, 45 | }, {headers: headers}); 46 | 47 | dispatch({ type: 'EDIT_PAGE_NAME', payload: response.data }); 48 | }; 49 | 50 | // On change handler for editing a page name (updates select page reducer) 51 | export const changeNameOnBody = (page_name, page_id) => { 52 | if (!page_name) { 53 | page_name = "Untitled" 54 | } 55 | const details = {page_name: page_name, page_id: page_id} 56 | 57 | return { type: 'EDIT_NAME_ON_CHANGE', payload: details }; 58 | }; 59 | 60 | // Delete a page 61 | export const delete_page = (page_id) => 62 | async (dispatch) => { 63 | await axios.delete(`/api_add_pages/${page_id}/`, undefined, {headers: headers}); 64 | 65 | dispatch({ type: 'DELETE_PAGE', payload: page_id }); 66 | }; -------------------------------------------------------------------------------- /frontend/src/actions/to_do.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const regeneratorRuntime = require("regenerator-runtime"); 3 | import { getCookie } from './index' 4 | 5 | // Configure axios to accept the CSRF Token 6 | const headers = { 7 | 'X-CSRFToken': getCookie('csrftoken') 8 | } 9 | 10 | axios.defaults.xsrfCookieName = 'csrftoken' 11 | axios.defaults.xsrfHeaderName = "X-CSRFTOKEN" 12 | 13 | // Create a To-do 14 | export const create_ToDo = async (element_id) => { 15 | const response = await axios.post('/api_to_dos/', { 16 | completed: false, 17 | page_element: element_id 18 | }, {headers: headers}); 19 | 20 | return response.data 21 | } 22 | 23 | // Change tick box 24 | export const change_tickBox = async (status, id) => { 25 | await axios.patch(`api_to_dos/${id}/`, { 26 | completed: status 27 | }, {headers: headers}); 28 | } 29 | 30 | // Change description 31 | export const change_description = async (description, id) => { 32 | await axios.patch(`api_to_dos/${id}/`, { 33 | description: description 34 | }, {headers: headers}); 35 | } -------------------------------------------------------------------------------- /frontend/src/components/app.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 3 | 4 | import Home from "./home/home" 5 | import Login from "./login" 6 | import Register from "./register" 7 | import Body from "./home/body_content" 8 | 9 | const App = () => { 10 | return ( 11 | 12 | 13 | } /> 14 | } /> 15 | }> 16 | } /> 17 | 18 | 19 | 20 | ) 21 | } 22 | export default App; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/body_header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from "react-router-dom"; 3 | 4 | function BodyHeader(props) { 5 | return ( 6 |
7 | {!props.menu_shown && 8 |
props.toggle_menu(true)}> 10 | 11 |
} 12 | 13 | {props.breadcrumb.map((page, count) => { 14 | return ( 15 |
16 | {count === 0? null:

/

} 17 | {page.name} 18 |
) 19 | })} 20 |
21 | ) 22 | } 23 | export default BodyHeader -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_1.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_2.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_3.jpeg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_4.jpeg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_5.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_6.jpeg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_7.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/earth_8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/earth_8.jpeg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/gradient_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/gradient_1.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/gradient_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/gradient_2.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/gradient_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/gradient_3.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/gradient_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/gradient_4.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/met_silk_kashan_carpet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/met_silk_kashan_carpet.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/met_william_morris_1875.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/met_william_morris_1875.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/met_william_morris_1877_willow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/met_william_morris_1877_willow.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/met_william_morris_1878.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/met_william_morris_1878.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_carina_nebula.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_carina_nebula.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_earth_grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_earth_grid.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_great_sandy_desert_australia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_great_sandy_desert_australia.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_new_york_city_grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_new_york_city_grid.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_orion_nebula.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_orion_nebula.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_robert_stewart_spacewalk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_robert_stewart_spacewalk.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_robert_stewart_spacewalk_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_robert_stewart_spacewalk_2.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/nasa_tim_peake_spacewalk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/nasa_tim_peake_spacewalk.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/solid_beige.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/solid_beige.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/solid_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/solid_blue.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/solid_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/solid_red.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/solid_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/solid_yellow.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/woodcuts_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/woodcuts_1.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/woodcuts_10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/woodcuts_10.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/woodcuts_15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/woodcuts_15.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/images/woodcuts_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/cover_photo/images/woodcuts_3.jpg -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import ImageIcon from '@material-ui/icons/Image'; 3 | import { connect } from "react-redux"; 4 | import { add_cover_image, remove_cover_image } from "../../../../actions/image"; 5 | import PhotoDropdown from "./photo_dropdown"; 6 | import { photos_list } from "./photos_list"; 7 | 8 | function CoverPhoto(props) { 9 | const imageClass = `cover-photo ${props.page.photo}` 10 | const [dropdownShown, setDropdownShown] = useState(false) 11 | const [rand_num, set_rand_num] = useState(0) 12 | 13 | // Generate random number (this is used when a user chooses to add a cover photo to the page. The 14 | // photo is slected randomly). 15 | useEffect(() => { 16 | set_rand_num(Math.floor(Math.random() * Math.floor(Object.keys(photos_list).length) + 1)) 17 | }, []); 18 | 19 | return ( 20 |
21 | {/* If page has a photo */} 22 | {props.page.photo? 23 |
24 | {props.page.photo} 25 |
26 |
setDropdownShown(true)}> 27 | Change cover 28 |
29 |
30 | props.remove_cover_image(props.page.id)}> 31 | Remove cover 32 |
33 |
34 | 35 | {/* Choose a photo dropdown */} 36 | {dropdownShown && 37 | } 41 |
42 | 43 | // Else show add photo button 44 | :
45 |
props.add_cover_image(props.page.id, 46 | Object.keys(photos_list)[rand_num])}> 47 | 48 | Add Cover 49 |
50 |
} 51 |
52 | ) 53 | } 54 | export default connect(null, { add_cover_image, remove_cover_image })(CoverPhoto) -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/cover_photo/photos_list.js: -------------------------------------------------------------------------------- 1 | // Color and Gradient 2 | import red from './images/solid_red.png'; 3 | import yellow from './images/solid_yellow.png'; 4 | import blue from './images/solid_blue.png'; 5 | import beige from './images/solid_beige.png'; 6 | import gradient_1 from './images/gradient_1.png'; 7 | import gradient_2 from './images/gradient_2.png'; 8 | import gradient_3 from './images/gradient_3.png'; 9 | import gradient_4 from './images/gradient_4.png'; 10 | 11 | // Nasa Archive 12 | import spacewalk from './images/nasa_robert_stewart_spacewalk.jpg'; 13 | import nasa_tim_peake_spacewalk from './images/nasa_tim_peake_spacewalk.jpg'; 14 | import nasa_robert_stewart_spacewalk_2 from './images/nasa_robert_stewart_spacewalk_2.jpg'; 15 | import nasa_earth_grid from './images/nasa_earth_grid.jpg'; 16 | import nasa_carina_nebula from './images/nasa_carina_nebula.jpg'; 17 | import nasa_orion_nebula from './images/nasa_orion_nebula.jpg'; 18 | import nasa_new_york_city_grid from './images/nasa_new_york_city_grid.jpg'; 19 | import nasa_great_sandy_desert_australia from './images/nasa_great_sandy_desert_australia.jpg'; 20 | 21 | // The MET Museum - Patterns 22 | import met_william_morris_1877_willow from './images/met_william_morris_1877_willow.jpg' 23 | import met_william_morris_1875 from './images/met_william_morris_1875.jpg' 24 | import met_william_morris_1878 from './images/met_william_morris_1878.jpg' 25 | import met_silk_kashan_carpet from './images/met_silk_kashan_carpet.jpg' 26 | 27 | // The MET Museum - Japanese Prints 28 | import woodcuts_10 from './images/woodcuts_10.jpg' 29 | import woodcuts_15 from './images/woodcuts_15.jpg' 30 | import woodcuts_3 from './images/woodcuts_3.jpg' 31 | import woodcuts_1 from './images/woodcuts_1.jpg' 32 | 33 | // Planet Earth 34 | import earth_1 from './images/earth_1.jpg' 35 | import earth_2 from './images/earth_2.jpg' 36 | import earth_3 from './images/earth_3.jpeg' 37 | import earth_4 from './images/earth_4.jpeg' 38 | import earth_5 from './images/earth_5.jpg' 39 | import earth_6 from './images/earth_6.jpeg' 40 | import earth_7 from './images/earth_7.jpg' 41 | import earth_8 from './images/earth_8.jpeg' 42 | 43 | export const photos_list = { 44 | // Color and Gradient 45 | red, 46 | yellow, 47 | blue, 48 | beige, 49 | gradient_1, 50 | gradient_2, 51 | gradient_3, 52 | gradient_4, 53 | 54 | // Nasa Archive 55 | spacewalk, 56 | nasa_tim_peake_spacewalk, 57 | nasa_robert_stewart_spacewalk_2, 58 | nasa_earth_grid, 59 | nasa_carina_nebula, 60 | nasa_orion_nebula, 61 | nasa_new_york_city_grid, 62 | nasa_great_sandy_desert_australia, 63 | 64 | // The MET Museum - Patterns 65 | met_william_morris_1877_willow, 66 | met_william_morris_1875, 67 | met_william_morris_1878, 68 | met_silk_kashan_carpet, 69 | 70 | // The MET Museum - Japanese Prints 71 | woodcuts_10, 72 | woodcuts_15, 73 | woodcuts_3, 74 | woodcuts_1, 75 | 76 | // Planet Earth 77 | earth_1, 78 | earth_2, 79 | earth_3, 80 | earth_4, 81 | earth_5, 82 | earth_6, 83 | earth_7, 84 | earth_8, 85 | } -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/color_dropdown.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CheckIcon from '@material-ui/icons/Check'; 3 | 4 | function ColorDropdown (props) { 5 | return ( 6 |
7 |

Colors

8 | 9 | {props.forElementBackground? 10 |
11 | props.change_color("")}> 12 |
13 |

White

14 | {!props.selected_color && } 15 |
: 16 |
17 | props.change_color('default')}> 18 |
19 |

Default

20 | {props.selected_color === 'default' && } 21 |
22 | } 23 | 24 |
25 | props.change_color('grey')}> 26 |
27 |

Grey

28 | {props.selected_color === 'grey' && } 29 |
30 | 31 |
32 | props.change_color('brown')}> 33 |
34 |

Brown

35 | {props.selected_color === 'brown' && } 36 |
37 | 38 |
39 | props.change_color('orange')}> 40 |
41 |

Orange

42 | {props.selected_color === 'orange' && } 43 |
44 | 45 |
46 | props.change_color('yellow')}> 47 |
48 |

Yellow

49 | {props.selected_color === 'yellow' && } 50 |
51 | 52 |
53 | props.change_color('green')}> 54 |
55 |

Green

56 | {props.selected_color === 'green' && } 57 |
58 | 59 |
60 | props.change_color('blue')}> 61 |
62 |

Blue

63 | {props.selected_color === 'blue' && } 64 |
65 | 66 |
67 | props.change_color('purple')}> 68 |
69 |

Purple

70 | {props.selected_color === 'purple' && } 71 |
72 | 73 |
74 | props.change_color('pink')}> 75 |
76 |

Pink

77 | {props.selected_color === 'pink' && } 78 |
79 | 80 |
81 | props.change_color('red')}> 82 |
83 |

Red

84 | {props.selected_color === 'red' && } 85 |
86 |
87 | ) 88 | } 89 | export default ColorDropdown -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/dropdown_container.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | 3 | // Purpose is to correctly position the dropdown menu given its position on the screen 4 | const DropdownContainer = React.forwardRef((props, ref) => { 5 | const [state, setState] = useState({ 6 | transform: "", 7 | opacity: 0 8 | }) 9 | const myRef = useRef(null); 10 | 11 | useEffect(() => { 12 | // _____________________ DROPDOWN POSITIONING ________________________ // 13 | const rect = myRef.current.getBoundingClientRect() 14 | let transform = state.transform 15 | 16 | // If the dropdown is dropping below the window size (meaning we can't see part of it) 17 | // reverse the direction and have it point upwards 18 | if (window.innerHeight - rect.bottom < 5) { 19 | transform = `translateY(${props.translate_Y || '-95%'})` 20 | 21 | console.log(true) 22 | 23 | // Check if dropdown is past the width of the window size 24 | if (rect.right > window.innerWidth) { 25 | transform = `translateY(${props.translate_Y || '-95%'}) translateX(${props.translate_X || '-20%'})` 26 | } 27 | // Check if dropdown is past the width of the window size 28 | } else if (rect.right > window.innerWidth) { 29 | transform = `translateX(${props.translate_X2 || props.translate_X || '-20%'})` 30 | } 31 | setState({...state, transform: transform, opacity: 100}) 32 | 33 | // _____________________ DROPDOWN CLICK OUTSIDE - EVENT LISTENR ________________________ // 34 | // Add event listener to detect when someone clicks away from dropdown 35 | const handler = async (event) => { 36 | if (!myRef.current) { 37 | return 38 | } 39 | 40 | // Close menu when user clicks away from it 41 | if (!myRef.current.contains(event.target)) { 42 | 43 | if (event.target.className !== "dropdown") { 44 | props.setDropdownShown(false); 45 | } 46 | } 47 | } 48 | document.addEventListener("mousedown", handler); 49 | 50 | return () => { 51 | document.removeEventListener("mousedown", handler); 52 | } 53 | }, []); 54 | 55 | const className = props.className? ` ${props.className}`: ""; 56 | 57 | // Dropdown dynamic positioning 58 | const topSubtraction = props.topSubtraction? props.topSubtraction : 0; 59 | const leftSubtraction = props.leftSubtraction? props.leftSubtraction : 0; 60 | 61 | const topPosition = ref? `${ref.current.getBoundingClientRect().top + 20 - topSubtraction}px`: ""; 62 | const leftPosition = ref? `${ref.current.getBoundingClientRect().left - leftSubtraction}px`: ""; 63 | 64 | return ( 65 |
66 | {props.screen !== false &&
} 67 |
72 | 73 | { props.children } 74 |
75 |
76 | ) 77 | }); 78 | export default DropdownContainer; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/board.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/heading_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/heading_1.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/heading_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/heading_2.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/link-to-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/link-to-page.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/table.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/text.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/to-do.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_icons/to-do.png -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/dropdown_option.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DropdownOption = (props) => { 4 | return ( 5 | props.create_element_func(props.elementType, props.element_above_order)}> 6 | {props.header} 7 |
8 |

{props.header}

9 |

{props.subheader}

10 |
11 |
12 | ) 13 | } 14 | export default DropdownOption; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import ToggleButton from "../../toggle_button"; 3 | import CreateElement_dropdownContent from "./dropdown_content"; 4 | import AddIcon from '@material-ui/icons/Add'; 5 | import DropdownContainer from "../../dropdown_container"; 6 | 7 | const CreateElement_dropdown = (props) => { 8 | const [dropdownShown, setDropdownShown] = useState(false) 9 | const dropdownRef = useRef(null); 10 | 11 | return ( 12 |
13 | 16 | 17 | 18 | 19 | {dropdownShown && 20 | 24 | 31 | 32 | } 33 |
34 | ) 35 | } 36 | export default CreateElement_dropdown -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/createElement_dropdown/listOfPages.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { connect } from 'react-redux'; 3 | import DropdownContainer from "../../dropdown_container"; 4 | 5 | const ListOfPages = (props) => { 6 | return ( 7 | 8 |
9 |

Select a page

10 | {props.pages.map(page => { 11 | return ( 12 | { 13 | props.setDropdownShown(false); 14 | props.create_element(props.index, props.page.id, "Page_link", props.order_on_page, 15 | props.element_above_order, page.id); 16 | }}> 17 | 18 | {page.name} 19 | 20 | ) 21 | })} 22 |
23 |
24 | ) 25 | }; 26 | 27 | const mapStateToProps = (state) => { 28 | return { pages: state.pages} 29 | } 30 | export default connect(mapStateToProps)(ListOfPages) -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/elementSettings_dropdown/dropdown_content.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { connect } from 'react-redux'; 3 | import { delete_element, change_bgColor } from "../../../../../../actions"; 4 | import ColorDropdown from "../../color_dropdown"; 5 | import DropdownContainer from "../../dropdown_container"; 6 | 7 | const ElementSettings_dropdownContent = (props) => { 8 | const [colorDropdownShown, setColorDropdownShown] = useState(false); 9 | 10 | // Change element background color 11 | const change_bgColor_forRedux = (color) => { 12 | props.change_bgColor(props.element.id, color) 13 | props.setDropdownShown(false) 14 | } 15 | 16 | return ( 17 |
18 | {/* Delete Element */} 19 | props.delete_element(props.element)} onMouseEnter={()=>setColorDropdownShown(false)}> 20 | 21 |

Delete

22 |
23 | 24 | {/* Change background color */} 25 | {props.element.element_type !== "Table" && props.element.element_type !== "Kanban" && 26 |
setColorDropdownShown(true)}> 27 | 28 |

Background

29 | 30 | {colorDropdownShown && 31 | 36 | 37 |
38 | 42 |
43 |
44 | } 45 |
46 | } 47 |
48 | ) 49 | }; 50 | 51 | const mapStateToProps = (state) => { 52 | return { selected_page: state.selected_page, pages: state.pages.data } 53 | } 54 | 55 | export default connect(mapStateToProps, { 56 | delete_element, 57 | change_bgColor 58 | })(ElementSettings_dropdownContent); -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/elementSettings_dropdown/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import ToggleButton from "../../toggle_button"; 3 | import ElementSettings_dropdownContent from "./dropdown_content"; 4 | import DragIndicatorIcon from '@material-ui/icons/DragIndicator'; 5 | import DropdownContainer from "../../dropdown_container"; 6 | 7 | const ElementSettings_dropdown = (props) => { 8 | const [dropdownShown, setDropdownShown] = useState(false); 9 | const dropdownRef = useRef(null); 10 | 11 | return ( 12 |
13 | 16 | 17 | 18 | 19 | { dropdownShown && 20 | 24 | 31 | 32 | } 33 |
34 | ) 35 | }; 36 | export default ElementSettings_dropdown; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/element_menu/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CreateElement_dropdown from './createElement_dropdown'; 3 | import ElementSettings_dropdown from "./elementSettings_dropdown"; 4 | 5 | const Element_menu = (props) => { 6 | return ( 7 |
8 | 14 | 15 | 17 |
18 | ) 19 | } 20 | export default Element_menu -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/dropdowns/toggle_button.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ToggleButton = (props) => { 4 | return ( 5 |
props.setDropdownShown(!props.dropdownShown)}> 6 | { props.children } 7 |
8 | ) 9 | } 10 | export default ToggleButton -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/index.js: -------------------------------------------------------------------------------- 1 | // General React and Redux 2 | import React, { useState, useEffect } from "react"; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | 5 | // Redux 6 | import { select_page, get_breadcrumb } from "../../../actions"; 7 | 8 | // Componenets 9 | import Loader from '../loader' 10 | import CoverPhoto from './cover_photo' 11 | import PageElements from './page_elements'; 12 | import Templates from "./templates"; 13 | import CreateElement_dropdown from './dropdowns/element_menu/createElement_dropdown'; 14 | import BodyHeader from "./body_header"; 15 | 16 | // React Router 17 | import { useParams, useOutletContext } from 'react-router-dom'; 18 | 19 | const Body = (props) => { 20 | const [state, setState] = useState({ 21 | isLoaded: false, 22 | not_found: false, 23 | page_elements: {}, 24 | }); 25 | const { pageId } = useParams(); 26 | 27 | // Redux 28 | const dispatch = useDispatch(); 29 | const selected_page = useSelector(state => state.selected_page); 30 | const pages = useSelector(state => state.pages); 31 | const current_user = useSelector(state => state.current_user); 32 | const breadcrumb = useSelector(state => state.breadcrumb); 33 | 34 | // Component props from Home 35 | const [toggle_menu, menu_shown] = useOutletContext(); 36 | 37 | useEffect(() => { 38 | const componentStart = async () => { 39 | setState({ ...state, isLoaded: false}) 40 | 41 | // Fetch the selected page 42 | const pageResponse = await dispatch(select_page(pageId)); 43 | 44 | // Check if page id in URL actually exists and the page was created by the current user 45 | if (!pageResponse || pageResponse === "page not found" || pageResponse.creator !== current_user.id) { 46 | setState({ ...state, isLoaded: true, not_found: true }) 47 | return 48 | } 49 | 50 | // Get page breadcrumb 51 | dispatch(get_breadcrumb(pageResponse, pages)); 52 | 53 | // Update state 54 | setState({ ...state, isLoaded: true }) 55 | } 56 | componentStart(); 57 | }, [pageId]) 58 | 59 | // Toggle page loading 60 | const isLoaded = (status) => { 61 | setState({ ...state, isLoaded: status }) 62 | } 63 | 64 | if (!state.isLoaded) return 65 | if (state.not_found) return

Page not found!

66 | 67 | return ( 68 |
69 | 73 | 74 |
75 | 76 | 77 |
78 | {/* Page title */} 79 |
80 |
81 | 88 |
89 | 90 |

{selected_page.name}

91 |
92 | 93 | {selected_page.page_elements.length === 0 && 94 | 97 | } 98 | 99 |
100 |
101 |
102 | ) 103 | } 104 | export default Body; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/other/deleteConfirmationModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DeleteConfirmationModal = (props) => { 4 | return ( 5 |
6 |
7 |

{props.message}

8 | 9 | 12 | 13 | 16 |
17 |
18 | ) 19 | } 20 | export default DeleteConfirmationModal; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/elements_list.js: -------------------------------------------------------------------------------- 1 | // Page elements 2 | import Heading_1 from './headings/heading_1'; 3 | import Heading_2 from './headings/heading_2'; 4 | import Text from './text'; 5 | import Kanban from './kanban'; 6 | import Page_link from './page_link'; 7 | import To_do from './to_do'; 8 | import Table from './table'; 9 | 10 | export const page_elements_list = { 11 | Heading_1, 12 | Heading_2, 13 | Text, 14 | Kanban, 15 | Page_link, 16 | To_do, 17 | Table, 18 | } -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/headings/heading_1.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | 3 | function Heading_1(props) { 4 | const myRef = useRef(); 5 | 6 | useEffect(() => { 7 | myRef.current.focus({ preventScroll: true }); 8 | }, []) 9 | 10 | // In case there is an element whose type is Heading_1 but there is no heading 1... 11 | if ((props.page_element.heading_1).length === 0) return null 12 | 13 | const [heading_1, set_heading_1] = useState(props.page_element.heading_1[0].heading_text) 14 | 15 | return ( 16 |
17 | set_heading_1(e.target.value)} 21 | name="heading_1" 22 | className={`user_input heading_1 ${props.page_element.color}`} 23 | placeholder="Heading 1" 24 | value={heading_1} 25 | onBlur={()=> props.edit_H1(props.page_element.heading_1[0].id, heading_1)} 26 | style={{ opacity: props.snapshot.isDragging? '0.5': '1' }}/> 27 |
28 | ) 29 | } 30 | export default Heading_1 -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/headings/heading_2.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | 3 | function Heading_2(props) { 4 | const myRef = useRef(); 5 | 6 | useEffect(() => { 7 | myRef.current.focus({ preventScroll: true }); 8 | }, []) 9 | 10 | // In case there is an element whose type is Heading_2 but there is no heading 2... 11 | if ((props.page_element.heading_2).length === 0) return null 12 | 13 | const [heading_2, set_heading_2] = useState(props.page_element.heading_2[0].heading_text) 14 | 15 | return ( 16 |
17 | set_heading_2(e.target.value)} 21 | name="heading_2" 22 | className={`user_input heading_2 ${props.page_element.color}`} 23 | placeholder="Heading 2" 24 | value={heading_2} 25 | onBlur={()=> props.edit_H2(props.page_element.heading_2[0].id, heading_2)} 26 | style={{ 27 | opacity: props.snapshot.isDragging? '0.5': '1' 28 | }}/> 29 |
30 | ) 31 | } 32 | export default Heading_2 -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/kanban/kanbanCard.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Draggable } from 'react-beautiful-dnd'; 3 | import TextareaAutosize from 'react-textarea-autosize'; 4 | 5 | function KanbanCard(props) { 6 | // State 7 | const [CardDescription, set_CardDescription] = useState(props.description); 8 | const [editCard, set_editCard] = useState(false); 9 | 10 | // Handle change 11 | const handleChange = (e) => set_CardDescription(e.target.value); 12 | 13 | return ( 14 | 15 | {provided => ( 16 |
18 | {editCard? { 26 | props.edit_card(props.card_id, CardDescription, props.kanban_id); 27 | set_editCard(false) 28 | }} 29 | resize="none" rows={1} wrap="soft" 30 | value={CardDescription} />: 31 | 32 |
set_editCard(true)}> 34 | {CardDescription? CardDescription:

Untitled

} 35 |
36 | } 37 | 38 |

39 | props.delete_card(props.card_id, props.group_id, props.kanban_id)}> 40 | × 41 |

42 |
43 | )} 44 |
45 | ) 46 | } 47 | export default KanbanCard -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/kanban/kanbanGroup.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import GroupDropdown from './kanban_dropdowns'; 3 | import KanbanCard from './kanbanCard'; 4 | import { Droppable, Draggable } from 'react-beautiful-dnd'; 5 | import AddIcon from '@material-ui/icons/Add'; 6 | 7 | function KanbanGroup(props) { 8 | // State 9 | const [groupName, set_GroupName] = useState(props.group.name) 10 | const [edit_groupName, set_edit_GroupName] = useState(false) 11 | 12 | // Change group name 13 | const input_blur = (e) => { 14 | if(e.key === 'Enter' || e.type === "blur") { 15 | set_edit_GroupName(false) 16 | props.edit_groupName(props.group.id, groupName) 17 | } 18 | } 19 | 20 | // Handle change 21 | const handleChange = (e) => set_GroupName(e.target.value); 22 | 23 | return ( 24 |
25 | 26 | {provided => ( 27 |
28 |
29 | 30 |
31 | {edit_groupName === false? 32 |
33 |

set_edit_GroupName(true)}>{groupName} 35 |

36 |
: 37 | 38 | } 40 |

{props.group.kanban_card.length}

41 |
42 | 43 | 48 |
49 | 50 | 51 | {provided => ( 52 |
53 | {props.group.kanban_card.map((card, index) => { 54 | return ( 55 | 64 | ) 65 | })} 66 | {provided.placeholder} 67 |
Placeholder
68 |
69 | )} 70 |
71 | 79 |
80 | )} 81 |
82 |
83 | ) 84 | } 85 | export default KanbanGroup -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/kanban/kanban_dropdowns/dropdown_content.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import { connect } from "react-redux"; 3 | import { change_color } from "../../../../../../actions/kanban"; 4 | import ColorDropdown from "../../../dropdowns/color_dropdown"; 5 | 6 | function GroupDropdown_content(props) { 7 | // Change color 8 | const change_color = (color) => { 9 | props.change_color(props.group_id, color, props.kanban_id) 10 | } 11 | 12 | return ( 13 |
14 | props.openModal()}> 15 | 16 | Delete 17 | 18 |
19 | 22 |
23 | ) 24 | } 25 | export default connect(null, { change_color })(GroupDropdown_content) -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/kanban/kanban_dropdowns/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import GroupDropdown_content from "./dropdown_content"; 3 | import { connect } from "react-redux"; 4 | import { delete_group } from "../../../../../../actions/kanban"; 5 | import DeleteConfirmationModal from "../../../other/deleteConfirmationModal"; 6 | import ToggleButton from "../../../dropdowns/toggle_button"; 7 | import DropdownContainer from "../../../dropdowns/dropdown_container"; 8 | 9 | const GroupDropdown = (props) => { 10 | const [dropdownShown, setDropdownShown] = useState(false); 11 | const [modalShown, setModalShown] = useState(false); 12 | const dropdownRef = useRef(null); 13 | 14 | // Open delete confirmation modal 15 | const openModal = () => { 16 | setDropdownShown(false); 17 | setModalShown(true); 18 | } 19 | 20 | // Delete kanban group 21 | const delete_group = () => { 22 | props.delete_group(props.kanban_id, props.group_id); // Redux function 23 | setModalShown(false); 24 | props.set_GroupName(''); 25 | } 26 | 27 | return ( 28 |
29 | 32 | 33 | 34 | 35 | {dropdownShown && 36 | 41 | 46 | } 47 | 48 | {/* Delete confirmation modal */} 49 | {modalShown && 50 | } 54 |
55 | ) 56 | } 57 | export default connect(null, { delete_group })(GroupDropdown); -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/page_link.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { delete_element } from '../../../../actions'; 3 | import { connect } from 'react-redux'; 4 | 5 | function Page_link(props) { 6 | const page = props.page_element.page_link[0] 7 | 8 | if (page) 9 | return ( 10 | 12 |
13 | 14 |

{page.page_name}

15 |
16 |
17 | ) 18 | props.delete_element(props.page_element) 19 | return null 20 | } 21 | 22 | const mapStateToProps = (state) => { 23 | return { 24 | selected_page: state.selected_page 25 | } 26 | } 27 | 28 | export default connect(mapStateToProps, { delete_element })(Page_link); -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table-dropdowns/column_options/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import TableColumn_dropdownContent from "./dropdown_content"; 3 | import ToggleButton from "../../../../dropdowns/toggle_button"; 4 | import DeleteConfirmationModal from "../../../../other/deleteConfirmationModal"; 5 | import DropdownContainer from "../../../../dropdowns/dropdown_container"; 6 | 7 | const TableColumn_dropdown = (props) => { 8 | const [dropdownShown, setDropdownShown] = useState(false); 9 | const [modalShown, setModalShown] = useState(false); 10 | const dropdownRef = useRef(null); 11 | 12 | const deleteColumn = () => { 13 | setModalShown(false); 14 | props.deleteColumn(props.index); 15 | } 16 | 17 | return ( 18 |
19 | 22 | 23 | 24 | 25 | {dropdownShown && 26 | 32 | 33 | 44 | } 45 | 46 | {/* Delete column confirmation modal */} 47 | {modalShown && 48 | } 52 |
53 | ) 54 | }; 55 | 56 | export default TableColumn_dropdown; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table-dropdowns/column_options/number-icon.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function NumberIcon() { 4 | return 5 | } 6 | export default NumberIcon; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table-dropdowns/column_options/property-type-dropdown.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import SubjectIcon from '@material-ui/icons/Subject'; 4 | import ListIcon from '@material-ui/icons/List'; 5 | import EventNoteIcon from '@material-ui/icons/EventNote'; 6 | import CheckBoxOutlinedIcon from '@material-ui/icons/CheckBoxOutlined'; 7 | import LinkIcon from '@material-ui/icons/Link'; 8 | import { change_property } from '../../../../../../../actions/table'; 9 | 10 | function PropertyType_dropdown(props) { 11 | return ( 12 |
13 | {props.setDropdownShown(false); 14 | props.change_property('Text', props.col_index, props.table, props.table_index)}}> 15 | 16 | 17 | 18 | 19 |

Text

20 |
21 | {props.setDropdownShown(false); 22 | props.change_property('Number', props.col_index, props.table)}}> 23 | 24 | 25 |

Number

26 |
27 | {props.setDropdownShown(false); 28 | props.change_property('Multi_select', props.col_index, props.table)}}> 29 | 30 | 31 | 32 | 33 |

Multi-select

34 |
35 | {props.setDropdownShown(false); 36 | props.change_property('Date', props.col_index, props.table)}}> 37 | 38 | 39 | 40 | 41 |

Date

42 |
43 | {props.setDropdownShown(false); 44 | props.change_property('Checkbox', props.col_index, props.table)}}> 45 | 46 | 47 | 48 | 49 |

Checkbox

50 |
51 | {props.setDropdownShown(false); 52 | props.change_property('URL', props.col_index, props.table)}}> 53 | 54 | 55 | 56 | 57 |

URL

58 |
59 |
60 | ) 61 | } 62 | export default connect(null, { change_property }) (PropertyType_dropdown) -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table-dropdowns/multi_select/multi-select-popup.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import TagOption_dropdown from "./tag_options"; 3 | import { connect } from "react-redux"; 4 | import { remove_tag, add_tag_to_cell, add_tag } from "../../../../../../../actions/table"; 5 | 6 | const MultiSelect_PopUp = (props) => { 7 | const [inputValue, changeInputValue] = useState("") 8 | const [headCell, setHeadCell] = useState(props.tables[props.table_index].rows[0].data[props.col_index]) 9 | const [data, setData] = useState(props.tables[props.table_index].rows[props.row_index + 1].data[props.col_index]) 10 | 11 | return ( 12 |
13 | {/* Tag input - where you can type in new tags + remove tags */} 14 |
15 | {data.tags.map((tag, tag_index) => { 16 | return ( 17 |
18 | {tag.name} 19 | 20 | props.remove_tag(data.id, tag, props.table_index, 21 | props.row_index, props.col_index, tag_index, props.table)}> 22 | × 23 | 24 |
25 | ) 26 | })} 27 | 0? "": "Select or create an option..."} 29 | value={inputValue} 30 | onChange={(e) => changeInputValue(e.target.value)} 31 | onBlur={() => { 32 | changeInputValue(""); 33 | props.add_tag(inputValue, headCell, data.id, props.table_index, props.row_index, 34 | props.col_index, props.table); 35 | }} 36 | onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur(); }}> 37 | 38 |
39 | 40 | {/* List of all tags with option on each tag to change color/name */} 41 |
42 |

Select an option or create one

43 |
44 | {headCell.tag_heads.map(tag => { 45 | return ( 46 |
49 | props.add_tag_to_cell(tag, data.id, props.table_index, 50 | props.row_index, props.col_index, e.target, props.table 51 | )}> 52 | 53 |
54 | {tag.name} 55 |
56 | 61 |
62 | ) 63 | })} 64 |
65 |
66 |
67 | ) 68 | } 69 | const mapStateToProps = (state) => { 70 | return { 71 | tables: state.tables, 72 | } 73 | } 74 | export default connect(mapStateToProps, { remove_tag, add_tag_to_cell, add_tag })(MultiSelect_PopUp) -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table-dropdowns/multi_select/tag_options/dropdown_content.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { connect } from "react-redux"; 3 | import { edit_tag_name, delete_tag, change_tag_color } from "../../../../../../../../actions/table"; 4 | import ColorDropdown from "../../../../../dropdowns/color_dropdown"; 5 | 6 | function TagOptions_dropdownContent(props) { 7 | const [tagName, set_tagName] = useState(props.tag.name) 8 | 9 | // Change tag color 10 | const change_color = (color) => { 11 | props.change_tag_color(props.tag.id, color, props.table_index, props.col_index) 12 | } 13 | 14 | return ( 15 |
16 | {/* Change tag name */} 17 | set_tagName(e.target.value)} 21 | onBlur={() => { 22 | props.edit_tag_name(tagName, props.tag.id, props.table_index, props.col_index) 23 | }} 24 | onKeyDown={(e) => { 25 | if (e.key === 'Enter') { 26 | e.target.blur(); 27 | props.setDropdownShown(false); 28 | } 29 | }} /> 30 | 31 | {/* Delete tag */} 32 | props.delete_tag(props.tag.id, props.table_index, 33 | props.col_index)}> 34 | 35 | 36 | Delete 37 | 38 | 39 | {/* Horizontal rule */} 40 |
41 | 42 | {/* Colors */} 43 | 46 |
47 | ) 48 | } 49 | export default connect(null, { edit_tag_name, delete_tag, change_tag_color })(TagOptions_dropdownContent) -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table-dropdowns/multi_select/tag_options/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import TagOptions_dropdownContent from "./dropdown_content" 3 | import DropdownContainer from "../../../../../dropdowns/dropdown_container"; 4 | 5 | function TagOption_dropdown(props) { 6 | const [dropdownShown, setDropdownShown] = useState(false) 7 | 8 | return ( 9 |
10 | setDropdownShown(!dropdownShown)} /> 11 | {dropdownShown && 12 | 16 | 22 | } 23 |
24 | ) 25 | } 26 | export default TagOption_dropdown; -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table-dropdowns/row_options/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import { connect } from "react-redux"; 3 | import { delete_row } from "../../../../../../../actions/table"; 4 | import DropdownContainer from "../../../../dropdowns/dropdown_container"; 5 | import DragIndicatorIcon from "@material-ui/icons/DragIndicator"; 6 | 7 | const TableRow_dropdown = (props) => { 8 | const [dropdownShown, setDropdownShown] = useState(false); 9 | const dropdownRef = useRef(null); 10 | return ( 11 |
12 | 13 | setDropdownShown(!dropdownShown)}/> 14 | 15 | 16 | {dropdownShown && 17 | 22 | {props.delete_row(props.row_id, props.table_index, 23 | props.row_index, props.table); props.setOpen(false); }}> 24 | 25 |

Delete row

26 |
27 |
} 28 |
29 | ) 30 | } 31 | export default connect(null, { delete_row })(TableRow_dropdown) -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_data/checkbox.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import CheckIcon from '@material-ui/icons/Check'; 3 | 4 | export default function TableData_checkbox(props) { 5 | const [checkbox, setCheckbox] = useState(props.data.checkbox); 6 | 7 | return ( 8 |
9 | {/* Checkbox empty */} 10 | { 14 | setCheckbox(e.target.checked); 15 | props.edit_cell(props.data.property_type, e.target.checked, props.data.id); 16 | }} 17 | /> 18 | 19 | {/* Checkbox checked */} 20 | {checkbox === true && 21 | { 22 | setCheckbox(false); 23 | props.edit_cell(props.data.property_type, false, props.data.id); 24 | }}> 25 | 26 | } 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_data/date.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { MuiPickersUtilsProvider, DateTimePicker } from '@material-ui/pickers'; 3 | import DateFnsUtils from '@date-io/date-fns'; 4 | 5 | export default function TableData_date(props) { 6 | const [date, setDate] = useState(props.data.date) 7 | return ( 8 |
9 | 10 | { 14 | setDate(e); 15 | props.edit_cell(props.data.property_type, e, props.data.id); 16 | }} 17 | fullWidth={true} 18 | InputProps={{ 19 | disableUnderline: true, 20 | }} 21 | /> 22 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_data/multi_select.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react' 2 | import DropdownContainer from "../../../dropdowns/dropdown_container"; 3 | import MultiSelect_PopUp from "../table-dropdowns/multi_select/multi-select-popup"; 4 | 5 | export default function TableData_multi_select(props) { 6 | const [multiSelectPopupShown, setMultiSelectPopupShown] = useState(false); 7 | const dropdownRef = useRef(null); 8 | 9 | return ( 10 |
11 | 12 | {/* Shows the tags in the table cell */} 13 |
14 | {props.tables[props.table_index].rows[props.row_index + 1].data[props.index].tags.map( 15 | tag => { 16 | return ( 17 |
18 | {tag.name} 19 |
20 | ) 21 | })} 22 |
23 | 24 | {/* Popup for multi-select tags */} 25 |
setMultiSelectPopupShown(true)} 27 | ref={dropdownRef}> 28 | 29 | {multiSelectPopupShown && 30 | 38 | 39 | 44 | 45 | } 46 |
47 |
48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_data/number.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | export default function TableData_number(props) { 4 | const [num, setNum] = useState(props.data.number) 5 | return ( 6 | setNum(e.target.value)} 11 | onBlur={() => props.edit_cell(props.data.property_type, num, props.data.id)} 12 | onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur(); }} 13 | /> 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_data/text.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import TextareaAutosize from 'react-textarea-autosize'; 3 | 4 | function TableData_text(props) { 5 | const [text, setText] = useState(props.data.text) 6 | 7 | return ( 8 | setText(e.target.value)} 13 | onBlur={() => props.edit_cell(props.data.property_type, text, props.data.id)} 14 | onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur(); }} 15 | resize="none" rows={1} wrap="soft" /> 16 | ) 17 | } 18 | export default TableData_text; 19 | -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_data/url.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from 'react'; 2 | import URL from '@material-ui/icons/Link'; 3 | 4 | export default function TableData_url(props) { 5 | const [url, setURL] = useState(props.data.url) 6 | const [urlInput, set_urlInput] = useState(false) 7 | const [urlButton_opacity, set_urlButton_opacity] = useState(100) 8 | const myRef = useRef(null); 9 | 10 | return ( 11 |
set_urlButton_opacity(100)} 13 | onMouseLeave={() => set_urlButton_opacity(100)} 14 | ref={myRef}> 15 |

set_urlInput(true)}> 16 | {url? url:  } 17 |

18 | 19 | {/* Input for changing the URL */} 20 | {urlInput === true && 21 |
22 | setURL(e.target.value)} 33 | onBlur={() => { 34 | props.edit_cell(props.data.property_type, url, props.data.id); 35 | set_urlInput(false); 36 | }} 37 | onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur(); }} 38 | /> 39 |
} 40 | 41 | {/* Button for clicking on URL. Only shown if url exists and user input contains a '.' */} 42 | {url && 43 | url.includes(".") && 44 | 48 | 49 | 50 | } 51 |
52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TableData from "./table_data"; 3 | import DragIndicatorIcon from '@material-ui/icons/DragIndicator'; 4 | 5 | function TableHeader(props) { 6 | return ( 7 |
8 | {props.row.data.map((data, index) => { 9 | return 21 | })} 22 |
23 | + 24 |
25 |
26 |
27 | ) 28 | } 29 | export default TableHeader -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/table/table_row.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TableData from "./table_data"; 3 | import { Draggable } from "react-beautiful-dnd"; 4 | import TableRow_dropdown from "./table-dropdowns/row_options"; 5 | import AddIcon from '@material-ui/icons/Add'; 6 | 7 | function TableRow(props) { 8 | return ( 9 | 10 | {(provided, snapshot) => ( 11 |
12 | {props.row.data.map((data, index) => { 13 | return 23 | })} 24 | 25 | {/* Row buttons */} 26 |
27 | props.addRow(props.row_index)}> 28 | 29 | 30 | 31 |
32 | 37 |
38 |
39 |
40 | )} 41 |
42 | ) 43 | } 44 | export default TableRow -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/text.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import TextareaAutosize from 'react-textarea-autosize'; 3 | 4 | function Text(props) { 5 | 6 | // In case there is an element whose type is text but there is no text... 7 | if ((props.page_element.text).length === 0) return null 8 | 9 | const [text, set_text] = useState(props.page_element.text[0].text) 10 | 11 | return ( 12 |
13 | set_text(e.target.value)} value={text} 17 | placeholder="Type your text here..." 18 | autoFocus 19 | className="text" 20 | onBlur={()=> props.edit_text(props.page_element.text[0].id, text)} 21 | style={{ opacity: props.snapshot.isDragging? '0.5': '1' }} /> 22 |
23 | ) 24 | } 25 | export default Text -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/page_elements/to_do.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import { connect } from 'react-redux'; 3 | import { change_tickBox, change_description } from "../../../../actions/to_do" 4 | import CheckIcon from '@material-ui/icons/Check'; 5 | 6 | function To_do(props) { 7 | const myRef = useRef(); 8 | 9 | useEffect(() => { 10 | myRef.current.focus({ preventScroll: true }); 11 | }, []) 12 | 13 | // In case there is an element whose type is to-do but there is no to-do... 14 | if ((props.page_element.to_do).length === 0) return null 15 | 16 | const to_do = props.page_element.to_do[0] 17 | 18 | // State 19 | const [description, set_description] = useState(to_do.description) 20 | const [complete, change_complete] = useState(to_do.completed) 21 | 22 | // Toggle check box 23 | const toggleCheckBox = (status) => { 24 | change_complete(status) 25 | change_tickBox(status, to_do.id) 26 | } 27 | 28 | return ( 29 |
31 | 32 | {/* Checkbox */} 33 |
34 | 35 | {/* Checkbox empty */} 36 | toggleCheckBox(true)}/> 37 | 38 | {/* Checkbox checked */} 39 | {complete === true? 40 | { 41 | toggleCheckBox(false); 42 | }}> 43 | 44 | : null} 45 |
46 | 47 | {/* To-do text */} 48 | set_description(e.target.value)} 52 | onBlur={() => change_description(description, to_do.id)} value={description? description: ""} 53 | placeholder="To-do" className={complete === false? "user_input": "user_input completed"} 54 | onKeyDown={(e) => { if(e.key === 'Enter') e.target.blur(); }} /> 55 |
56 | ) 57 | } 58 | 59 | const mapStateToProps = (state) => { 60 | return { 61 | selected_page: state.selected_page 62 | } 63 | } 64 | 65 | export default connect(mapStateToProps, { change_tickBox, change_description })(To_do); -------------------------------------------------------------------------------- /frontend/src/components/home/body_content/templates/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState } from 'react' 3 | import TemplatesContent from "./templates_content"; 4 | 5 | function Templates(props) { 6 | const [templatesModal, setTemplatesModal] = useState(false) 7 | return ( 8 |
9 |
10 |
11 | Hover over the page title and click the plus sign to add elements to the page. 12 |
13 | 14 |
15 | You can also choose a page layout from one of our pre-designed 16 | setTemplatesModal(true)}> 17 | 18 | templates. 19 | 20 |
21 |
22 | 23 | {templatesModal && 24 | } 28 |
29 | ) 30 | } 31 | export default Templates; 32 | -------------------------------------------------------------------------------- /frontend/src/components/home/home.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Navigate, Outlet, useParams } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import Menu from "./menu"; 5 | import { fetch_pages } from "../../actions"; 6 | import { get_user_data, logout } from "../../actions/LogIn_out_register"; 7 | import Loader from "./loader"; 8 | import { motion, AnimatePresence } from "framer-motion"; 9 | 10 | function Home(props) { 11 | const [state, setState] = useState({ 12 | isLoaded: false, 13 | isLoggedIn: false, 14 | menu_shown: true 15 | }) 16 | const { pageId } = useParams(); 17 | 18 | useEffect(() => { 19 | componentStart(); 20 | }, []); 21 | 22 | const componentStart = async () => { 23 | let userResponse; 24 | 25 | // Check if user is logged in (by seeing if there is a token in local storage) 26 | if (localStorage.getItem('token')) { 27 | try { 28 | userResponse = await props.get_user_data() 29 | } catch { 30 | // If token is wrong then return back to login and clear token from localstorage 31 | localStorage.setItem('token', "") 32 | setState({ ...state, isLoaded: true }) 33 | return 34 | } 35 | // Fetch the pages and update state 36 | await props.fetch_pages(userResponse.id) 37 | setState({ ...state, isLoaded: true, isLoggedIn: true }) 38 | } else { 39 | setState({ ...state, isLoaded: true }) 40 | } 41 | } 42 | 43 | const logout = () => { 44 | // Change state to false so that we redirect to log in page. 45 | setState({ ...state, isLoggedIn: false }) 46 | 47 | // Call logout redux action 48 | props.logout() 49 | } 50 | 51 | const toggle_menu = (status) => { 52 | setState({ ...state, menu_shown: status }) 53 | } 54 | 55 | if (!state.isLoaded) return 56 | if (!state.isLoggedIn) return 57 | if (!pageId) return 58 | 59 | return ( 60 |
61 | {/* Menu */} 62 | 63 | {state.menu_shown && 64 | 71 | 75 | } 76 | 77 | 78 | {/* Body */} 79 | 80 |
81 | ) 82 | } 83 | 84 | const mapStateToProps = (state) => { 85 | return { pages: state.pages, current_user: state.current_user } 86 | } 87 | 88 | export default connect(mapStateToProps, { 89 | fetch_pages, 90 | get_user_data, 91 | logout 92 | })(Home); -------------------------------------------------------------------------------- /frontend/src/components/home/loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CircularProgress from '@material-ui/core/CircularProgress'; 3 | 4 | function Loader() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | export default Loader -------------------------------------------------------------------------------- /frontend/src/components/home/menu/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { connect } from 'react-redux'; 3 | import { add_page, delete_page } from "../../../actions/page_menu" 4 | import QuickFind from "./quick_find"; 5 | import MenuHeader from "./menu_header"; 6 | import ListOfPages from "./list_of_pages"; 7 | import AddIcon from '@material-ui/icons/Add'; 8 | 9 | function Menu(props) { 10 | const [pages, setPages] = useState(props.pages) 11 | const [quickFindShown, setQuickFindShown] = useState(false) 12 | 13 | // Open or close the page folder 14 | const openClosePages = (pageID) => { 15 | let newPages = [...pages] 16 | const page = findPage(newPages, pageID) 17 | if (!page) return 18 | 19 | // Open page 20 | if (page.closed) { 21 | page.closed = false 22 | 23 | // Close page 24 | } else { 25 | page.closed = true 26 | page.children = closePages(page.children) 27 | } 28 | setPages(newPages) 29 | } 30 | 31 | const findPage = (pages, pageID) => { 32 | for (const page of pages) { 33 | if (page.id === pageID) return page 34 | const result = findPage(page.children, pageID) 35 | if (result) return result 36 | } 37 | return null 38 | } 39 | 40 | const closePages = (pages) => { 41 | for (const page of pages) { 42 | page.closed = true 43 | page.children = closePages(page.children) 44 | } 45 | return pages 46 | } 47 | 48 | // Add a new page 49 | const addPage = async (parentID, depth) => { 50 | 51 | // Create a new page 52 | const newPage = await props.add_page(parentID, props.current_user.id) 53 | newPage.depth = depth 54 | 55 | // Append new page to state 56 | let newPages = [...pages] 57 | if (parentID) { 58 | const parentPage = findPage(newPages, parentID) 59 | parentPage.children = [...parentPage.children, newPage] 60 | 61 | // Open parent page 62 | if (parentPage.closed) openClosePages(parentID) 63 | } else { 64 | newPages = [...newPages, newPage] 65 | } 66 | setPages(newPages) 67 | } 68 | 69 | // Change page name on menu 70 | const changeNameOnMenu = (pageID, pageName) => { 71 | 72 | // Find page 73 | let newPages = [...pages] 74 | const page = findPage(newPages, pageID) 75 | 76 | // Change name and update state 77 | page.name = pageName 78 | setPages(newPages) 79 | } 80 | 81 | // Delete page 82 | const deletePage = async (pageID, parentID) => { 83 | 84 | // Delete from database 85 | await props.delete_page(pageID) 86 | 87 | // Update state 88 | let newPages = [...pages] 89 | if (parentID) { 90 | let page = findPage(newPages, parentID) 91 | page.children = page.children.filter(page => page.id !== pageID) 92 | } else { 93 | newPages = newPages.filter(page => page.id !== pageID) 94 | } 95 | setPages(newPages) 96 | } 97 | 98 | return ( 99 |
100 | {/* Menu Header */} 101 | 104 | 105 | {/* Quick Find */} 106 |
setQuickFindShown(true)}> 107 | 108 |

Quick Find

109 |
110 | 111 | {quickFindShown && 112 | 116 | } 117 | 118 | {/* User pages */} 119 |
120 | 130 | 131 | {/* Add a page */} 132 |
addPage(null, 0)}> 133 | 134 |

Add a page

135 |
136 |
137 | 138 | {/* Logout */} 139 |
props.logout()} className="logout"> 140 | 141 |

Logout

142 |
143 |
144 | ) 145 | } 146 | 147 | const mapStateToProps = (state) => { 148 | return { current_user: state.current_user, selected_page: state.selected_page } 149 | } 150 | 151 | export default connect(mapStateToProps, { add_page, delete_page })(Menu); -------------------------------------------------------------------------------- /frontend/src/components/home/menu/list_of_pages.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PageNav from "./page-nav"; 3 | 4 | const ListOfPages = (props) => { 5 | return ( 6 | props.pages.map(page => { 7 | if (!props.parentClosed) 8 | return ( 9 |
10 | 20 | 21 | {page.children && 22 | } 32 |
33 | ) 34 | }) 35 | ) 36 | } 37 | export default ListOfPages; -------------------------------------------------------------------------------- /frontend/src/components/home/menu/menu_header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function MenuHeader(props) { 4 | return ( 5 |
6 |
7 | {props.current_user.first_name.charAt(0)} 8 |
9 |
10 |

{props.current_user.first_name.charAt(0).toUpperCase()} 11 | {props.current_user.first_name.substring(1)}'s Notion

12 |
13 |
props.toggle_menu(false)}> 14 |
15 |
16 |
17 |
18 | ) 19 | } -------------------------------------------------------------------------------- /frontend/src/components/home/menu/page-nav.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from 'react-router-dom'; 3 | import MenuDropdown from "./menu_dropdown"; 4 | 5 | function PageNav(props) { 6 | const navigate = useNavigate(); 7 | 8 | return ( 9 |
{ 12 | if (e.target.className === "page_name" || e.target.className === "page-title" 13 | || e.target.className === "page" || e.target.className === "far fa-file-alt") { 14 | navigate(`/${props.page.id}`); 15 | if (screen.width <= 725) props.toggle_menu(false); 16 | } 17 | }}> 18 | 19 | {/* Open/close arrow */} 20 |
props.openClosePages(props.page.id)}> 21 |
22 |
23 | 24 | {/* File icon */} 25 | 26 | 27 | {/* Page name */} 28 |
29 |

{props.page.name || "Untitled"}

30 |
31 | 32 | {/* Dropdown */} 33 | 38 |
) 39 | } 40 | export default PageNav -------------------------------------------------------------------------------- /frontend/src/components/home/menu/quick_find/index.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from "react"; 2 | import { useNavigate } from 'react-router-dom'; 3 | import QuickFindPages from "./quick_find_pages"; 4 | 5 | function QuickFind(props) { 6 | const firstName = props.current_user.first_name 7 | const [searchValue, set_searchValue] = useState("") 8 | const node = useRef(); 9 | const navigate = useNavigate(); 10 | 11 | // Adding event listener to detect clicks outside the modal 12 | useEffect(() => { 13 | document.addEventListener("mousedown", handleClick); 14 | 15 | return () => { 16 | document.removeEventListener("mousedown", handleClick); 17 | }; 18 | }, []); 19 | 20 | const handleClick = e => { 21 | // If user clicked outside modal - hide it 22 | if (node.current === e.target) { 23 | set_searchValue(""); 24 | props.setQuickFindShown(false); 25 | } 26 | } 27 | 28 | return ( 29 |
30 |
31 |
32 | 33 | set_searchValue(e.target.value)}> 37 | 38 |
39 | 40 | {searchValue !== "" && 41 |
42 | 46 |
47 | } 48 |
49 |
50 | ) 51 | } 52 | export default QuickFind -------------------------------------------------------------------------------- /frontend/src/components/home/menu/quick_find/quick_find_pages.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useNavigate } from 'react-router-dom'; 3 | 4 | export default function QuickFindPages(props) { 5 | const navigate = useNavigate(); 6 | return ( 7 |
8 | {props.pages.map(page => { 9 | return ( 10 |
11 | {page.name.toUpperCase().startsWith(props.searchValue.toUpperCase()) && 12 |
{ 13 | navigate(`/${page.id}`); 14 | props.setQuickFindShown(false); 15 | }}> 16 | 17 | {page.name} 18 |
19 | } 20 | 21 | {/* Child pages */} 22 | 26 |
27 | ) 28 | })} 29 |
30 | ) 31 | } -------------------------------------------------------------------------------- /frontend/src/components/login.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { login, logout } from '../actions/LogIn_out_register'; 3 | import { useNavigate, Link } from 'react-router-dom'; 4 | import { connect } from 'react-redux'; 5 | import NotionLogo from './notion-logo'; 6 | import { Alert } from '@material-ui/lab'; 7 | 8 | function Login(props) { 9 | const [ state, setState ] = useState({ 10 | email: 'guest@example.com', 11 | password: 'password', 12 | incorrect_credentials: false, 13 | }); 14 | const navigate = useNavigate(); 15 | 16 | // Check if user is logged in. If so then log them out. 17 | useEffect(() => { 18 | if (localStorage.getItem('token')) { 19 | props.logout() 20 | } 21 | }, []) 22 | 23 | const handle_change = (event) => { 24 | setState({ 25 | ...state, 26 | [event.target.name]: event.target.value 27 | }); 28 | } 29 | 30 | const handle_submit = async (e) => { 31 | e.preventDefault(); 32 | 33 | // Log the user in through a redux action 34 | const response = await props.login(state.email, state.password) 35 | 36 | // Check if credentials are correct 37 | if (response === "Incorrect Credentials") { 38 | setState({ ...state, incorrect_credentials: true, email: '', password: '' }) 39 | } else { 40 | // Navigate to home page 41 | navigate("/"); 42 | } 43 | } 44 | 45 | return ( 46 |
47 | 48 | {/* Incorrect Credentials Alert */} 49 | {state.incorrect_credentials && 50 | setState({ ...state, incorrect_credentials: false })}> 51 | Email or password is incorrect! 52 | 53 | } 54 | 55 | {/* Notion logo */} 56 | 57 | 58 | {/* Log in form */} 59 |
60 |
61 |

Log in

62 | 63 | 64 | 70 |
71 | 72 | 73 | 79 |
80 | 81 | 82 |
83 |

Don't have an account?

84 | Sign up 85 |
86 |
87 |
88 |
89 | ) 90 | } 91 | export default connect(null, { 92 | login, 93 | logout 94 | })(Login); -------------------------------------------------------------------------------- /frontend/src/components/notion-logo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function NotionLogo() { 4 | return ( 5 |
6 | 7 | 24 | 25 |

Notion

26 |
27 | ) 28 | } 29 | export default NotionLogo -------------------------------------------------------------------------------- /frontend/src/components/register.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { register } from '../actions/LogIn_out_register'; 3 | import { add_page } from '../actions/page_menu'; 4 | import { connect } from 'react-redux'; 5 | import { useNavigate, Link } from 'react-router-dom'; 6 | import NotionLogo from './notion-logo'; 7 | import { Alert } from '@material-ui/lab'; 8 | 9 | function Register(props) { 10 | const [state, setState] = useState({ 11 | first_name: '', 12 | last_name: '', 13 | email: '', 14 | password: '', 15 | confirm_password: '', 16 | alert: '' 17 | }); 18 | const navigate = useNavigate(); 19 | 20 | const handle_change = (event) => { 21 | setState({ 22 | ...state, 23 | [event.target.name]: event.target.value 24 | }); 25 | } 26 | 27 | const handleSubmit = async (e) => { 28 | e.preventDefault(); 29 | 30 | // Check if passwords match 31 | if (state.password !== state.confirm_password) { 32 | setState({ 33 | ...state, 34 | alert: "Passwords do not match!" 35 | }) 36 | return 37 | } 38 | 39 | // Register user through Redux action 40 | const response = await props.register(state.first_name, state.last_name, 41 | state.email, state.password) 42 | 43 | // Check if email already exists 44 | if (response === "user with this email already exists.") { 45 | setState({ ...state, alert: "A user with this email already exists" }) 46 | } else { 47 | // Create a new page for the user 48 | await props.add_page(null, response.user.id) 49 | 50 | // Redirect to home page 51 | navigate("/"); 52 | } 53 | } 54 | 55 | return( 56 |
57 | {/* Incorrect Credentials Alert */} 58 | {state.alert !== "" && 59 | setState({ ...state, alert: "" })}> 60 | {state.alert} 61 | 62 | } 63 | 64 | {/* Notion logo */} 65 | 66 | 67 | {/* Sign up form */} 68 |
69 |
70 |

Sign up

71 | 72 | 73 |
75 | 76 | 77 |
79 | 80 | 81 |
83 | 84 | 85 |
87 | 88 | 89 |
91 | 92 | 93 | 94 |
95 |

Already have an account?

96 | Log In 97 |
98 |
99 |
100 |
101 | ) 102 | } 103 | export default connect(null, { 104 | register, 105 | add_page, 106 | })(Register); -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './components/app'; 4 | 5 | // Good guide on how to publish react + django to heroku: https://dev.to/mdrhmn/deploying-react-django-app-using-heroku-2gfa 6 | 7 | // Redux 8 | import { Provider } from 'react-redux'; 9 | import { createStore, applyMiddleware, compose } from 'redux'; 10 | import reducers from './reducers'; 11 | import thunk from 'redux-thunk'; 12 | 13 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 14 | 15 | const root = ReactDOM.createRoot(document.getElementById("root")); 16 | root.render( 17 | 18 | 19 | 20 | ); -------------------------------------------------------------------------------- /frontend/static/frontend/0dcd3c3b86c8a552563e30fcd91f7a53.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/0dcd3c3b86c8a552563e30fcd91f7a53.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/0fd2ca50c6317bb99d04953eca1d21b8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/0fd2ca50c6317bb99d04953eca1d21b8.png -------------------------------------------------------------------------------- /frontend/static/frontend/14744af249126b8822bbbc973a763a3b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/14744af249126b8822bbbc973a763a3b.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/1956a50ca86ebc95c55a17d1897a9b28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/1956a50ca86ebc95c55a17d1897a9b28.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/228d99e89139cf4e0e17ab41848beb6c.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/228d99e89139cf4e0e17ab41848beb6c.jpeg -------------------------------------------------------------------------------- /frontend/static/frontend/231105f2c0dddc5d1bd3895d1f17ed06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/231105f2c0dddc5d1bd3895d1f17ed06.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/243da70ef07be091abdb33333f93afc7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/243da70ef07be091abdb33333f93afc7.png -------------------------------------------------------------------------------- /frontend/static/frontend/2e9bcdcacceac7178acf756ade92407f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/2e9bcdcacceac7178acf756ade92407f.png -------------------------------------------------------------------------------- /frontend/static/frontend/40030e653dff791483f1abc2145834fd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/40030e653dff791483f1abc2145834fd.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/4c4460cd9678d6361066f496c2c80e89.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/4c4460cd9678d6361066f496c2c80e89.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/568c5fce08dbfa6cc8ca8d7a46eafbdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/568c5fce08dbfa6cc8ca8d7a46eafbdb.png -------------------------------------------------------------------------------- /frontend/static/frontend/58380460e7ba563fdcd2c05beea7b352.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/58380460e7ba563fdcd2c05beea7b352.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/5d2abc6dcf2399fc533d805a85220b2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/5d2abc6dcf2399fc533d805a85220b2d.png -------------------------------------------------------------------------------- /frontend/static/frontend/5f0a79e93e318d2b26ed5d16073af4d2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/5f0a79e93e318d2b26ed5d16073af4d2.jpeg -------------------------------------------------------------------------------- /frontend/static/frontend/639ebd5fa707a5501c425f0385c9f9bf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/639ebd5fa707a5501c425f0385c9f9bf.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/768fb4a529f09ac24c2230b9af8f558a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/768fb4a529f09ac24c2230b9af8f558a.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/7a749025257f71b71d74c60ff96efcaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/7a749025257f71b71d74c60ff96efcaf.png -------------------------------------------------------------------------------- /frontend/static/frontend/912362a80d9876a73d33519c1c02df06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/912362a80d9876a73d33519c1c02df06.png -------------------------------------------------------------------------------- /frontend/static/frontend/92dbf76c6412df27deb22d35aa1bc094.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/92dbf76c6412df27deb22d35aa1bc094.jpeg -------------------------------------------------------------------------------- /frontend/static/frontend/9a9d59537cb2804844d1bde43d15c495.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/9a9d59537cb2804844d1bde43d15c495.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/9ba05ee9b948b065df17ace50aee1b91.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/9ba05ee9b948b065df17ace50aee1b91.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/Notion_app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/Notion_app_logo.png -------------------------------------------------------------------------------- /frontend/static/frontend/ad79d5554cb4fffa621090af22212075.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/ad79d5554cb4fffa621090af22212075.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/ae8757bfaa998d7bc3a0f6b7c8616561.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/ae8757bfaa998d7bc3a0f6b7c8616561.png -------------------------------------------------------------------------------- /frontend/static/frontend/b03be388890362d40475184694c6f2ce.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/b03be388890362d40475184694c6f2ce.jpeg -------------------------------------------------------------------------------- /frontend/static/frontend/b341a0d37c39f79c8edc9cb845d6fb2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/b341a0d37c39f79c8edc9cb845d6fb2b.png -------------------------------------------------------------------------------- /frontend/static/frontend/bd146962a6e7c17a036ffa3e205ab09c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/bd146962a6e7c17a036ffa3e205ab09c.png -------------------------------------------------------------------------------- /frontend/static/frontend/c04c9389e02bfdf3afc161814b1faa04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/c04c9389e02bfdf3afc161814b1faa04.png -------------------------------------------------------------------------------- /frontend/static/frontend/c2ca688cb638be86d436e3f7a3c7a64f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/c2ca688cb638be86d436e3f7a3c7a64f.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/c63230bed9c4f3a49ec1629c02e1313e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/c63230bed9c4f3a49ec1629c02e1313e.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/c707f24d228eba64614c14d631496449.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/c707f24d228eba64614c14d631496449.png -------------------------------------------------------------------------------- /frontend/static/frontend/cc15727d2cbf639130786b464c0b0e4a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/cc15727d2cbf639130786b464c0b0e4a.png -------------------------------------------------------------------------------- /frontend/static/frontend/e12afe5e859c951cc3f326bf7265fbdf.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | // Redux 6 | import { Provider } from 'react-redux'; 7 | import { createStore, applyMiddleware, compose } from 'redux'; 8 | import reducers from './reducers'; 9 | import thunk from 'redux-thunk'; 10 | 11 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | , 19 | document.getElementById('root') 20 | ); -------------------------------------------------------------------------------- /frontend/static/frontend/e1b20381ebe5bafedffa21d3286c4a91.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/e1b20381ebe5bafedffa21d3286c4a91.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/e1f1833f9274cf1de61d02a96b49acdb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/e1f1833f9274cf1de61d02a96b49acdb.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/e308503ff876ee4861ee7e89a8099552.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/e308503ff876ee4861ee7e89a8099552.png -------------------------------------------------------------------------------- /frontend/static/frontend/e7cff9e8f7a5fd7b393929b5ab2e6814.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/e7cff9e8f7a5fd7b393929b5ab2e6814.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/e882f14370d623461cb660616e142e1b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/e882f14370d623461cb660616e142e1b.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/edb408ce1bc76f1fc1c0ec2820f13c59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/edb408ce1bc76f1fc1c0ec2820f13c59.png -------------------------------------------------------------------------------- /frontend/static/frontend/f0d16b0ac2b43aa2e5ced84704b16b4b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/f0d16b0ac2b43aa2e5ced84704b16b4b.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/f440e065e6f7ab4666106fe627891f0b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/f440e065e6f7ab4666106fe627891f0b.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/f61e1d19dba0ddd469683d92ea390f3a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/f61e1d19dba0ddd469683d92ea390f3a.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/f832b178dfdfbb0377a9f64a10b440e4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/f832b178dfdfbb0377a9f64a10b440e4.jpg -------------------------------------------------------------------------------- /frontend/static/frontend/fb1165729217462067c4b78c8806fcc5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/fb1165729217462067c4b78c8806fcc5.png -------------------------------------------------------------------------------- /frontend/static/frontend/fonts/SF-Pro-Text-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/fonts/SF-Pro-Text-Bold.otf -------------------------------------------------------------------------------- /frontend/static/frontend/fonts/SF-Pro-Text-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/fonts/SF-Pro-Text-Light.otf -------------------------------------------------------------------------------- /frontend/static/frontend/fonts/SF-Pro-Text-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/fonts/SF-Pro-Text-Medium.otf -------------------------------------------------------------------------------- /frontend/static/frontend/fonts/SF-Pro-Text-Semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/fonts/SF-Pro-Text-Semibold.otf -------------------------------------------------------------------------------- /frontend/static/frontend/fonts/SF-Pro.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/frontend/static/frontend/fonts/SF-Pro.ttf -------------------------------------------------------------------------------- /frontend/static/frontend/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** 8 | * A better abstraction over CSS. 9 | * 10 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 11 | * @website https://github.com/cssinjs/jss 12 | * @license MIT 13 | */ 14 | 15 | /** @license React v0.20.1 16 | * scheduler.production.min.js 17 | * 18 | * Copyright (c) Facebook, Inc. and its affiliates. 19 | * 20 | * This source code is licensed under the MIT license found in the 21 | * LICENSE file in the root directory of this source tree. 22 | */ 23 | 24 | /** @license React v16.13.1 25 | * react-is.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** @license React v17.0.1 34 | * react-dom.production.min.js 35 | * 36 | * Copyright (c) Facebook, Inc. and its affiliates. 37 | * 38 | * This source code is licensed under the MIT license found in the 39 | * LICENSE file in the root directory of this source tree. 40 | */ 41 | 42 | /** @license React v17.0.1 43 | * react.production.min.js 44 | * 45 | * Copyright (c) Facebook, Inc. and its affiliates. 46 | * 47 | * This source code is licensed under the MIT license found in the 48 | * LICENSE file in the root directory of this source tree. 49 | */ 50 | -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/base/_layout.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | src: url(../../fonts/SF-Pro.ttf); 3 | font-family: san-fran; 4 | } 5 | 6 | @font-face { 7 | src: url(../../fonts/SF-Pro-Text-Medium.otf); 8 | font-family: san-fran-med; 9 | } 10 | 11 | @font-face { 12 | src: url(../../fonts/SF-Pro-Text-Bold.otf); 13 | font-family: san-fran-bold; 14 | } 15 | 16 | @font-face { 17 | src: url(../../fonts/SF-Pro-Text-Semibold.otf); 18 | font-family: san-fran-semi; 19 | } 20 | 21 | * { 22 | margin: 0; 23 | padding: 0; 24 | box-sizing: border-box; 25 | font-family: san-fran, Helvetica, sans-serif; 26 | 27 | &:focus { 28 | outline: 0; 29 | } 30 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/base/_media_queries.scss: -------------------------------------------------------------------------------- 1 | @media (max-width: 725px) { 2 | 3 | // Templates 4 | .empty-page-text { 5 | margin-right: 40px; 6 | } 7 | 8 | .templates-modal { 9 | width: 80%; 10 | } 11 | 12 | // Body General 13 | .body-container { 14 | overflow-x: hidden; 15 | } 16 | 17 | .body-header { 18 | background-color: rgba(0,0,0,0);; 19 | } 20 | 21 | .body-content { 22 | padding-left: 0; 23 | padding-right: 50px; 24 | } 25 | 26 | .element { 27 | width: 100vw; 28 | max-width: 100%; 29 | overflow-x: scroll; 30 | } 31 | 32 | .element-options { 33 | opacity: 1; 34 | margin-left: 10px; 35 | } 36 | 37 | // Cover Photo 38 | .photo-container { 39 | position: relative; 40 | 41 | .cover-buttons { 42 | opacity: 1; 43 | position: absolute; 44 | top: 90%; 45 | left: 50%; 46 | transform: translate(-50%, -50%); 47 | width: 200px; 48 | margin: 0; 49 | } 50 | } 51 | 52 | .photo-container { 53 | z-index: -1; 54 | } 55 | 56 | .photo-dropdown { 57 | position: fixed; 58 | top: 50%; 59 | left: 50%; 60 | transform: translate(-50%, -50%) !important; 61 | width: 90% 62 | } 63 | 64 | .grid { 65 | grid-template-columns: auto auto; 66 | 67 | .grid-photo { 68 | width: auto; 69 | } 70 | } 71 | 72 | .add-photo-container { 73 | opacity: 1; 74 | margin-left: 49px; 75 | 76 | .add-photo { 77 | margin: 0; 78 | } 79 | } 80 | 81 | // Table 82 | .Table { 83 | padding-bottom: 250px; 84 | margin-bottom: -250px; 85 | 86 | .table { 87 | margin-bottom: 30px; 88 | } 89 | 90 | .table-head { 91 | .fa-ellipsis-h { 92 | opacity: 1; 93 | } 94 | } 95 | 96 | .table-row { 97 | .element-options { 98 | opacity: 1 !important; 99 | position: relative; 100 | } 101 | } 102 | 103 | .row-dropdown { 104 | position: absolute; 105 | top: 50%; 106 | left: 0; 107 | transform: translate(-80%, -50%) !important; 108 | } 109 | 110 | .sub-menu { 111 | margin-left: 0; 112 | } 113 | 114 | .multi-select-popup { 115 | transform: translateX(0) !important; 116 | 117 | .tag-options { 118 | 119 | .tag-option-dropdown { 120 | right: 0; 121 | } 122 | } 123 | 124 | .tag-option { 125 | i { 126 | opacity: 1; 127 | } 128 | } 129 | } 130 | } 131 | 132 | // Kanban 133 | .Kanban { 134 | padding-bottom: 250px; 135 | margin-bottom: -250px; 136 | } 137 | 138 | // Login and sign up 139 | .logo { 140 | margin-left: 30px; 141 | } 142 | 143 | } 144 | 145 | @media (max-width: 350px) { 146 | 147 | // Menu 148 | .menu { 149 | box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; 150 | .arrows { 151 | opacity: 1; 152 | } 153 | 154 | .user-first-name { 155 | flex: 0.99; 156 | } 157 | 158 | .quick-find-modal { 159 | width: 90%; 160 | } 161 | 162 | .page, .search, .add-page, .logout { 163 | p { 164 | font-size: 1rem; 165 | } 166 | } 167 | 168 | .user-first-name { 169 | font-size: 1.1rem; 170 | } 171 | 172 | .page { 173 | .page_name { 174 | padding-top: 11px; 175 | padding-bottom: 11px; 176 | } 177 | } 178 | 179 | .logout { 180 | width: 100%; 181 | 182 | i { 183 | font-size: 1rem; 184 | } 185 | } 186 | 187 | .menu-dropdown { 188 | .page-nav-icons { 189 | opacity: 1 !important; 190 | } 191 | 192 | .dropdown { 193 | transform: translateX(-90%); 194 | } 195 | } 196 | 197 | .add-page { 198 | height: 50px; 199 | } 200 | } 201 | } 202 | 203 | // Login and sign up -- below 330px 204 | @media (max-width: 330px) { 205 | .form { 206 | form { 207 | width: 90%; 208 | } 209 | } 210 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/base/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin menu-item { 2 | display: flex; 3 | align-items: center; 4 | padding: $menu-padding; 5 | cursor: pointer; 6 | border-radius: 3px; 7 | margin-left: 4px; 8 | margin-right: 4px; 9 | 10 | i { 11 | color: $medium-gray; 12 | margin-right: 10px; 13 | font-size: $normal-font-size; 14 | } 15 | 16 | p { 17 | color: $med-light-gray; 18 | font-size: $normal-font-size; 19 | font-family: san-fran-med, $back-up-fonts; 20 | } 21 | 22 | &:hover { 23 | background-color: $hover-color; 24 | } 25 | } 26 | 27 | @mixin dropdown { 28 | position: absolute; 29 | border-radius: 3px; 30 | background-color: white; 31 | font-size: $normal-font-size; 32 | z-index: 100; 33 | min-width: 180px; 34 | box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, 35 | rgba(15, 15, 15, 0.2) 0px 9px 24px; 36 | padding: 6px 0px 6px 0px; 37 | 38 | a, .dropdown-option { 39 | color: $dark-gray; 40 | padding: 6px 6px; 41 | display: flex; 42 | cursor: pointer; 43 | align-items: center; 44 | 45 | &:hover { 46 | background-color: $hover-color; 47 | } 48 | } 49 | 50 | i { 51 | margin: 0px 10px 0px 8px; 52 | color: $dark-gray; 53 | font-size: 0.9rem; 54 | } 55 | } 56 | 57 | @mixin element-options($font-size) { 58 | color: rgba($dark-gray, 0.3); 59 | font-size: $font-size; 60 | border-radius: 3px; 61 | display: flex; 62 | transition: 0.3s; 63 | 64 | &:hover { 65 | background-color: $hover-color; 66 | transition: 0.3s; 67 | } 68 | } 69 | 70 | @mixin notion_input { 71 | padding: 4px 6px; 72 | border-radius: 3px; 73 | color: $dark-gray; 74 | font-size: $normal-font-size; 75 | background-color: rgba(242, 241, 238, 0.6); 76 | border: 1px solid rgba(0,0,0,0); 77 | box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px inset; 78 | 79 | &:focus { 80 | border: 1px solid rgb(46, 170, 220); 81 | box-shadow: 0px 0px 0px 2.5px rgba(46, 170, 220, 0.4); 82 | } 83 | } 84 | 85 | @mixin kanabn_button { 86 | border: none; 87 | background-color: white; 88 | font-size: $normal-font-size; 89 | color: $medium-gray; 90 | cursor: pointer; 91 | padding: 3px 6px; 92 | border-radius: 3px; 93 | display: flex; 94 | align-items: center; 95 | 96 | &:hover { 97 | background-color: $hover-color; 98 | } 99 | 100 | span { 101 | font-size: 1.3rem; 102 | height: 20.8px; 103 | margin-right: 5px; 104 | } 105 | } 106 | 107 | @mixin hr { 108 | background: $border-color; 109 | height:1px; 110 | border:none; 111 | } 112 | 113 | @mixin tag { 114 | color: $dark-gray; 115 | font-size: $normal-font-size; 116 | margin-top: 5px; 117 | margin-right: 7px; 118 | padding: 6px; 119 | padding-top: 1px; 120 | padding-bottom: 1px; 121 | border-radius: 3px; 122 | display: flex; 123 | align-items: center; 124 | cursor: pointer; 125 | max-width: 235px; 126 | overflow: hidden; 127 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/base/_variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $dark-gray: rgb(55, 53, 47); 3 | $medium-gray: rgba($dark-gray, 0.4); 4 | $med-light-gray: rgba(25, 23, 17, 0.6); 5 | $hover-color: rgba($dark-gray, 0.08); 6 | $border-color: rgb(233, 233, 231); 7 | $menu-icon-color: rgba(55, 53, 47, 0.45); 8 | 9 | // Padding 10 | $menu-padding: 5px 14px 5px 14px; 11 | 12 | // Font 13 | $normal-font-size: 0.875rem; 14 | $menu-font-weight: 600; 15 | $back-up-fonts: "Helvetica, sans-serif"; 16 | 17 | // Animations 18 | $transition: 0.3s; -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/_dropdown.scss: -------------------------------------------------------------------------------- 1 | .create_element_dropdown, .element_settings_dropdown { 2 | position: relative; 3 | } 4 | 5 | .add-element-drpdn-parent, .element-settings-drpdn-parent { 6 | margin-top: 3px; 7 | } 8 | 9 | .dropdown { 10 | @include dropdown; 11 | position: fixed; 12 | animation-name: dropdownEnterAnimation; 13 | animation-duration: 0.3s; 14 | 15 | .element-color { 16 | color: $dark-gray; 17 | align-items: center; 18 | 19 | i { 20 | font-size: 0.9rem; 21 | margin: 0; 22 | margin-right: 7px; 23 | margin-left: 8px; 24 | } 25 | 26 | p { 27 | flex: 1; 28 | } 29 | } 30 | } 31 | 32 | @keyframes dropdownEnterAnimation { 33 | 0% {opacity: 0} 34 | 100% {opacity: 1} 35 | } 36 | 37 | .edit-drpdn { 38 | 39 | .fa-edit { 40 | margin-right: 7px; 41 | } 42 | } 43 | 44 | .rename { 45 | a { 46 | cursor: default; 47 | padding: 0px; 48 | min-width: 250px; 49 | 50 | &:hover { 51 | background-color: white; 52 | } 53 | 54 | input { 55 | width: 100%; 56 | margin: 0px 8px 0px 8px; 57 | padding: 5px; 58 | border: none; 59 | border-radius: 3px; 60 | box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px inset; 61 | background: rgba(242, 241, 238, 0.6); 62 | caret-color: $dark-gray; 63 | color: $dark-gray; 64 | } 65 | } 66 | } 67 | 68 | .left-aligned { 69 | right: 0; 70 | } 71 | 72 | .dropdown-screen { 73 | z-index: 99 !important; 74 | 75 | &:hover .element-options { 76 | opacity: 1; 77 | } 78 | } 79 | 80 | // Color dropdown 81 | .colors { 82 | margin-bottom: 6px; 83 | h3 { 84 | margin-left: 13px; 85 | margin-bottom: 8px; 86 | font-size: 0.8rem; 87 | color: $med-light-gray; 88 | } 89 | 90 | .color { 91 | padding-top: 4px; 92 | padding-bottom: 4px; 93 | transition: 0.15s; 94 | color: $dark-gray; 95 | } 96 | 97 | .check { 98 | margin-right: 15px; 99 | font-size: 0.9rem; 100 | } 101 | } 102 | 103 | .colors-hr { 104 | margin-top: 10px; 105 | margin-bottom: 10px; 106 | } 107 | 108 | .square { 109 | height: 18px; 110 | width: 18px; 111 | border-radius: 3px; 112 | box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px inset; 113 | margin-left: 12px; 114 | margin-right: 10px; 115 | } 116 | 117 | .color:hover { 118 | background-color: rgba(55, 53, 47, 0.08); 119 | transition: 0.15s; 120 | cursor: pointer; 121 | } 122 | 123 | .color > p { 124 | flex: 1; 125 | } 126 | 127 | .element_settings_dropdown { 128 | .sub-menu { 129 | top: 0; 130 | right: -180px; 131 | } 132 | } 133 | 134 | // Add element dropdown 135 | .add-element-drpdn { 136 | width: 324px; 137 | height: 40vh; 138 | overflow: scroll; 139 | 140 | a { 141 | align-items: center; 142 | } 143 | 144 | .drpdn-text { 145 | margin-left: 8px; 146 | 147 | .drpdn-head { 148 | font-size: 0.9rem; 149 | color: $dark-gray; 150 | } 151 | 152 | .drpdn-sub-head { 153 | font-size: 0.75rem; 154 | color: rgba($dark-gray, 0.6); 155 | margin-top: 2px; 156 | } 157 | } 158 | 159 | .drpdn-icon { 160 | box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px; 161 | border-radius: 3px; 162 | margin-left: 10px; 163 | color: $dark-gray; 164 | height: 46px; 165 | width: 46px; 166 | background-color: white; 167 | } 168 | } 169 | 170 | // Link to page dropdown 171 | .list-of-pages { 172 | width: 324px; 173 | max-height: 40vh; 174 | overflow: scroll; 175 | 176 | p { 177 | margin: 8px 14px; 178 | font-size: 0.8rem; 179 | color: rgba($dark-gray, 0.6) 180 | } 181 | } 182 | 183 | .menu-overlay { 184 | width: 100vh; 185 | height: 100vh; 186 | position: absolute; 187 | top: 0; 188 | left: 0; 189 | z-index: 10; 190 | cursor: default; 191 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/_home.scss: -------------------------------------------------------------------------------- 1 | .home-page { 2 | height: 100vh; 3 | display: flex; 4 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/_loader.scss: -------------------------------------------------------------------------------- 1 | .loader { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/_login-register.scss: -------------------------------------------------------------------------------- 1 | .logo { 2 | display: flex; 3 | align-items: center; 4 | margin: 25px 0px 0px 88px; 5 | color: $dark-gray; 6 | 7 | .notionLogo { 8 | height: 30px; 9 | width: 30px; 10 | fill: $dark-gray; 11 | } 12 | 13 | p { 14 | margin-left: 10px; 15 | font-family: "san-fran-med", $back-up-fonts; 16 | } 17 | } 18 | 19 | .form { 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | width: 100%; 24 | height: 70vh; 25 | 26 | form { 27 | width: 320px; 28 | 29 | h1 { 30 | font-family: (san-fran-bold, $back-up-fonts); 31 | font-size: 3rem; 32 | text-align: center; 33 | margin-bottom: 25px; 34 | } 35 | 36 | 37 | label { 38 | font-size: 0.75rem; 39 | color: rgba($dark-gray, 0.6); 40 | } 41 | 42 | input:not([type=submit]) { 43 | @include notion_input; 44 | padding: 8px; 45 | width: 100%; 46 | border-radius: 3px; 47 | font-size: 0.95rem; 48 | margin-bottom: 8px; 49 | margin-top: 3px; 50 | 51 | &::placeholder { 52 | color: $medium-gray; 53 | } 54 | } 55 | 56 | input[type=submit] { 57 | width: 100%; 58 | padding: 8px; 59 | border-radius: 3px; 60 | font-size: 0.95rem; 61 | border: none; 62 | color: rgb(235, 87, 87); 63 | background: rgb(251, 235, 232); 64 | box-shadow: rgba(15, 15, 15, 0.1) 0px 1px 2px, rgba(235, 87, 87, 0.3) 0px 0px 0px 1px inset; 65 | cursor: pointer; 66 | margin-top: 5px; 67 | } 68 | 69 | .bottom-text { 70 | text-align: center; 71 | margin-top: 30px; 72 | 73 | p, a { 74 | display: inline; 75 | margin-right: 5px; 76 | color: rgba($dark-gray, 0.6); 77 | font-size: 0.875rem; 78 | } 79 | 80 | a { 81 | text-decoration: underline; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/_modal.scss: -------------------------------------------------------------------------------- 1 | .semi-transparent-bg { 2 | background-color: black; 3 | height: 100%; 4 | width: 100%; 5 | top: 0; 6 | left: 0; 7 | bottom: 0; 8 | right: 0; 9 | position: fixed; 10 | cursor: default; 11 | background-color: rgba(0, 0, 0, 0.6); 12 | z-index: 10; 13 | justify-content: center; 14 | } 15 | 16 | .modal { 17 | position: fixed; 18 | top: 45%; 19 | left: 50%; 20 | transform: translate(-50%, -50%); 21 | padding: 24px 30px; 22 | z-index: 10; 23 | border-radius: 3px; 24 | width: 336px; 25 | color: $dark-gray; 26 | background-color: white; 27 | box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 5px 10px, 28 | rgba(15, 15, 15, 0.2) 0px 15px 40px; 29 | overflow-y: scroll; 30 | 31 | p { 32 | margin-bottom: 20px; 33 | font-size: 1rem; 34 | color: $dark-gray; 35 | font-weight: 500; 36 | font-family: san-fran, $back-up-fonts; 37 | } 38 | 39 | button { 40 | display: block; 41 | border: none; 42 | background: none; 43 | margin: auto; 44 | height: 32px; 45 | width: 100%; 46 | border-radius: 3px; 47 | cursor: pointer; 48 | margin-top: 8px; 49 | font-size: $normal-font-size; 50 | } 51 | 52 | .delete-btn { 53 | color: rgb(235, 87, 87); 54 | border: 1px solid rgba(235, 87, 87, 0.5); 55 | 56 | &:hover { 57 | background: rgba(235, 87, 87, 0.1); 58 | } 59 | } 60 | 61 | .cancel-btn { 62 | border: 1px solid rgba($dark-gray, 0.16); 63 | 64 | &:hover { 65 | background: rgba($dark-gray, 0.08); 66 | } 67 | } 68 | } 69 | 70 | .quick-find-modal { 71 | width: 600px; 72 | padding: 0; 73 | top: 30%; 74 | transform: translate(-50%,0); 75 | left: 50%; 76 | max-height: 300px; 77 | overflow: scroll; 78 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_body-header.scss: -------------------------------------------------------------------------------- 1 | .body-header { 2 | display: flex; 3 | padding: 12px; 4 | font-size: $normal-font-size; 5 | align-items: center; 6 | height: 45px; 7 | background-color: white; 8 | width: 100vw; 9 | z-index: 2; 10 | 11 | a { 12 | color: $dark-gray; 13 | padding: 4px 6px; 14 | transition: $transition; 15 | border-radius: 3px; 16 | 17 | &:hover { 18 | background-color: $hover-color; 19 | transition: $transition; 20 | } 21 | } 22 | 23 | i { 24 | font-size: 1.1rem; 25 | color: rgba(55, 53, 47, 0.8); 26 | margin-right: 12px; 27 | border-radius: 3px; 28 | padding: 5px; 29 | transition: $transition; 30 | 31 | &:hover { 32 | transition: $transition; 33 | background-color: $hover-color; 34 | } 35 | } 36 | 37 | .slash { 38 | display: inline; 39 | margin-left: 2px; 40 | color: $medium-gray; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_colors.scss: -------------------------------------------------------------------------------- 1 | // Standard Colors 2 | .default { 3 | background: rgba(227, 226, 224, 0.5); 4 | } 5 | 6 | .grey { 7 | background: rgb(227, 226, 224); 8 | } 9 | 10 | .brown { 11 | background: rgb(238, 224, 218); 12 | } 13 | 14 | .orange { 15 | background: rgb(250, 222, 201); 16 | } 17 | 18 | .yellow { 19 | background: rgb(253, 236, 200); 20 | } 21 | 22 | .green { 23 | background: rgb(219, 237, 219); 24 | } 25 | 26 | .blue { 27 | background: rgb(211, 229, 239); 28 | } 29 | 30 | .purple { 31 | background: rgb(232, 222, 238); 32 | } 33 | 34 | .pink { 35 | background: rgb(245, 224, 233); 36 | } 37 | 38 | .red { 39 | background: rgb(255, 226, 221); 40 | } 41 | 42 | // Background Colors 43 | .grey-bg { 44 | background: rgb(235, 236, 237); 45 | } 46 | 47 | .brown-bg { 48 | background: rgb(233, 229, 227); 49 | } 50 | 51 | .orange-bg { 52 | background: rgb(250, 235, 221); 53 | } 54 | 55 | .yellow-bg { 56 | background: rgb(251, 243, 219); 57 | } 58 | 59 | .green-bg { 60 | background: rgb(221, 237, 234); 61 | } 62 | 63 | .blue-bg { 64 | background: rgb(221, 235, 241); 65 | } 66 | 67 | .purple-bg { 68 | background: rgb(234, 228, 242); 69 | } 70 | 71 | .pink-bg { 72 | background: rgb(244, 223, 235); 73 | } 74 | 75 | .red-bg { 76 | background: rgb(251, 228, 228); 77 | } 78 | 79 | // Text 80 | .default-text, .grey-text { 81 | color: rgb(50, 48, 44); 82 | } 83 | 84 | .brown-text { 85 | color: rgb(68, 42, 30); 86 | } 87 | 88 | .orange-text { 89 | color: rgb(73, 41, 14); 90 | } 91 | 92 | .yellow-text { 93 | color: rgb(67,47,30); 94 | } 95 | 96 | .green-text { 97 | color: rgb(47,73,58); 98 | } 99 | 100 | .blue-text { 101 | color: rgb(27,53,73); 102 | } 103 | 104 | .purple-text { 105 | color: rgb(65, 36, 84); 106 | } 107 | 108 | .pink-text { 109 | color: rgb(76, 35, 55); 110 | } 111 | 112 | .red-text { 113 | color: rgb(93, 23, 21); 114 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_cover_photo.scss: -------------------------------------------------------------------------------- 1 | // Photo container (shown if photo is on a page) 2 | .photo-container { 3 | margin-bottom: 45px; 4 | width: 100%; 5 | position: relative; 6 | 7 | .photo-container-placeholder { 8 | background-color: green; 9 | height: 30vh; 10 | width: 100vw; 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | 15 | .anchor { 16 | position: relative; 17 | } 18 | } 19 | 20 | .cover-photo { 21 | width: 100%; 22 | height: 30vh; 23 | object-fit: cover; 24 | object-position: center 40%; 25 | } 26 | 27 | .cover-buttons { 28 | position: absolute; 29 | right: 0; 30 | top: 175px; 31 | margin-right: 120px; 32 | background-color: white; 33 | align-items: center; 34 | cursor: pointer; 35 | border-radius: 3px; 36 | color: rgba($dark-gray, 0.6); 37 | box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 2px 4px; 38 | transition: 0.2s; 39 | opacity: 0; 40 | display: flex; 41 | font-size: 0.8rem; 42 | 43 | .change-cover-button { 44 | border-right: 1px solid rgba($dark-gray, 0.09); 45 | padding: 4px 6px; 46 | 47 | &:hover { 48 | background-color: rgb(239, 239, 238); 49 | transition: $transition; 50 | border-radius: 3px 0 0 3px; 51 | } 52 | } 53 | 54 | .remove-cover-button { 55 | padding: 4px 6px; 56 | 57 | &:hover { 58 | background-color: rgb(239, 239, 238); 59 | transition: $transition; 60 | border-radius: 0 3px 3px 0; 61 | } 62 | } 63 | } 64 | 65 | &:hover .cover-buttons { 66 | opacity: 100; 67 | transition: 0.3s; 68 | } 69 | } 70 | 71 | // Photo Dropdown 72 | .photo-dropdown { 73 | position: absolute; 74 | right: 0; 75 | top: 600px; 76 | margin-right: 15px; 77 | background-color: white; 78 | border-radius: 3px; 79 | width: 558px; 80 | height: 420px; 81 | overflow: scroll; 82 | background-color: white; 83 | box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, 84 | rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px; 85 | padding: 10px; 86 | } 87 | 88 | .photo-drpdn-header { 89 | font-weight: 300; 90 | font-size: 0.8rem; 91 | color: rgba($dark-gray, 0.6); 92 | margin: 10px 0px 5px 8px; 93 | } 94 | 95 | .first-header { 96 | margin: 3px 0px 5px 8px; 97 | } 98 | 99 | .grid { 100 | display: grid; 101 | grid-template-columns: auto auto auto auto; 102 | column-gap: 5px; 103 | row-gap: 5px; 104 | padding: 8px; 105 | } 106 | 107 | .grid-photo { 108 | width: 127px; 109 | height: 64px; 110 | position: relative; 111 | 112 | img { 113 | width: 100%; 114 | border-radius: 3px; 115 | height: 64px; 116 | object-fit: cover; 117 | object-position: center 40%; 118 | } 119 | } 120 | 121 | .img-overlay { 122 | position: absolute; 123 | width: 100%; 124 | height: 100%; 125 | top: 0; 126 | border-radius: 3px; 127 | background-color: white; 128 | opacity: 0; 129 | transition: 0.2s; 130 | 131 | &:hover { 132 | opacity: 0.2; 133 | cursor: pointer; 134 | transition: 0.2s; 135 | } 136 | } 137 | 138 | // Add photo Container 139 | .add-photo-container { 140 | opacity: 0; 141 | transition: 0.3s; 142 | padding-top: 80px; 143 | margin-bottom: 5px; 144 | 145 | &:hover { 146 | opacity: 100; 147 | transition: 0.3s; 148 | } 149 | 150 | .add-photo { 151 | display: inline-flex; 152 | transition: 0.3s; 153 | cursor: pointer; 154 | align-items: center; 155 | padding: 2px 5px; 156 | border-radius: 3px; 157 | margin-left: 95px; 158 | 159 | &:hover { 160 | transition: 0.3s; 161 | background-color: $hover-color; 162 | } 163 | 164 | .photo-icon { 165 | color: rgba($dark-gray, 0.3); 166 | font-size: 1.2rem; 167 | line-height: 1.2px; 168 | margin-right: 5px; 169 | } 170 | 171 | .add-photo-text { 172 | color: rgba($medium-gray, 0.4); 173 | font-size: $normal-font-size; 174 | } 175 | } 176 | } 177 | 178 | // Photos 179 | .nasa_tim_peake_spacewalk { 180 | object-position: center 59% !important; 181 | } 182 | 183 | .nasa_robert_stewart_spacewalk_2 { 184 | object-position: center 45% !important; 185 | } 186 | 187 | .met_silk_kashan_carpet { 188 | object-position: center 47% !important; 189 | } 190 | 191 | .met_william_morris_1878 { 192 | object-position: center 50% !important; 193 | } 194 | 195 | .woodcuts_10 { 196 | object-position: center 0% !important; 197 | } 198 | 199 | .woodcuts_15 { 200 | object-position: center 35% !important; 201 | } 202 | 203 | .woodcuts_3 { 204 | object-position: center 20% !important; 205 | } 206 | 207 | .woodcuts_1 { 208 | object-position: center 72% !important; 209 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_element_options.scss: -------------------------------------------------------------------------------- 1 | .element-options, .element-options-row { 2 | display: flex; 3 | align-items: center; 4 | margin-right: 7px; 5 | opacity: 0; 6 | transition: $transition; 7 | 8 | .add_element, .element-option-icon { 9 | @include element-options(1.4rem); 10 | } 11 | } 12 | 13 | .page_title { 14 | 15 | .add_element { 16 | @include element-options(1.5rem); 17 | margin-right: 7px; 18 | } 19 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_general.scss: -------------------------------------------------------------------------------- 1 | .side-by-side { 2 | display: flex; 3 | align-items: flex-start; 4 | } 5 | 6 | .body { 7 | height: 100vh; 8 | flex: 1; 9 | position: relative; 10 | display: flex; 11 | flex-direction: column; 12 | overflow: hidden; 13 | } 14 | 15 | .body-scroller { 16 | flex: 1; 17 | } 18 | 19 | .body-content { 20 | padding-left: 50px; 21 | padding-bottom: 200px; 22 | padding-right: 120px; 23 | } 24 | 25 | .test { 26 | height: 90%; 27 | } 28 | 29 | .page_title { 30 | margin-left: 14px; 31 | color: $dark-gray; 32 | font-size: 1.21rem; 33 | margin-bottom: 33px; 34 | display: flex; 35 | align-items: center; 36 | 37 | h1 { 38 | font-family: san-fran-bold, $back-up-fonts; 39 | } 40 | } 41 | 42 | .element, .page_title { 43 | display: flex; 44 | 45 | &:hover .element-options { 46 | opacity: 1; 47 | } 48 | } 49 | 50 | .first_child { 51 | margin-top: 0px !important; 52 | } 53 | 54 | .show-menu-btn { 55 | cursor: pointer; 56 | } 57 | 58 | .pointer { 59 | cursor: pointer; 60 | } 61 | 62 | a { 63 | text-decoration: none; 64 | } 65 | 66 | .add_element { 67 | font-size: 1.7rem; 68 | cursor: pointer; 69 | } 70 | 71 | .option { 72 | transition: 0.3s; 73 | display: flex; 74 | 75 | &:hover { 76 | opacity: 1; 77 | transition: 0.3s; 78 | } 79 | } 80 | 81 | .opacity_1 { 82 | opacity: 1; 83 | } 84 | 85 | .width_100 { 86 | width: 100%; 87 | } 88 | 89 | .user_input { 90 | border: none; 91 | 92 | &:focus { 93 | outline: none; 94 | } 95 | } 96 | 97 | .fa-ellipsis-h { 98 | font-size: 0.8rem; 99 | margin-right: 5px; 100 | cursor: pointer; 101 | } 102 | 103 | .e_container { 104 | width: 100%; 105 | } 106 | 107 | .inline { 108 | display: inline; 109 | } 110 | 111 | .flex_1 { 112 | flex: 1 113 | } 114 | 115 | .group { 116 | display: flex; 117 | max-width: 90%; 118 | } 119 | 120 | .dragging-over { 121 | box-shadow: 0px 0px 0px 3px rgba(45, 170, 219, 0.3); 122 | } 123 | -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_headers.scss: -------------------------------------------------------------------------------- 1 | .heading_1 { 2 | font-size: 1.8rem; 3 | color: $dark-gray; 4 | font-family: san-fran-semi, $back-up-fonts; 5 | width: 100%; 6 | padding: 5px 3px; 7 | 8 | &::placeholder { 9 | color: $medium-gray; 10 | } 11 | } 12 | 13 | .Heading_1 { 14 | margin-top: 30px; 15 | align-items: center; 16 | } 17 | 18 | .heading_2 { 19 | font-size: 1.45rem; 20 | color: $dark-gray; 21 | font-family: san-fran-semi, $back-up-fonts; 22 | width: 100%; 23 | padding: 5px 3px; 24 | 25 | &::placeholder { 26 | color: $medium-gray; 27 | } 28 | } 29 | 30 | .Heading_2 { 31 | margin-top: 25px; 32 | align-items: center; 33 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_kanban.scss: -------------------------------------------------------------------------------- 1 | .kanban { 2 | margin-bottom: 35px; 3 | width: 100%; 4 | overflow: hidden; 5 | 6 | hr { 7 | @include hr 8 | } 9 | } 10 | 11 | .kanban-title { 12 | color: $dark-gray; 13 | font-size: 1.3rem; 14 | font-family: san-fran-semi, $back-up-fonts; 15 | margin-bottom: 7px; 16 | width: 100%; 17 | } 18 | 19 | .kanban_groups { 20 | display: flex; 21 | padding-top: 10px; 22 | overflow: scroll; 23 | padding-left: 3px; 24 | 25 | .new-group { 26 | margin-right: 2px; 27 | button { 28 | @include kanabn_button 29 | } 30 | 31 | input { 32 | @include notion_input; 33 | } 34 | 35 | p { 36 | min-width: 80px; 37 | } 38 | } 39 | } 40 | 41 | .current_groups { 42 | display: flex; 43 | } 44 | 45 | // Kanban Group 46 | .kanban_group { 47 | padding-right: 16px; 48 | 49 | .kanban-group-header { 50 | display: flex; 51 | align-items: center; 52 | margin-bottom: 10px; 53 | 54 | .group-options { 55 | color: $medium-gray; 56 | padding: 5px; 57 | border-radius: 3px; 58 | // font-size: 1rem; 59 | } 60 | 61 | i:hover { 62 | background-color: $hover-color; 63 | } 64 | 65 | .group-name-and-card-count { 66 | display: flex; 67 | flex: 1; 68 | align-items: center; 69 | } 70 | 71 | .group-title { 72 | padding: 3px; 73 | 74 | &:hover { 75 | background-color: $hover-color; 76 | border-radius: 3px; 77 | } 78 | 79 | p { 80 | font-size: $normal-font-size; 81 | border-radius: 3px; 82 | padding: 1.5px 6px; 83 | cursor: pointer; 84 | } 85 | } 86 | 87 | input { 88 | @include notion_input; 89 | max-width: 158px; 90 | } 91 | 92 | .card-count { 93 | color: $medium-gray; 94 | font-size: $normal-font-size; 95 | margin-left: 10px; 96 | } 97 | } 98 | 99 | .placeholder { 100 | opacity: 0; 101 | font-size: 0.1rem; 102 | } 103 | } 104 | 105 | .new-card { 106 | @include kanabn_button; 107 | height: 32px; 108 | width: 260px; 109 | // margin-top: -20px; 110 | } 111 | 112 | // Kanban Card 113 | .kanban_card { 114 | border-radius: 3px; 115 | box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 2px 4px; 116 | background-color: white; 117 | min-height: 40px; 118 | width: 260px; 119 | margin-bottom: 8px; 120 | display: flex; 121 | align-items: center; 122 | padding-right: 10px; 123 | 124 | &:hover .close-card { 125 | opacity: 1; 126 | transition: 0.3s; 127 | } 128 | 129 | .card_name { 130 | flex: 1; 131 | color: $dark-gray; 132 | font-family: san-fran-med, $back-up-fonts; 133 | white-space: pre-wrap; 134 | width: 100%; 135 | cursor: text; 136 | resize: none; 137 | padding: 10px; 138 | font-size: $normal-font-size; 139 | } 140 | 141 | .untitled { 142 | font-family: san-fran-med, $back-up-fonts; 143 | opacity: 0.7; 144 | } 145 | 146 | .close-card { 147 | cursor: pointer; 148 | color: $medium-gray; 149 | border-radius: 3px; 150 | padding: 0px 3px 2px 3px; 151 | opacity: 0; 152 | transition: 0.3s; 153 | 154 | &:hover { 155 | background-color: $hover-color; 156 | } 157 | } 158 | } 159 | 160 | .Kanban { 161 | margin-top: 10px; 162 | margin-bottom: 30px; 163 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_link_to_page.scss: -------------------------------------------------------------------------------- 1 | .page_link { 2 | color: $dark-gray; 3 | width: 100%; 4 | 5 | i { 6 | font-size: 1.1rem; 7 | } 8 | 9 | p { 10 | font-family: san-fran-med, $back-up-fonts; 11 | font-size: 1rem; 12 | border-bottom: 1px solid rgba(55, 53, 47, 0.16); 13 | display: inline; 14 | margin-left: 10px; 15 | width: 100%; 16 | } 17 | } 18 | 19 | .Page_link { 20 | margin-top: 2px; 21 | align-items: center; 22 | } 23 | 24 | .hover-overlay { 25 | width: 100%; 26 | height: 100%; 27 | padding: 5px 3px; 28 | 29 | &:hover { 30 | background-color: $hover-color; 31 | } 32 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_templates.scss: -------------------------------------------------------------------------------- 1 | .empty-page-text { 2 | margin-left: 55px; 3 | color: rgba(55, 53, 47, 0.4); 4 | 5 | div:first-child { 6 | margin-bottom: 10px; 7 | } 8 | 9 | a { 10 | text-decoration: underline; 11 | cursor: pointer; 12 | margin-left: 5px; 13 | 14 | i { 15 | margin-right: 5px; 16 | } 17 | } 18 | } 19 | 20 | .templates-modal { 21 | width: 40%; 22 | padding: 10px 0px; 23 | 24 | .template-option { 25 | display: flex; 26 | align-items: center; 27 | padding-left: 15px; 28 | cursor: pointer; 29 | 30 | &:hover { 31 | background-color: $hover-color; 32 | } 33 | 34 | span { 35 | font-size: 1.3rem; 36 | margin-right: 10px; 37 | margin-bottom: 5px; 38 | } 39 | } 40 | 41 | .pen, .inbox { 42 | span { 43 | margin-bottom: 0; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_text.scss: -------------------------------------------------------------------------------- 1 | .text-element { 2 | width: 100%; 3 | 4 | .text { 5 | resize: none; 6 | border: none; 7 | font-size: 1rem; 8 | color: $dark-gray; 9 | width: 100%; 10 | line-height: 1.4; 11 | background: none; 12 | align-items: center; 13 | display: flex; 14 | padding: 5px 3px; 15 | 16 | &:focus { 17 | outline: none; 18 | } 19 | } 20 | } 21 | 22 | .Text { 23 | margin-bottom: 7px; 24 | 25 | .element-options { 26 | margin-top: 5px; 27 | } 28 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/components/body/_to_do.scss: -------------------------------------------------------------------------------- 1 | // To do container 2 | .to-do { 3 | display: flex; 4 | margin-left: 3px; 5 | width: 100%; 6 | padding: 5px; 7 | 8 | input[type="text"] { 9 | color: $dark-gray; 10 | font-size: 1rem; 11 | margin-left: 10px; 12 | width: 100%; 13 | background: none; 14 | 15 | &::placeholder { 16 | color: $medium-gray; 17 | } 18 | } 19 | 20 | // Text styling for when a to-do item is marked as checked/completed 21 | .completed { 22 | color: $medium-gray !important; 23 | text-decoration: line-through; 24 | } 25 | } 26 | 27 | // Checkbox 28 | .checkbox { 29 | height: 14px; 30 | margin-top: 1px; 31 | position: relative; 32 | 33 | input[type="checkbox"] { 34 | appearance: none; 35 | -webkit-appearance: none; 36 | height: 14px; 37 | width: 14px; 38 | box-shadow: 0px 0px 0px 1.5px black; 39 | cursor: pointer; 40 | 41 | &:checked { 42 | background-color: #2eaadb; 43 | box-shadow: 0px 0px 0px 1.5px #2eaadb; 44 | } 45 | } 46 | 47 | .to-do-check { 48 | color: white; 49 | position: absolute; 50 | left: -1px; 51 | font-weight: 400; 52 | cursor: pointer; 53 | height: 14px; 54 | } 55 | } 56 | 57 | .To_do { 58 | margin-bottom: 3px; 59 | align-items: center; 60 | } -------------------------------------------------------------------------------- /frontend/static/frontend/styles/sass/index.scss: -------------------------------------------------------------------------------- 1 | // Base 2 | @import "base/_layout"; 3 | @import "base/_mixins"; 4 | @import "base/_variables"; 5 | 6 | // Home 7 | @import "components/_home"; 8 | 9 | // Components 10 | @import "components/_menu"; 11 | @import "components/_dropdown"; 12 | @import "components/modal"; 13 | @import "components/_loader"; 14 | 15 | // Body 16 | @import "components/body/_general"; 17 | @import "components/body/_body-header"; 18 | @import "components/body/templates"; 19 | @import "components/body/_cover_photo"; 20 | @import "components/body/_element_options"; 21 | @import "components/body/_kanban"; 22 | @import "components/body/_colors"; 23 | @import "components/body/_table"; 24 | @import "components/body/_text"; 25 | @import "components/body/_to_do"; 26 | @import "components/body/_headers"; 27 | @import "components/body/_link_to_page"; 28 | 29 | // Login and Register 30 | @import "components/_login-register"; 31 | 32 | @import "base/media_queries"; -------------------------------------------------------------------------------- /frontend/templates/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% load static %} 7 | 8 | 9 | 10 | Notion 11 | 12 | 13 |
14 | {% load static %} 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /frontend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .import views 3 | from django.contrib import admin 4 | 5 | urlpatterns = [ 6 | path('admin/', admin.site.urls), 7 | path('', views.index, name="index"), 8 | path('login', views.index, name="index"), 9 | path('register', views.index, name="index"), 10 | path('', views.index, name="index") 11 | ] -------------------------------------------------------------------------------- /frontend/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse, HttpResponseNotFound 3 | import os 4 | from django.views import View 5 | 6 | def index(request, *args, **kwargs): 7 | return render(request, "frontend/index.html") 8 | 9 | class Assets(View): 10 | 11 | def get(self, _request, filename): 12 | path = os.path.join(os.path.dirname(__file__), 'static', filename) 13 | 14 | if os.path.isfile(path): 15 | with open(path, 'rb') as file: 16 | return HttpResponse(file.read(), content_type='application/javascript') 17 | else: 18 | return HttpResponseNotFound() 19 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'notionClone.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /notionClone/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobi4120/notion-clone/9cf5a34e2bf9e3b2a27ed6166aa28e32c59c9295/notionClone/__init__.py -------------------------------------------------------------------------------- /notionClone/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for notionClone project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'notionClone.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /notionClone/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for notionClone project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path, os 14 | from datetime import timedelta 15 | import environ 16 | 17 | # Initialise environment variables 18 | env = environ.Env() 19 | environ.Env.read_env() 20 | 21 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 22 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent 23 | 24 | # Quick-start development settings - unsuitable for production 25 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 26 | 27 | # SECURITY WARNING: keep the secret key used in production secret! 28 | SECRET_KEY = 'm1925=zmpn6_1u39lr)=g^q)y+&6ae1=i$(2d=s@%xjc8n!o%e' 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = True 32 | 33 | ALLOWED_HOSTS = ['127.0.0.1', 'notion-app-clone.herokuapp.com', 'localhost'] 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | 'django.contrib.admin', 39 | 'django.contrib.auth', 40 | 'django.contrib.contenttypes', 41 | 'django.contrib.sessions', 42 | 'django.contrib.messages', 43 | 'django.contrib.staticfiles', 44 | 'backend', 45 | 'rest_framework', 46 | 'knox', 47 | 'corsheaders', 48 | 'frontend', 49 | 'storages', 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | 'corsheaders.middleware.CorsMiddleware', 54 | 'django.middleware.security.SecurityMiddleware', 55 | 'django.contrib.sessions.middleware.SessionMiddleware', 56 | 'django.middleware.common.CommonMiddleware', 57 | 'django.middleware.csrf.CsrfViewMiddleware', 58 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 59 | 'django.contrib.messages.middleware.MessageMiddleware', 60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 61 | 'whitenoise.middleware.WhiteNoiseMiddleware', 62 | ] 63 | 64 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 65 | 66 | ROOT_URLCONF = 'notionClone.urls' 67 | 68 | TEMPLATES = [ 69 | { 70 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 71 | 'DIRS': [os.path.join(BASE_DIR, 'frontend')], 72 | 'APP_DIRS': True, 73 | 'OPTIONS': { 74 | 'context_processors': [ 75 | 'django.template.context_processors.debug', 76 | 'django.template.context_processors.request', 77 | 'django.contrib.auth.context_processors.auth', 78 | 'django.contrib.messages.context_processors.messages', 79 | ], 80 | }, 81 | }, 82 | ] 83 | 84 | WSGI_APPLICATION = 'notionClone.wsgi.application' 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 88 | 89 | DATABASES = { 90 | 'default': { 91 | 'ENGINE': 'django.db.backends.sqlite3', 92 | 'NAME': BASE_DIR / 'db.sqlite3', 93 | } 94 | } 95 | 96 | import dj_database_url 97 | db_from_env = dj_database_url.config(conn_max_age=600) 98 | DATABASES['default'].update(db_from_env) 99 | 100 | # Password validation 101 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 102 | 103 | AUTH_PASSWORD_VALIDATORS = [ 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 106 | }, 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 109 | }, 110 | { 111 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 112 | }, 113 | { 114 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 115 | }, 116 | ] 117 | 118 | # Internationalization 119 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 120 | 121 | LANGUAGE_CODE = 'en-us' 122 | 123 | TIME_ZONE = 'UTC' 124 | 125 | USE_I18N = True 126 | 127 | USE_L10N = True 128 | 129 | USE_TZ = True 130 | 131 | # Static files (CSS, JavaScript, Images) 132 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 133 | 134 | STATIC_URL = '/static/' 135 | 136 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 137 | 138 | STATIC_DIRS = { 139 | os.path.join(BASE_DIR, 'frontend/static') 140 | } 141 | 142 | AUTH_USER_MODEL = 'backend.User' 143 | 144 | REST_FRAMEWORK = { 145 | 'DEFAULT_PERMISSION_CLASSES': ( 146 | 'rest_framework.permissions.AllowAny', 147 | ), 148 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 149 | 'knox.auth.TokenAuthentication', 150 | 'rest_framework.authentication.SessionAuthentication', 151 | 'rest_framework.authentication.BasicAuthentication', 152 | ), 153 | } 154 | 155 | SIMPLE_JWT = { 156 | 'REFRESH_TOKEN_LIFETIME': timedelta(days=15), 157 | 'ROTATE_REFRESH_TOKENS': True, 158 | } 159 | 160 | CORS_ORIGIN_ALLOW_ALL = True 161 | 162 | REST_KNOX = { 163 | 'TOKEN_TTL': None 164 | } -------------------------------------------------------------------------------- /notionClone/urls.py: -------------------------------------------------------------------------------- 1 | """notionClone URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | from django.views.generic import TemplateView 19 | from rest_framework_jwt.views import obtain_jwt_token 20 | 21 | urlpatterns = [ 22 | path('', include('backend.urls')), 23 | path('', include('frontend.urls')), 24 | path('admin/', admin.site.urls) 25 | ] 26 | -------------------------------------------------------------------------------- /notionClone/utils.py: -------------------------------------------------------------------------------- 1 | from backend.serializers import UserSerializer 2 | 3 | def my_jwt_response_handler(token, user=None, request=None): 4 | return { 5 | 'token': token, 6 | 'user': UserSerializer(user, context={'request': request}).data 7 | } -------------------------------------------------------------------------------- /notionClone/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for notionClone project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'notionClone.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notion-clone", 3 | "version": "1.0.0", 4 | "description": "For this project I am creating a Notion clone on a smaller scale. This website will allow users to create pages, add notes to those pages, add to-do's, link to other pages and more!", 5 | "main": "index.js", 6 | "engines": { 7 | "node": "14.x" 8 | }, 9 | "dependencies": { 10 | "@date-io/date-fns": "^1.3.13", 11 | "@emotion/react": "^11.10.0", 12 | "@emotion/styled": "^11.10.0", 13 | "@material-ui/core": "^4.12.4", 14 | "@material-ui/icons": "^4.11.3", 15 | "@material-ui/lab": "^4.0.0-alpha.57", 16 | "@material-ui/pickers": "^3.2.10", 17 | "@mui/icons-material": "^5.8.4", 18 | "@mui/material": "^5.10.0", 19 | "axios": "^0.21.0", 20 | "date-fns": "^2.17.0", 21 | "framer-motion": "^7.1.0", 22 | "prop-types": "^15.7.2", 23 | "react": "^18.2.0", 24 | "react-beautiful-dnd": "^13.0.0", 25 | "react-dom": "^18.2.0", 26 | "react-redux": "^7.2.2", 27 | "react-router-dom": "^6.3.0", 28 | "react-textarea-autosize": "^8.3.0", 29 | "redux": "^4.0.5", 30 | "redux-devtools-extension": "^2.13.8", 31 | "redux-form": "^8.3.7", 32 | "redux-thunk": "^2.3.0" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.12.9", 36 | "@babel/preset-env": "^7.12.7", 37 | "@babel/preset-react": "^7.12.7", 38 | "babel-core": "^6.26.3", 39 | "babel-loader": "^8.2.2", 40 | "babel-plugin-transform-class-properties": "^6.24.1", 41 | "babel-polyfill": "^6.26.0", 42 | "babel-preset-es2015": "^6.24.1", 43 | "babel-preset-stage-0": "^6.24.1", 44 | "file-loader": "^6.2.0", 45 | "node-sass": "^7.0.1", 46 | "webpack": "^5.8.0", 47 | "webpack-cli": "^4.2.0" 48 | }, 49 | "scripts": { 50 | "dev": "concurrently \"npm run startApp\" \"python3 manage.py runserver\" \"npm run compile:sass\"", 51 | "startApp": "webpack --mode development --watch ./frontend/src/index.js -o ./frontend/static/frontend/", 52 | "build": "webpack --mode production ./frontend/src/index.js -o ./frontend/static/frontend/", 53 | "compile:sass": "node-sass frontend/static/frontend/styles/sass/index.scss frontend/static/frontend/styles/css/index.css -w" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/tobi4120/notion-clone.git" 58 | }, 59 | "keywords": [], 60 | "author": "", 61 | "license": "ISC", 62 | "bugs": { 63 | "url": "https://github.com/tobi4120/notion-clone/issues" 64 | }, 65 | "homepage": "https://github.com/tobi4120/notion-clone#readme" 66 | } 67 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | appdirs==1.4.4 3 | asgiref==3.2.10 4 | astor==0.8.1 5 | astroid==2.4.2 6 | attrs==19.3.0 7 | autoflake==1.4 8 | Babel==2.9.0 9 | bandit==1.7.0 10 | black==20.8b1 11 | boto3==1.17.33 12 | botocore==1.20.33 13 | cachelib==0.1.1 14 | certifi==2020.6.20 15 | cffi==1.14.1 16 | chardet==3.0.4 17 | click==7.1.2 18 | coverage==5.5 19 | cryptography==3.0 20 | cs50==6.0.2 21 | cycler==0.10.0 22 | darglint==1.7.0 23 | distlib==0.3.1 24 | dj-database-url==0.5.0 25 | Django==3.1 26 | django-cors-headers==3.5.0 27 | django-environ==0.4.5 28 | django-heroku==0.3.1 29 | django-rest-knox==4.1.0 30 | django-storages==1.11.1 31 | djangorestframework==3.11.1 32 | djangorestframework-jwt==1.11.0 33 | djangorestframework-simplejwt==4.6.0 34 | doc8==0.8.1 35 | docutils==0.16 36 | eradicate==1.0 37 | facebook-sdk==3.1.0 38 | filelock==3.0.12 39 | flake8==3.9.0 40 | flake8-2020==1.6.0 41 | flake8-bandit==2.1.2 42 | flake8-broken-line==0.2.1 43 | flake8-bugbear==19.8.0 44 | flake8-builtins==1.5.3 45 | flake8-commas==2.0.0 46 | flake8-comprehensions==3.4.0 47 | flake8-debugger==3.2.1 48 | flake8-docstrings==1.6.0 49 | flake8-eradicate 50 | flake8-fixme==1.1.1 51 | flake8-isort==3.0.1 52 | flake8-logging-format==0.6.0 53 | flake8-mutable==1.2.0 54 | flake8-polyfill==1.0.2 55 | flake8-quotes==2.1.2 56 | flake8-rst-docstrings==0.0.12 57 | flake8-string-format==0.2.3 58 | flake8-variables-names==0.0.3 59 | Flask==1.1.2 60 | Flask-Session==0.3.2 61 | gitdb==4.0.5 62 | GitPython==3.1.14 63 | gunicorn==20.0.4 64 | idna==2.10 65 | imagesize==1.2.0 66 | iniconfig==1.1.1 67 | isort==4.3.21 68 | itsdangerous==1.1.0 69 | jellyfish==0.8.2 70 | Jinja2==2.11.2 71 | jmespath==0.10.0 72 | kiwisolver==1.3.1 73 | lazy-object-proxy==1.4.3 74 | lib50==2.0.8 75 | Markdown==3.2.2 76 | markdown2==2.3.9 77 | MarkupSafe==1.1.1 78 | matplotlib==3.3.3 79 | mccabe==0.6.1 80 | mypy-extensions==0.4.3 81 | mysql-connector==2.2.9 82 | mysql-connector-python==8.0.20 83 | numpy==1.19.4 84 | packaging==20.9 85 | pathspec==0.8.1 86 | pbr==5.5.1 87 | pep8-naming==0.9.1 88 | pexpect==4.8.0 89 | Pillow==8.0.1 90 | pluggy==0.13.1 91 | protobuf==3.12.2 92 | psycopg2==2.8.6 93 | psycopg2-binary==2.8.6 94 | ptyprocess==0.6.0 95 | py==1.10.0 96 | pycodestyle==2.7.0 97 | pycparser==2.20 98 | pydocstyle==5.1.1 99 | pyflakes==2.3.0 100 | Pygments==2.8.1 101 | PyJWT==1.7.1 102 | pylint==2.5.3 103 | pyparsing==2.4.7 104 | pytest==6.2.2 105 | pytest-cov==2.11.1 106 | python-dateutil==2.8.1 107 | python-dev-tools 108 | python-dotenv==0.17.0 109 | pytz==2020.1 110 | pyupgrade==2.11.0 111 | PyYAML==5.3.1 112 | regex==2021.3.17 113 | requests==2.24.0 114 | restructuredtext-lint==1.3.2 115 | s3transfer==0.3.6 116 | six==1.15.0 117 | smmap==3.0.5 118 | snowballstemmer==2.1.0 119 | Sphinx==2.4.4 120 | sphinxcontrib-applehelp==1.0.2 121 | sphinxcontrib-devhelp==1.0.2 122 | sphinxcontrib-htmlhelp==1.0.3 123 | sphinxcontrib-jsmath==1.0.1 124 | sphinxcontrib-qthelp==1.0.3 125 | sphinxcontrib-serializinghtml==1.1.4 126 | SQLAlchemy==1.3.22 127 | sqlparse==0.3.1 128 | stevedore==3.3.0 129 | submit50==3.0.2 130 | termcolor==1.1.0 131 | testfixtures==6.17.1 132 | tokenize-rt==4.1.0 133 | toml==0.10.1 134 | tox==3.23.0 135 | tox-travis==0.12 136 | typed-ast==1.4.2 137 | typing-extensions==3.7.4.3 138 | urllib3==1.25.9 139 | virtualenv==20.2.0 140 | wemake-python-styleguide==0.14.1 141 | Werkzeug==1.0.1 142 | whitenoise==5.2.0 143 | wrapt==1.12.1 144 | XlsxWriter==1.3.7 145 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.2 -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.js$/, 6 | exclude: /node_modules/, 7 | use: { 8 | loader: "babel-loader", 9 | } 10 | }, 11 | { 12 | test: /\.(png|jpe?g|gif)$/i, 13 | use: [ 14 | { 15 | loader: 'file-loader', 16 | }, 17 | ], 18 | }, 19 | ] 20 | } 21 | } --------------------------------------------------------------------------------