├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .dockerignore
├── .github
└── FUNDING.yaml
├── .gitignore
├── .htmlnanorc
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.experimental
├── LICENSE
├── Pipfile
├── Pipfile.lock
├── README.md
├── client
├── App.vue
├── api.js
├── assets
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ └── fonts
│ │ └── Poppins
│ │ ├── OFL.txt
│ │ ├── Poppins-Italic.ttf
│ │ ├── Poppins-Regular.ttf
│ │ ├── Poppins-SemiBold.ttf
│ │ └── Poppins-SemiBoldItalic.ttf
├── classes.js
├── components
│ ├── ConfirmModal.vue
│ ├── CustomButton.vue
│ ├── IconLabel.vue
│ ├── LoadingIndicator.vue
│ ├── Logo.vue
│ ├── Modal.vue
│ ├── PrimeMenu.vue
│ ├── PrimeToast.vue
│ ├── Tag.vue
│ ├── TextInput.vue
│ ├── Toggle.vue
│ └── toastui
│ │ ├── ToastEditor.vue
│ │ ├── ToastViewer.vue
│ │ ├── baseOptions.js
│ │ ├── extendedAutolinks.js
│ │ └── toastui-editor-overrides.scss
├── constants.js
├── globalStore.js
├── helpers.js
├── index.html
├── index.js
├── partials
│ ├── NavBar.vue
│ ├── SearchInput.vue
│ └── SearchModal.vue
├── public
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── safari-pinned-tab.svg
│ └── site.webmanifest
├── router.js
├── style.css
├── tokenStorage.js
└── views
│ ├── Home.vue
│ ├── LogIn.vue
│ ├── Note.vue
│ └── SearchResults.vue
├── docs
└── logo.svg
├── entrypoint.sh
├── healthcheck.sh
├── package-lock.json
├── package.json
├── postcss.config.js
├── prettier.config.js
├── pyproject.toml
├── server
├── api_messages.py
├── attachments
│ ├── base.py
│ ├── file_system
│ │ ├── __init__.py
│ │ └── file_system.py
│ └── models.py
├── auth
│ ├── base.py
│ ├── local
│ │ ├── __init__.py
│ │ └── local.py
│ └── models.py
├── global_config.py
├── helpers.py
├── logger.py
├── main.py
└── notes
│ ├── base.py
│ ├── file_system
│ ├── __init__.py
│ └── file_system.py
│ └── models.py
├── tailwind.config.js
└── vite.config.js
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.11-slim-bullseye
2 |
3 | # Install pipenv
4 | RUN pip install --no-cache-dir pipenv
5 |
6 | # Install curl & git
7 | RUN apt update && apt install -y \
8 | curl \
9 | git \
10 | && rm -rf /var/lib/apt/lists/*
11 |
12 | # Install Node.js
13 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash \
14 | && export NVM_DIR="$HOME/.nvm" \
15 | && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" \
16 | && nvm install 20
17 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2 | // README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
3 | {
4 | "name": "Dev Container",
5 | "build": {
6 | // Sets the run context to one level up instead of the .devcontainer folder.
7 | "context": "..",
8 | // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
9 | "dockerfile": "./Dockerfile"
10 | },
11 |
12 | // Features to add to the dev container. More info: https://containers.dev/features.
13 | // "features": {},
14 |
15 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
16 | "forwardPorts": [
17 | 8080
18 | ],
19 |
20 | // Uncomment the next line to run commands after the container is created.
21 | // "postCreateCommand": "cat /etc/os-release",
22 |
23 | // Configure tool-specific properties.
24 | "customizations": {
25 | "vscode": {
26 | "extensions": [
27 | "ms-python.python",
28 | "ms-azuretools.vscode-docker",
29 | "ms-python.black-formatter",
30 | "esbenp.prettier-vscode",
31 | "Vue.volar"
32 | ]
33 | }
34 | }
35 |
36 | // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
37 | // "remoteUser": "devcontainer"
38 | }
39 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Ignore everything
2 | **
3 |
4 | # Include the following
5 | !.htmlnanorc
6 | !LICENSE
7 | !Pipfile
8 | !Pipfile.lock
9 | !client/**
10 | !entrypoint.sh
11 | !healthcheck.sh
12 | !package-lock.json
13 | !package.json
14 | !postcss.config.js
15 | !server/**
16 | !tailwind.config.js
17 | !vite.config.js
18 |
--------------------------------------------------------------------------------
/.github/FUNDING.yaml:
--------------------------------------------------------------------------------
1 | github: Dullage
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python
2 |
3 | # Byte-compiled / optimized / DLL files
4 | __pycache__/
5 | *.py[cod]
6 | *$py.class
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 | cover/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | .pybuilder/
78 | target/
79 |
80 | # Jupyter Notebook
81 | .ipynb_checkpoints
82 |
83 | # IPython
84 | profile_default/
85 | ipython_config.py
86 |
87 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
88 | __pypackages__/
89 |
90 | # Celery stuff
91 | celerybeat-schedule
92 | celerybeat.pid
93 |
94 | # SageMath parsed files
95 | *.sage.py
96 |
97 | # Environments
98 | .env
99 | .venv
100 | env/
101 | venv/
102 | ENV/
103 | env.bak/
104 | venv.bak/
105 |
106 | # Spyder project settings
107 | .spyderproject
108 | .spyproject
109 |
110 | # Rope project settings
111 | .ropeproject
112 |
113 | # mkdocs documentation
114 | /site
115 |
116 | # mypy
117 | .mypy_cache/
118 | .dmypy.json
119 | dmypy.json
120 |
121 | # Pyre type checker
122 | .pyre/
123 |
124 | # pytype static type analyzer
125 | .pytype/
126 |
127 | # Cython debug symbols
128 | cython_debug/
129 |
130 |
131 | # Node
132 |
133 | # Logs
134 | logs
135 | npm-debug.log*
136 | yarn-debug.log*
137 | yarn-error.log*
138 | lerna-debug.log*
139 | .pnpm-debug.log*
140 |
141 | # Diagnostic reports (https://nodejs.org/api/report.html)
142 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
143 |
144 | # Runtime data
145 | pids
146 | *.pid
147 | *.seed
148 | *.pid.lock
149 |
150 | # Directory for instrumented libs generated by jscoverage/JSCover
151 | lib-cov
152 |
153 | # Coverage directory used by tools like istanbul
154 | coverage
155 | *.lcov
156 |
157 | # nyc test coverage
158 | .nyc_output
159 |
160 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins
161 | #storing-task-files)
162 | .grunt
163 |
164 | # Bower dependency directory (https://bower.io/)
165 | bower_components
166 |
167 | # node-waf configuration
168 | .lock-wscript
169 |
170 | # Compiled binary addons (https://nodejs.org/api/addons.html)
171 | build/Release
172 |
173 | # Dependency directories
174 | node_modules/
175 | jspm_packages/
176 |
177 | # Snowpack dependency directory (https://snowpack.dev/)
178 | web_modules/
179 |
180 | # TypeScript cache
181 | *.tsbuildinfo
182 |
183 | # Optional npm cache directory
184 | .npm
185 |
186 | # Optional eslint cache
187 | .eslintcache
188 |
189 | # Microbundle cache
190 | .rpt2_cache/
191 | .rts2_cache_cjs/
192 | .rts2_cache_es/
193 | .rts2_cache_umd/
194 |
195 | # Optional REPL history
196 | .node_repl_history
197 |
198 | # Output of 'npm pack'
199 | *.tgz
200 |
201 | # Yarn Integrity file
202 | .yarn-integrity
203 |
204 | # dotenv environment variables file
205 | .env.test
206 | .env.production
207 |
208 | # parcel-bundler cache (https://parceljs.org/)
209 | .parcel-cache
210 |
211 | # Next.js build output
212 | .next
213 | out
214 |
215 | # Nuxt.js build / generate output
216 | .nuxt
217 | dist
218 |
219 | # Gatsby files
220 | .cache/
221 |
222 | # vuepress build output
223 | .vuepress/dist
224 |
225 | # Serverless directories
226 | .serverless/
227 |
228 | # FuseBox cache
229 | .fusebox/
230 |
231 | # DynamoDB Local files
232 | .dynamodb/
233 |
234 | # TernJS port file
235 | .tern-port
236 |
237 | # Stores VSCode versions used for testing VSCode extensions
238 | .vscode-test
239 |
240 | # yarn v2
241 | .yarn/cache
242 | .yarn/unplugged
243 | .yarn/build-state.yml
244 | .yarn/install-state.gz
245 | .pnp.*
246 |
247 | # Custom
248 | .vscode/
249 | data/
250 | /notes/
251 |
--------------------------------------------------------------------------------
/.htmlnanorc:
--------------------------------------------------------------------------------
1 | {
2 | "minifySvg": false
3 | }
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to flatnotes
2 |
3 | ## Bug Fixes
4 |
5 | If you spot something not quite right in flatnotes and have the skills to fix it, then I'd welcome the pull request. If the fix requires a large change, then it would be best to open an issue first to discuss.
6 |
7 | ## New Features
8 |
9 | If you're interested in adding a new feature to flatnotes, then please open an issue first to discuss the idea. This will help to ensure that the feature is a good fit for the project and that you're not wasting your time. Whilst I'm keen to improve flatnotes, I'm committed to keeping it simple and focused, which will mean saying "no" more than I say "yes".
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG BUILD_DIR=/build
2 |
3 | # Build Container
4 | FROM --platform=$BUILDPLATFORM node:20-alpine AS build
5 |
6 | ARG BUILD_DIR
7 |
8 | RUN mkdir ${BUILD_DIR}
9 | WORKDIR ${BUILD_DIR}
10 |
11 | COPY .htmlnanorc \
12 | package.json \
13 | package-lock.json \
14 | postcss.config.js \
15 | tailwind.config.js \
16 | vite.config.js \
17 | ./
18 |
19 | RUN npm ci
20 |
21 | COPY client ./client
22 | RUN npm run build
23 |
24 | # Runtime Container
25 | FROM python:3.11-slim-bullseye
26 |
27 | ARG BUILD_DIR
28 |
29 | ENV PUID=1000
30 | ENV PGID=1000
31 | ENV EXEC_TOOL=gosu
32 | ENV FLATNOTES_HOST=0.0.0.0
33 | ENV FLATNOTES_PORT=8080
34 |
35 | ENV APP_PATH=/app
36 | ENV FLATNOTES_PATH=/data
37 |
38 | RUN mkdir -p ${APP_PATH}
39 | RUN mkdir -p ${FLATNOTES_PATH}
40 |
41 | RUN apt update && apt install -y \
42 | curl \
43 | gosu \
44 | && rm -rf /var/lib/apt/lists/*
45 |
46 | RUN pip install --no-cache-dir pipenv
47 |
48 | WORKDIR ${APP_PATH}
49 |
50 | COPY LICENSE Pipfile Pipfile.lock ./
51 | RUN pipenv install --deploy --ignore-pipfile --system && \
52 | pipenv --clear
53 |
54 | COPY server ./server
55 | COPY --from=build --chmod=777 ${BUILD_DIR}/client/dist ./client/dist
56 |
57 | COPY entrypoint.sh healthcheck.sh /
58 | RUN chmod +x /entrypoint.sh /healthcheck.sh
59 |
60 | VOLUME /data
61 | EXPOSE ${FLATNOTES_PORT}/tcp
62 | HEALTHCHECK --interval=60s --timeout=10s CMD /healthcheck.sh
63 |
64 | ENTRYPOINT [ "/entrypoint.sh" ]
65 |
--------------------------------------------------------------------------------
/Dockerfile.experimental:
--------------------------------------------------------------------------------
1 | ARG BUILD_DIR=/build
2 |
3 | # Client Build Container
4 | FROM --platform=$BUILDPLATFORM node:20-alpine AS build
5 |
6 | ARG BUILD_DIR
7 |
8 | RUN mkdir ${BUILD_DIR}
9 | WORKDIR ${BUILD_DIR}
10 |
11 | COPY .htmlnanorc \
12 | package.json \
13 | package-lock.json \
14 | postcss.config.js \
15 | tailwind.config.js \
16 | vite.config.js \
17 | ./
18 |
19 | RUN npm ci
20 |
21 | COPY client ./client
22 | RUN npm run build
23 |
24 | # Pipenv Build Container
25 | FROM python:3.11-alpine3.20 as pipenv-build
26 |
27 | ARG BUILD_DIR
28 |
29 | ENV APP_PATH=/app
30 |
31 | RUN apk add --no-cache build-base rust cargo libffi libffi-dev libssl3 openssl-dev
32 |
33 | RUN pip install --no-cache-dir pipenv
34 |
35 | WORKDIR ${APP_PATH}
36 |
37 | COPY LICENSE Pipfile Pipfile.lock ./
38 | RUN mkdir .venv
39 | RUN pipenv install --deploy --ignore-pipfile && \
40 | pipenv --clear
41 |
42 | # Runtime Container
43 | FROM python:3.11-alpine3.20
44 |
45 | ARG BUILD_DIR
46 |
47 | ENV PUID=1000
48 | ENV PGID=1000
49 | ENV EXEC_TOOL=su-exec
50 | ENV FLATNOTES_HOST=0.0.0.0
51 | ENV FLATNOTES_PORT=8080
52 |
53 | ENV APP_PATH=/app
54 | ENV FLATNOTES_PATH=/data
55 |
56 | RUN mkdir -p ${APP_PATH}
57 | RUN mkdir -p ${FLATNOTES_PATH}
58 |
59 | RUN apk add --no-cache su-exec libssl3 libgcc curl
60 |
61 | WORKDIR ${APP_PATH}
62 |
63 | COPY server ./server
64 | COPY --from=build --chmod=777 ${BUILD_DIR}/client/dist ./client/dist
65 | COPY --from=pipenv-build ${APP_PATH}/.venv/lib/python3.11/site-packages/ /usr/local/lib/python3.11/site-packages/
66 |
67 | COPY entrypoint.sh healthcheck.sh /
68 | RUN chmod +x /entrypoint.sh /healthcheck.sh
69 |
70 | VOLUME /data
71 | EXPOSE ${FLATNOTES_PORT}/tcp
72 | HEALTHCHECK --interval=60s --timeout=10s CMD /healthcheck.sh
73 |
74 | ENTRYPOINT [ "/entrypoint.sh" ]
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Adam Dullage
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 | black = "*"
8 | flake8 = "*"
9 | rope = "*"
10 | isort = "*"
11 |
12 | [packages]
13 | whoosh = "==2.7.4"
14 | fastapi = "==0.115.12"
15 | uvicorn = {extras = ["standard"], version = "==0.34.0"}
16 | aiofiles = "==24.1.0"
17 | python-jose = {extras = ["cryptography"], version = "==3.4.0"}
18 | pyotp = "==2.9.0"
19 | qrcode = "==8.0"
20 | python-multipart = "==0.0.20"
21 |
22 | [requires]
23 | python_version = "3.11"
24 |
25 | [pipenv]
26 | allow_prereleases = true
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | A self-hosted, database-less note-taking web app that utilises a flat folder of markdown files for storage.
9 |
10 | Log into the [demo site](https://demo.flatnotes.io) and take a look around. *Note: This site resets every 15 minutes.*
11 |
12 | ## Contents
13 |
14 | * [Design Principle](#design-principle)
15 | * [Features](#features)
16 | * [Getting Started](#getting-started)
17 | * [Hosted](#hosted)
18 | * [Self Hosted](#self-hosted)
19 | * [Roadmap](#roadmap)
20 | * [Contributing](#contributing)
21 | * [Sponsorship](#sponsorship)
22 | * [Thanks](#thanks)
23 |
24 | ## Design Principle
25 |
26 | flatnotes is designed to be a distraction-free note-taking app that puts your note content first. This means:
27 |
28 | * A clean and simple user interface.
29 | * No folders, notebooks or anything like that. Just all of your notes, backed by powerful search and tagging functionality.
30 | * Quick access to a full-text search from anywhere in the app (keyboard shortcut "/").
31 |
32 | Another key design principle is not to take your notes hostage. Your notes are just markdown files. There's no database, proprietary formatting, complicated folder structures or anything like that. You're free at any point to just move the files elsewhere and use another app.
33 |
34 | Equally, the only thing flatnotes caches is the search index and that's incrementally synced on every search (and when flatnotes first starts). This means that you're free to add, edit & delete the markdown files outside of flatnotes even whilst flatnotes is running.
35 |
36 | ## Features
37 |
38 | * Mobile responsive web interface.
39 | * Raw/WYSIWYG markdown editor modes.
40 | * Advanced search functionality.
41 | * Note "tagging" functionality.
42 | * Customisable home page.
43 | * Wikilink support to easily link to other notes (`[[My Other Note]]`).
44 | * Light/dark themes.
45 | * Multiple authentication options (none, read-only, username/password, 2FA).
46 | * Restful API.
47 |
48 | See [the wiki](https://github.com/dullage/flatnotes/wiki) for more details.
49 |
50 | ## Getting Started
51 |
52 | ### Hosted
53 |
54 | A quick and easy way to get started with flatnotes is to host it on PikaPods. Just click the button below and follow the instructions.
55 |
56 | [](https://www.pikapods.com/pods?run=flatnotes)
57 |
58 |
59 | ### Self Hosted
60 |
61 | If you'd prefer to host flatnotes yourself then the recommendation is to use Docker.
62 |
63 | ### Example Docker Run Command
64 |
65 | ```shell
66 | docker run -d \
67 | -e "PUID=1000" \
68 | -e "PGID=1000" \
69 | -e "FLATNOTES_AUTH_TYPE=password" \
70 | -e "FLATNOTES_USERNAME=user" \
71 | -e 'FLATNOTES_PASSWORD=changeMe!' \
72 | -e "FLATNOTES_SECRET_KEY=aLongRandomSeriesOfCharacters" \
73 | -v "$(pwd)/data:/data" \
74 | -p "8080:8080" \
75 | dullage/flatnotes:latest
76 | ```
77 |
78 | ### Example Docker Compose
79 | ```yaml
80 | version: "3"
81 |
82 | services:
83 | flatnotes:
84 | container_name: flatnotes
85 | image: dullage/flatnotes:latest
86 | environment:
87 | PUID: 1000
88 | PGID: 1000
89 | FLATNOTES_AUTH_TYPE: "password"
90 | FLATNOTES_USERNAME: "user"
91 | FLATNOTES_PASSWORD: "changeMe!"
92 | FLATNOTES_SECRET_KEY: "aLongRandomSeriesOfCharacters"
93 | volumes:
94 | - "./data:/data"
95 | # Optional. Allows you to save the search index in a different location:
96 | # - "./index:/data/.flatnotes"
97 | ports:
98 | - "8080:8080"
99 | restart: unless-stopped
100 | ```
101 |
102 | See the [Environment Variables](https://github.com/dullage/flatnotes/wiki/Environment-Variables) article in the wiki for a full list of configuration options.
103 |
104 | ## Roadmap
105 |
106 | I want to keep flatnotes as simple and distraction-free as possible which means limiting new features. This said, I welcome feedback and suggestions.
107 |
108 | ## Contributing
109 |
110 | If you're interested in contributing to flatnotes, then please read the [CONTRIBUTING.md](CONTRIBUTING.md) file.
111 |
112 | ## Sponsorship
113 |
114 | If you find this project useful, please consider buying me a beer. It would genuinely make my day.
115 |
116 | [](https://github.com/sponsors/Dullage)
117 |
118 | ## Thanks
119 |
120 | A special thanks to 2 fantastic open-source projects that make flatnotes possible.
121 |
122 | * [Whoosh](https://whoosh.readthedocs.io/en/latest/intro.html) - A fast, pure Python search engine library.
123 | * [TOAST UI Editor](https://ui.toast.com/tui-editor) - A GFM Markdown and WYSIWYG editor for the browser.
124 |
--------------------------------------------------------------------------------
/client/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
93 |
--------------------------------------------------------------------------------
/client/api.js:
--------------------------------------------------------------------------------
1 | import * as constants from "./constants.js";
2 |
3 | import { Note, SearchResult } from "./classes.js";
4 |
5 | import axios from "axios";
6 | import { getStoredToken } from "./tokenStorage.js";
7 | import { getToastOptions } from "./helpers.js";
8 | import router from "./router.js";
9 |
10 | const api = axios.create();
11 |
12 | api.interceptors.request.use(
13 | // If the request is not for the token endpoint, add the token to the headers.
14 | function (config) {
15 | if (config.url !== "api/token") {
16 | const token = getStoredToken();
17 | if (token) {
18 | config.headers.Authorization = `Bearer ${token}`;
19 | }
20 | }
21 | return config;
22 | },
23 | function (error) {
24 | return Promise.reject(error);
25 | },
26 | );
27 |
28 | export function apiErrorHandler(error, toast) {
29 | if (error.response?.status === 401) {
30 | const redirectPath = router.currentRoute.value.fullPath;
31 | router.push({
32 | name: "login",
33 | query: { [constants.params.redirect]: redirectPath },
34 | });
35 | } else {
36 | console.error(error);
37 | toast.add(
38 | getToastOptions(
39 | "Unknown error communicating with the server. Please try again.",
40 | "Unknown Error",
41 | "error",
42 | ),
43 | );
44 | }
45 | }
46 |
47 | export async function getConfig() {
48 | try {
49 | const response = await api.get("api/config");
50 | return response.data;
51 | } catch (error) {
52 | return Promise.reject(error);
53 | }
54 | }
55 |
56 | export async function getToken(username, password, totp) {
57 | try {
58 | const response = await api.post("api/token", {
59 | username: username,
60 | password: totp ? password + totp : password,
61 | });
62 | return response.data.access_token;
63 | } catch (response) {
64 | return Promise.reject(response);
65 | }
66 | }
67 |
68 | export async function getNotes(term, sort, order, limit) {
69 | try {
70 | const response = await api.get("api/search", {
71 | params: {
72 | term: term,
73 | sort: sort,
74 | order: order,
75 | limit: limit,
76 | },
77 | });
78 | return response.data.map((note) => new SearchResult(note));
79 | } catch (response) {
80 | return Promise.reject(response);
81 | }
82 | }
83 |
84 | export async function createNote(title, content) {
85 | try {
86 | const response = await api.post("api/notes", {
87 | title: title,
88 | content: content,
89 | });
90 | return new Note(response.data);
91 | } catch (response) {
92 | return Promise.reject(response);
93 | }
94 | }
95 |
96 | export async function getNote(title) {
97 | try {
98 | const response = await api.get(`api/notes/${encodeURIComponent(title)}`);
99 | return new Note(response.data);
100 | } catch (response) {
101 | return Promise.reject(response);
102 | }
103 | }
104 |
105 | export async function updateNote(title, newTitle, newContent) {
106 | try {
107 | const response = await api.patch(`api/notes/${encodeURIComponent(title)}`, {
108 | newTitle: newTitle,
109 | newContent: newContent,
110 | });
111 | return new Note(response.data);
112 | } catch (response) {
113 | return Promise.reject(response);
114 | }
115 | }
116 |
117 | export async function deleteNote(title) {
118 | try {
119 | await api.delete(`api/notes/${encodeURIComponent(title)}`);
120 | } catch (response) {
121 | return Promise.reject(response);
122 | }
123 | }
124 |
125 | export async function getTags() {
126 | try {
127 | const response = await api.get("api/tags");
128 | return response.data;
129 | } catch (response) {
130 | return Promise.reject(response);
131 | }
132 | }
133 |
134 | export async function createAttachment(file) {
135 | try {
136 | const formData = new FormData();
137 | formData.append("file", file);
138 | const response = await api.post("api/attachments", formData, {
139 | headers: {
140 | "Content-Type": "multipart/form-data",
141 | },
142 | });
143 | return response.data;
144 | } catch (response) {
145 | return Promise.reject(response);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/client/assets/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/apple-touch-icon.png
--------------------------------------------------------------------------------
/client/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/client/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/client/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/favicon.ico
--------------------------------------------------------------------------------
/client/assets/fonts/Poppins/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/client/assets/fonts/Poppins/Poppins-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/fonts/Poppins/Poppins-Italic.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/Poppins/Poppins-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/fonts/Poppins/Poppins-Regular.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/Poppins/Poppins-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/fonts/Poppins/Poppins-SemiBold.ttf
--------------------------------------------------------------------------------
/client/assets/fonts/Poppins/Poppins-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dullage/flatnotes/cf355dd8586cc484cde23e6a35c8fcf5dca331ac/client/assets/fonts/Poppins/Poppins-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/client/classes.js:
--------------------------------------------------------------------------------
1 | import router from "./router.js";
2 |
3 | class Note {
4 | constructor(note) {
5 | this.title = note?.title;
6 | this.lastModified = note?.lastModified;
7 | this.content = note?.content;
8 | }
9 |
10 | get lastModifiedAsDate() {
11 | return new Date(this.lastModified * 1000);
12 | }
13 |
14 | get lastModifiedAsString() {
15 | return this.lastModifiedAsDate.toLocaleString();
16 | }
17 | }
18 |
19 | class SearchResult extends Note {
20 | constructor(searchResult) {
21 | super(searchResult);
22 | this.score = searchResult.score;
23 | this.titleHighlights = searchResult.titleHighlights;
24 | this.contentHighlights = searchResult.contentHighlights;
25 | this.tagMatches = searchResult.tagMatches;
26 | }
27 |
28 | get titleHighlightsOrTitle() {
29 | return this.titleHighlights ? this.titleHighlights : this.title;
30 | }
31 |
32 | get includesHighlights() {
33 | if (
34 | this.titleHighlights ||
35 | this.contentHighlights ||
36 | (this.tagMatches != null && this.tagMatches.length)
37 | ) {
38 | return true;
39 | } else {
40 | return false;
41 | }
42 | }
43 | }
44 |
45 | export { Note, SearchResult };
46 |
--------------------------------------------------------------------------------
/client/components/ConfirmModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |