├── .dockerignore ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── components.json ├── docker-compose.yaml ├── docs ├── Changelog │ └── 3.0.0.md ├── Development │ ├── APIs.md │ ├── Markdown Rendering.md │ └── components.md ├── Markdown Syntax.md ├── image.png ├── installation.md ├── introduction.md ├── roadmap.md └── usage.md ├── eslint.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── .DS_Store ├── app.css ├── app.d.ts ├── app.html ├── lib │ ├── .DS_Store │ ├── components │ │ ├── FileTree.svelte │ │ ├── GraphScene.svelte │ │ ├── LoginForm.svelte │ │ ├── MDGraph.svelte │ │ ├── MDsvexRenderer.svelte │ │ ├── MarkdownGraph.svelte │ │ ├── Scene.svelte │ │ ├── SearchComponent.svelte │ │ ├── Sidebar.svelte │ │ ├── TagBar.svelte │ │ ├── TopBar.svelte │ │ ├── award.svelte │ │ ├── schema.ts │ │ └── ui │ │ │ ├── button │ │ │ ├── button.svelte │ │ │ └── index.ts │ │ │ ├── card │ │ │ ├── card-content.svelte │ │ │ ├── card-description.svelte │ │ │ ├── card-footer.svelte │ │ │ ├── card-header.svelte │ │ │ ├── card-title.svelte │ │ │ ├── card.svelte │ │ │ └── index.ts │ │ │ ├── dialog │ │ │ ├── dialog-content.svelte │ │ │ ├── dialog-description.svelte │ │ │ ├── dialog-footer.svelte │ │ │ ├── dialog-header.svelte │ │ │ ├── dialog-overlay.svelte │ │ │ ├── dialog-portal.svelte │ │ │ ├── dialog-title.svelte │ │ │ └── index.ts │ │ │ ├── form │ │ │ ├── form-button.svelte │ │ │ ├── form-description.svelte │ │ │ ├── form-element-field.svelte │ │ │ ├── form-field-errors.svelte │ │ │ ├── form-field.svelte │ │ │ ├── form-fieldset.svelte │ │ │ ├── form-label.svelte │ │ │ ├── form-legend.svelte │ │ │ └── index.ts │ │ │ ├── input │ │ │ ├── index.ts │ │ │ └── input.svelte │ │ │ ├── label │ │ │ ├── index.ts │ │ │ └── label.svelte │ │ │ ├── scroll-area │ │ │ ├── index.ts │ │ │ ├── scroll-area-scrollbar.svelte │ │ │ └── scroll-area.svelte │ │ │ └── separator │ │ │ ├── index.ts │ │ │ └── separator.svelte │ ├── highlightCode.ts │ ├── index.ts │ ├── md.ts │ ├── pbStore.ts │ ├── pocketbase.ts │ ├── remark-plugins │ │ ├── footNotes.js │ │ ├── highlightSyn.js │ │ ├── imgRel.js │ │ ├── mermaidDiag.js │ │ ├── obsidianImage.js │ │ └── remarkTags.ts │ ├── server │ │ └── auth.ts │ ├── stores │ │ └── sidebarStore.ts │ └── utils.ts ├── routes │ ├── +layout.server.ts │ ├── +layout.svelte │ ├── +page.server.ts │ ├── +page.svelte │ ├── .DS_Store │ ├── [...post].md │ │ ├── +page.server.ts │ │ └── +page.svelte │ ├── about │ │ └── +page.svelte │ ├── api │ │ ├── backlinks │ │ │ └── +server.ts │ │ ├── graph │ │ │ └── +server.ts │ │ ├── hello │ │ │ └── +server.ts │ │ ├── img │ │ │ └── [...path] │ │ │ │ └── +server.ts │ │ ├── links │ │ │ └── +server.ts │ │ ├── ls │ │ │ └── +server.ts │ │ ├── search │ │ │ └── +server.ts │ │ ├── tags │ │ │ └── +server.ts │ │ └── upload │ │ │ └── +server.ts │ ├── login │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ └── success │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ ├── publications │ │ └── +page.svelte │ └── tags │ │ └── [tag] │ │ ├── +page.server.ts │ │ └── +page.svelte └── writing │ ├── +page.server.ts │ ├── +page.svelte │ ├── .DS_Store │ ├── [...dir] │ ├── +page.server.ts │ └── +page.svelte │ └── [...post].md │ ├── +page.server.ts │ └── +page.svelte ├── start.sh ├── start_services.sh ├── static ├── .DS_Store ├── a11y-dark.min.css ├── a11y-light.min.css ├── favicon.png ├── fonts.css └── fonts │ ├── .DS_Store │ ├── IBMPlexMono-Bold.ttf │ ├── IBMPlexMono-BoldItalic.ttf │ ├── IBMPlexMono-ExtraLight.ttf │ ├── IBMPlexMono-ExtraLightItalic.ttf │ ├── IBMPlexMono-Italic.ttf │ ├── IBMPlexMono-Light.ttf │ ├── IBMPlexMono-LightItalic.ttf │ ├── IBMPlexMono-Medium.ttf │ ├── IBMPlexMono-MediumItalic.ttf │ ├── IBMPlexMono-Regular.ttf │ ├── IBMPlexMono-SemiBold.ttf │ ├── IBMPlexMono-SemiBoldItalic.ttf │ ├── IBMPlexMono-Text.ttf │ ├── IBMPlexMono-TextItalic.ttf │ ├── IBMPlexMono-Thin.ttf │ ├── IBMPlexMono-ThinItalic.ttf │ ├── IBMPlexSans-Bold.ttf │ ├── IBMPlexSans-BoldItalic.ttf │ ├── IBMPlexSans-ExtraLight.ttf │ ├── IBMPlexSans-ExtraLightItalic.ttf │ ├── IBMPlexSans-Italic.ttf │ ├── IBMPlexSans-Light.ttf │ ├── IBMPlexSans-LightItalic.ttf │ ├── IBMPlexSans-Medium.ttf │ ├── IBMPlexSans-MediumItalic.ttf │ ├── IBMPlexSans-Regular.ttf │ ├── IBMPlexSans-SemiBold.ttf │ ├── IBMPlexSans-SemiBoldItalic.ttf │ ├── IBMPlexSans-Text.ttf │ ├── IBMPlexSans-TextItalic.ttf │ ├── IBMPlexSans-Thin.ttf │ ├── IBMPlexSans-ThinItalic.ttf │ ├── Lombok.otf │ └── Megrim-Regular.ttf ├── supervisord.conf ├── svelte.config.js ├── tailwind.config.ts ├── tsconfig.json └── vite.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | # flyctl launch added from .gitignore 2 | # Byte-compiled / optimized / DLL files 3 | **/__pycache__ 4 | **/*.py[cod] 5 | **/*$py.class 6 | **/stdout 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 | **/lib64 20 | **/parts 21 | **/sdist 22 | **/var 23 | **/wheels 24 | **/share/python-wheels 25 | **/*.egg-info 26 | **/.installed.cfg 27 | **/*.egg 28 | **/MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | **/*.manifest 34 | **/*.spec 35 | 36 | # Installer logs 37 | **/pip-log.txt 38 | **/pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | **/htmlcov 42 | **/.tox 43 | **/.nox 44 | **/.coverage 45 | **/.coverage.* 46 | **/.cache 47 | **/nosetests.xml 48 | **/coverage.xml 49 | **/*.cover 50 | **/*.py,cover 51 | **/.hypothesis 52 | **/.pytest_cache 53 | **/cover 54 | 55 | # Translations 56 | **/*.mo 57 | **/*.pot 58 | 59 | # Django stuff: 60 | **/*.log 61 | **/local_settings.py 62 | **/db.sqlite3 63 | **/db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | **/instance 67 | **/.webassets-cache 68 | 69 | # Scrapy stuff: 70 | **/.scrapy 71 | 72 | # Sphinx documentation 73 | **/docs/_build 74 | 75 | # PyBuilder 76 | **/.pybuilder 77 | **/target 78 | 79 | # Jupyter Notebook 80 | **/.ipynb_checkpoints 81 | 82 | # IPython 83 | **/profile_default 84 | **/ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # poetry 99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 100 | # This is especially recommended for binary packages to ensure reproducibility, and is more 101 | # commonly ignored for libraries. 102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 103 | #poetry.lock 104 | 105 | # pdm 106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 107 | #pdm.lock 108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 109 | # in version control. 110 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 111 | **/.pdm.toml 112 | **/.pdm-python 113 | **/.pdm-build 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | **/__pypackages__ 117 | 118 | # Celery stuff 119 | **/celerybeat-schedule 120 | **/celerybeat.pid 121 | 122 | # SageMath parsed files 123 | **/*.sage.py 124 | 125 | # Environments 126 | **/.env 127 | **/.venv 128 | **/env 129 | **/venv 130 | **/ENV 131 | **/env.bak 132 | **/venv.bak 133 | 134 | # Spyder project settings 135 | **/.spyderproject 136 | **/.spyproject 137 | 138 | # Rope project settings 139 | **/.ropeproject 140 | 141 | # mkdocs documentation 142 | site 143 | 144 | # mypy 145 | **/.mypy_cache 146 | **/.dmypy.json 147 | **/dmypy.json 148 | 149 | # Pyre type checker 150 | **/.pyre 151 | 152 | # pytype static type analyzer 153 | **/.pytype 154 | 155 | # Cython debug symbols 156 | **/cython_debug 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ 164 | 165 | # flyctl launch added from .pytest_cache/.gitignore 166 | # Created by pytest automatically. 167 | .pytest_cache/**/* 168 | 169 | # flyctl launch added from .ruff_cache/.gitignore 170 | # Automatically created by ruff. 171 | .ruff_cache/**/* 172 | fly.toml 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | stdout 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: check-ast 6 | - id: check-merge-conflict 7 | - id: check-case-conflict 8 | - id: check-docstring-first 9 | - id: end-of-file-fixer 10 | - id: trailing-whitespace 11 | - id: mixed-line-ending 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | # Ruff version. 14 | rev: v0.5.1 15 | hooks: 16 | # Run the linter. 17 | - id: ruff 18 | args: [ --fix ] 19 | # Run the formatter. 20 | - id: ruff-format 21 | # - repo: local 22 | # hooks: 23 | # - id: pytest 24 | # name: pytest 25 | # entry: pytest 26 | # language: system 27 | # types: [python] 28 | # pass_filenames: false 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.9.0-alpine3.20 2 | 3 | ARG POCKETBASE_ADMIN_EMAIL 4 | ARG POCKETBASE_ADMIN_PASSWORD 5 | ARG POCKETBASE_URL 6 | ARG TITLE 7 | ARG API_KEY 8 | ARG CAP1 9 | ARG CAP2 10 | ARG CAP3 11 | 12 | # Set environment variables to be overridden at runtime 13 | ENV POCKETBASE_ADMIN_EMAIL=$POCKETBASE_ADMIN_EMAIL 14 | ENV POCKETBASE_ADMIN_PASSWORD=$POCKETBASE_ADMIN_PASSWORD 15 | ENV POCKETBASE_URL=$POCKETBASE_URL 16 | ENV TITLE=$TITLE 17 | ENV API_KEY=$API_KEY 18 | ENV CAP1=$CAP1 19 | ENV CAP2=$CAP2 20 | ENV CAP3=$CAP3 21 | 22 | # Install required packages 23 | RUN apk update && apk add --no-cache \ 24 | unzip \ 25 | curl 26 | 27 | # download and unzip PocketBase 28 | ADD https://github.com/pocketbase/pocketbase/releases/download/v0.22.21/pocketbase_0.22.21_linux_amd64.zip /tmp/pb.zip 29 | RUN unzip /tmp/pb.zip -d /pb/ 30 | 31 | # create PocketBase data directory 32 | RUN mkdir -p /pb/pb_data 33 | # COPY start.sh /pb/start.sh 34 | 35 | # start PocketBase in a background process to set up the database 36 | # RUN chmod +x /pb/start.sh 37 | 38 | # uncomment to copy the local pb_migrations dir into the image 39 | # COPY ./pb_migrations /pb/pb_migrations 40 | 41 | # uncomment to copy the local pb_hooks dir into the image 42 | # COPY ./pb_hooks /pb/pb_hooks 43 | 44 | WORKDIR /app 45 | COPY . . 46 | COPY start_services.sh /app/start.sh 47 | 48 | RUN npm ci 49 | 50 | # RUN /pb/start.sh 51 | 52 | EXPOSE 3000 8080 53 | 54 | # start PocketBase 55 | CMD ["/bin/sh", "/app/start.sh"] 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Rishikanth Chandrasekaran 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | Hi, 3 | I’m [Rishikanth](https://rishikanth.me), and I’m excited to introduce you to Markopolis! It’s a web app and API server I 4 | built that lets you easily share your Markdown notes as websites while giving you full control to 5 | interact with and manage your Markdown files via a powerful API. Just point Markopolis to a folder 6 | with your Markdown files, and it’ll handle the rest. The idea is to help you create your own tools 7 | and features around your notes without being tied down by proprietary systems. It’s completely 8 | open-source and free under the MIT License. Check out the [GitHub repo](https://github.com/rishikanthc/markopolis) and start exploring! 9 | 10 | **TLDR:** Self-hosted Obsidian publish with an API to extend functionality. 11 | 12 | ## Features 13 | 14 | - **Easy setup** Extremely simple to deploy and use 15 | - **Easy publish** Publish notes online with a single command 16 | - **Markdown API interface** Interact with aspecs of markdown using REST APIs 17 | - **Extensible** Extendable using exposed APIs 18 | - **Develop your own frontend** You can use the api calls to get every section of markdown files to design your own frontend 19 | - **Instand rendering** Article is available online as soon as ypu publish 20 | - **Full text search** Fuzzy search across your entire notes vault 21 | - **Obsidian markdown flavor** Maintains compatibility with obsidian markdown syntax. Supports 22 | callouts, equations, code highlighting etc. 23 | - **Dark & Light modes** Supports toggling between light and dark themes 24 | - **Easy maintenance** Requires very little to no maintenance 25 | - **Docker support** Available as docker images to self host 26 | 27 | and lots more to come. Checkout the [roadmap](https://markopolis.app/roadmap) page for planned features. 28 | 29 | ## Demo 30 | The documentation [website](https://markopolis.app) is hosted using Markopolis and is a live demo. 31 | These notes are used to demonstrate the various aspects of Markopolis. 32 | Checkout the [Markdown Syntax](https://markopolis.app/Markdown%20Syntax.md) page for a full showcase of all supported markdown syntax. 33 | 34 | Thank you for considering Markopolis for your Markdown note-sharing needs! If you like 35 | the project considering starring the repository. 36 | 37 | ## Versioning 38 | 39 | I try to follow semantic versioning as much as possible. However, I have still not 40 | streamlined the process yet, so please bear with me if there are any mishaps. v2.0.0 achieves 41 | code separation between backend and frontend because of which I had to fast forward the 42 | docker versioning to match the python package. Going forward I'll try to avoid such mishaps 43 | and I'll be maintaining a detailed changelog at [changelog](https://markopolis.app/changelog). 44 | 45 | This is my first open-source project and I'm excited to scale it well. I started building this 46 | mostly out of my personal need, but if there's public interest I'm more than happy to 47 | accept feature requests and contributions. Any and all feedback is welcome. This project 48 | will always be open-source and maintained as I rely on it for my own notes system. 49 | 50 | If you like the project please don't forget to star the [github repo](https://github.com/rishikanthc/markopolis.git). 51 | 52 | ## Installation 53 | Installing Markopolis involves two steps. First deploying the server. Second 54 | installing the CLI tool. The CLI tool provides a utility command to upload 55 | your markdown files to the server. The articles are published as soon as 56 | this command is run. 57 | 58 | ## Step 1: Server installation 59 | 60 | We will be using Docker for deploying Markopolis. 61 | Create a docker-compose and configure environment variables. 62 | Make sure to generate and add a secure `API_KEY`. 63 | Allocate persistent storage for the Markdown files. 64 | 65 | 66 | Next create a `docker-compose.yaml` file with the following: 67 | 68 | ```yaml 69 | version: '3.8' 70 | 71 | services: 72 | markopolis: 73 | image: ghcr.io/rishikanthc/markopolis:latest 74 | ports: 75 | - "8080:8080" 76 | - "3000:3000" 77 | environment: 78 | - POCKETBASE_URL=http://127.0.0.1:8080 79 | - API_KEY=test 80 | - POCKETBASE_ADMIN_EMAIL=admin@admin.com 81 | - POCKETBASE_ADMIN_PASSWORD=password 82 | - TITLE=Markopolis 83 | - CAP1=caption1 84 | - CAP2=caption2 85 | - CAP3=caption3 86 | volumes: 87 | - ./pb_data:/app/db 88 | ``` 89 | 90 | Now you can deploy Markopolis by running `docker-compse up -d` 91 | 92 | Parameter | Description 93 | -- | -- 94 | POCKETBASE_URL | **DO NOT Change this** 95 | POCKETBASE_ADMIN_EMAIL | The admin account email for the database 96 | POCKETBASE_ADMIN_PASSWORD | The admin account password 97 | TITLE | SITE TITLE 98 | API_KEY | For security, most of the API endpoints are protected by an API key. Make sure to use a secure API key and don't share it publicly. 99 | CAP1 | Caption 1, text that appears below the site title 100 | CAP2 | Caption 2 101 | CAP3 | Caption 3 102 | 103 | 104 | 105 | ## STEP 2: Local installation 106 | 107 | I highly recommend configuring a virtual environment for python to keep your environment clean and 108 | and prevent any dependency issues. Below I detail the steps to do this using Conda or pip. If you are familar 109 | with this feel free to skip to the package installation section. 110 | 111 | > [!info] 112 | > You need to have python version >= 3.12 113 | 114 | ### Setting up a virtual environment 115 | 116 | You can use either `pip` or `conda` to do this. If you are using `pip` simply run 117 | ```bash 118 | python3.12 -m venv 119 | ``` 120 | 121 | Replace `` with your desired virtual environment name. You can then activate the virtual environment 122 | using: 123 | ```bash 124 | source 125 | ``` 126 | 127 | For conda, you can use 128 | ```bash 129 | conda create -n python==3.12 130 | ``` 131 | 132 | and activate it with 133 | ```bash 134 | conda activate 135 | ``` 136 | 137 | ### Package installation 138 | 139 | Simply install the markopolis python package using your preferred package manager. 140 | 141 | **pip:** 142 | ```bash 143 | pip install markopolis 144 | ``` 145 | 146 | ### Configuration 147 | 148 | Set the environment variables `MARKOPOLIS_DOMAIN` and `MARKOPOLIS_API` 149 | 150 | **bash or zsh (temporarily for current session)** 151 | ```bash 152 | export MARKOPOLIS_DOMAIN=https://markopolis.example.com 153 | ``` 154 | 155 | **bash or zsh (permanently for all sessions)** 156 | ```bash 157 | echo 'export MARKOPOLIS_DOMAIN=https://markopolis.example.com' >> ~/.zshrc 158 | echo 'export MARKOPOLIS_DOMAIN=https://markopolis.example.com' >> ~/.bashrc 159 | 160 | source ~/.zshrc 161 | source ~/.bashrc 162 | ``` 163 | 164 | **fish (temporarily for current session)** 165 | ```fish 166 | set -x MARKOPOLIS_DOMAIN https://markopolis.example.com 167 | ``` 168 | 169 | 170 | **fish (permanently for all sessions)** 171 | ```fish 172 | echo 'set -x MARKOPOLIS_DOMAIN "https://markopolis.example.com"' >> ~/.config/fish/config.fish 173 | source ~/.config/fish/config.fish 174 | ``` 175 | 176 | For more information on how to use Markopolis checkout the [Markopolis](https://markopolis.app) website. 177 | If you like this project please considering starring it. 178 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "style": "new-york", 4 | "tailwind": { 5 | "config": "tailwind.config.ts", 6 | "css": "src/app.css", 7 | "baseColor": "neutral" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/utils" 12 | }, 13 | "typescript": true 14 | } 15 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | markopolis: 3 | image: ghcr.io/rishikanthc/markopolis:3.0.0 4 | ports: 5 | - "8080:8080" 6 | - "3000:3000" 7 | environment: 8 | - POCKETBASE_URL=http://127.0.0.1:8080 9 | - API_KEY=test 10 | - POCKETBASE_ADMIN_EMAIL=admin@gmail.com 11 | - POCKETBASE_ADMIN_PASSWORD=password 12 | - TITLE=Markopolis 13 | - CAP1=Markdown 14 | - CAP2="Self-hosting" 15 | - CAP3="Knowledge Garden" 16 | volumes: 17 | - ./pb_data:/app/pb 18 | -------------------------------------------------------------------------------- /docs/Changelog/3.0.0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Version 3.0.0 3 | date: 09-24-2024 4 | tags: 5 | - 3.0.0 6 | 7 | --- 8 | 9 | ## Backend Rewrite 10 | * **Technology Shift:** The backend has been completely rewritten, moving from Python to SwellKit. 11 | * **Database:** Transitioned from file-based management to using PocketBase for the backend database. 12 | 13 | This version is a full rewrite of the backend in sveltekit. The choice was made as managing both 14 | the frontend and backend using the same language and framework simplifies the architecture and 15 | management a lot. Additionally pocketbase provides a nice `js` interface which is also nice. 16 | 17 | ## UI Enhancements 18 | - The UI has been refined using ShadeCN, and Tailwind CSS. 19 | 20 | ## New Features 21 | * The new version introduces support for using relative paths in wiki links and images. 22 | * Tag Management 23 | * Tag Pages: Tags now get their own dedicated pages. 24 | * Menu Bar Icon: A new menu bar icon has been added to view the list of all tags. 25 | * Simplified Python CLI Tool 26 | * The Python CLI tool has been simplified to remove most dependencies. 27 | -------------------------------------------------------------------------------- /docs/Development/APIs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API overview 3 | date: 22-09-2024 4 | tags: 5 | - api 6 | - backend 7 | publish: true 8 | --- 9 | 10 | This section details the core operations of the app, specifically the backend. 11 | The backend exposes various REST API endpoints which can serve different types of requests related 12 | to markdown files. Below we detail each of them. 13 | 14 | ## Overview 15 | This document provides an overview of the API endpoints for the Marco Polo's application. The application is built using SvelteKit for the backend API and PocketBase for the database. A Python package is also used to expose a CLI for uploading files to the server. 16 | ## Endpoints 17 | ### Upload API 18 | 19 | > [!important] 20 | > **Endpoint:** ==/api/upload== 21 | > **Method:** *POST* 22 | 23 | - **Description** Uploads markdown files to the server, sets up the database, parses HTML, and stores the compiled results and original files in the database. 24 | - **Parameters** 25 | - `file`: The markdown file to be uploaded. 26 | - `url`: Absolute path of file from root of vault. 27 | 28 | #### Details 29 | 30 | Refer [[Markdown Rendering]] 31 | 32 | --- 33 | ### LS API 34 | - **Endpoint** `/api/ls` 35 | - **Method** GET 36 | - **Description** Builds a file tree from the URL field in each record of the MDBase collection. 37 | - **Responses**: 38 | - `200 OK`: Returns a JSON response with the file tree. 39 | - `500 Internal Server Error`: Error in processing the request. 40 | --- 41 | ### Search API 42 | - **Endpoint** `/api/search` 43 | - **Method** GET 44 | - **Description** Performs a fuzzy search through the content stored in the database. 45 | - **Parameters**: 46 | - `query`: The search query string. 47 | - **Responses**: 48 | - `200 OK`: Returns search results with snippets containing matches. 49 | - `404 Not Found`: No matches found. 50 | --- 51 | ### Links API 52 | - **Endpoint** `/api/links` 53 | - **Method** GET 54 | - **Description** Retrieves all links and backlinks for a given markdown file. 55 | - **Parameters** 56 | - `url`: The URL of the markdown file. 57 | - **Responses** 58 | - `200 OK`: Returns forward and backward links. 59 | - `404 Not Found`: File not found. 60 | --- 61 | ### Backlinks API 62 | - **Endpoint** `/api/backlinks` 63 | - **Method** GET 64 | - **Description** Retrieves only the backlinks for a given markdown file. 65 | - **Parameters** 66 | - `url`: The URL of the markdown file. 67 | - **Responses**: 68 | - `200 OK`: Returns backlinks. 69 | - `404 Not Found`: File not found. 70 | --- 71 | ### Image API 72 | - **Endpoint** `/api/image` 73 | - **Method** GET 74 | - **Description**: Fetches images from the database. 75 | - **Parameters** 76 | - `url`: The URL of the image file. 77 | - **Responses**: 78 | - `200 OK`: Returns the image file. 79 | - `404 Not Found`: Image not found. 80 | -------------------------------------------------------------------------------- /docs/Development/Markdown Rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rendering Markdown as HTML 3 | date: 09-22-2024 4 | tags: 5 | - markdown 6 | - mdsvex 7 | publish: true 8 | --- 9 | 10 | This document provides an in-depth explanation of the markdown parsing process used in the Marco Polo's application. The parsing is implemented using the `md-swex` library, which allows for the integration of Svelte components within markdown files. 11 | ## Parsing Process 12 | 1. **Markdown to HTML Conversion**: 13 | - The `md-swex` library is used to parse markdown files and convert them into HTML blocks. 14 | - The `compile` function of `md-swex` is utilized to directly compile markdown content. 15 | 2. **Database Storage**: 16 | - Parsed content is stored in the PocketBase database. 17 | - Fields include ID, title, parsed HTML content, URL, markdown file, front matter (as JSON), and relational fields for backlinks and forward links. 18 | 3. **Plugins and Extensions**: 19 | - **Remark Plugins**: Used for processing markdown abstract syntax trees. 20 | - `Remark Math`: Renders LaTeX equations. 21 | - `Remark Footnotes`: Processes footnotes. 22 | - `Custom Wikilink Plugin`: Resolves relative links. 23 | - `Custom Obsidian Image Plugin`: Handles inline images. 24 | - `Custom Remark Mermaid Plugin`: Processes Mermaid diagrams. 25 | 4. **Custom Wikilink Plugin**: 26 | - Recognizes Wikilink syntax and evaluates relative paths. 27 | - Uses front matter to access the file path and resolve links. 28 | 5. **Handling Code Blocks**: 29 | - Addresses issues with `md-swex` parsing code blocks as inline HTML. 30 | - Cleanup functions remove unwanted syntax to ensure proper rendering. 31 | 6. **Operational Checks**: 32 | - Ensures the existence of necessary database collections (e.g., `mdbase`, `attachments`). 33 | - Handles file uploads and updates, storing markdown and image files appropriately. 34 | ## Conclusion 35 | The markdown parsing component is integral to the Marco Polo's application, enabling efficient storage and retrieval of markdown content. The use of `md-swex` and various plugins ensures robust parsing and rendering capabilities. 36 | -------------------------------------------------------------------------------- /docs/Development/components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Components behind Markopolis 3 | tags: 4 | - development 5 | - working 6 | date: 09/20/2024 7 | publish: true 8 | --- 9 | 10 | Markopolis is built using 2 frameworks: [sveltekit](https://svelte.dev) and [pocketbase](https://pocketbase.docs). 11 | Pocketbase is used for database and the backend API is implemented using sveltekit. It additionally has a 12 | client side convenience python CLI interface. 13 | The sveltekit webapp exposes APIs for interaction, pre-renders and routes the websites. The functionality of the 14 | app begins with the python CLI interface. Running the sync command uploads files using an API. The API on 15 | recieving the file, converts it to html and stores it in the database along with the original file. 16 | The sveltekit app then uses dynamic routing to search and fetch the file from the database and renders it as a 17 | webpage. 18 | 19 | On the high-level Markopolis consists of a backend and a database. The front-end interacts with the backend 20 | and requests things needed to render pages. The backend, according to the requests, pulls data from the database 21 | and returns them. 22 | The front-end interacts with the backend via REST APIs exposed by the backend and for webpages also uses 23 | Server Side Rendering(SSR). Below is a diagram illustrating the data and control flow: 24 | 25 | ```mermaid 26 | sequenceDiagram 27 | Frontend->>Backend: API calls 28 | Backend-->>Database: fetch data using PocketBase API 29 | Database-->>Backend: Requested data 30 | Backend-)Frontend: Requested data 31 | ``` 32 | 33 | 34 | ## Backend 35 | 36 | The backends primary function is 37 | -------------------------------------------------------------------------------- /docs/Markdown Syntax.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: true 3 | tags: 4 | - syntax 5 | - markdown 6 | title: Markdown Syntax 7 | --- 8 | # Headings 9 | 10 | ```markdown 11 | # Heading 1 12 | ## Heading 2 13 | ### Heading 3 14 | #### Heading 4 15 | ##### Heading 5 16 | ``` 17 | 18 | 19 | # Heading 1 20 | ## Heading 2 21 | ### Heading 3 22 | #### Heading 4 23 | ##### Heading 5 24 | 25 | ## Horizontal line 26 | 27 | --- 28 | 29 | ## Tags 30 | 31 | ```markdown 32 | #tag1 #tag2 #tag3 33 | ``` 34 | 35 | #tag1 #tag2 #tag3 36 | 37 | ## Images 38 | 39 | ### embed images 40 | Image names should be unique. Duplicate images will be overwritten. 41 | 42 | ```markdown 43 | ![[image.png]] 44 | 45 | ![](image.png) 46 | ``` 47 | 48 | ![[image.png]] 49 | 50 | ![](image.png) 51 | 52 | ### external images 53 | 54 | ```markdown 55 | ![Engelbart](https://history-computer.com/ModernComputer/Basis/images/Engelbart.jpg) 56 | ``` 57 | 58 | ![Engelbart](https://history-computer.com/ModernComputer/Basis/images/Engelbart.jpg) 59 | 60 | ## Wikilinks 61 | 62 | ```markdown 63 | [[Installation]] 64 | [[f1/test]] 65 | [[f2/test]] 66 | ``` 67 | 68 | [[installation]] 69 | [[f1/test]] 70 | [[f2/test]] 71 | 72 | ## Text formatting 73 | ```markdown 74 | **Bold text** 75 | *Italic text* 76 | ~~this puts a strikethrough~~ 77 | ==this highlights text== 78 | **Bold text and _nested italic_ text** 79 | ***Bold and italic text*** 80 | ``` 81 | 82 | **Bold text** 83 | *Italic text* 84 | ~~this puts a strikethrough~~ 85 | ==this highlights text== 86 | **Bold text and _nested italic_ text** 87 | ***Bold and italic text*** 88 | 89 | ## Equations 90 | 91 | ```markdown 92 | $$ 93 | \sum_i = x 94 | $$ 95 | ``` 96 | 97 | $$ 98 | \sum_i = x 99 | $$ 100 | 101 | ## Footnotes 102 | 103 | ```markdown 104 | This is a simple footnote[^1]. 105 | 106 | 107 | [^1]: This is the referenced text. 108 | [^2]: Add 2 spaces at the start of each new line. 109 | This lets you write footnotes that span multiple lines. 110 | [^note]: Named footnotes still appear as numbers, but can make it easier to identify and link references. 111 | ``` 112 | 113 | This is a simple footnote[^1]. 114 | 115 | 116 | ## Quotes 117 | 118 | ```markdown 119 | > Human beings face ever more complex and urgent problems, and their effectiveness in dealing with these problems is a matter that is critical to the stability and continued progress of society. 120 | 121 | \- Doug Engelbart, 1961 122 | ``` 123 | 124 | > Human beings face ever more complex and urgent problems, and their effectiveness in dealing with these problems is a matter that is critical to the stability and continued progress of society. 125 | 126 | \- Doug Engelbart, 1961 127 | 128 | ## Tables 129 | 130 | ```markdown 131 | | First name | Last name | 132 | | ---------- | --------- | 133 | | Max | Planck | 134 | | Marie | Curie | 135 | ``` 136 | 137 | | First name | Last name | 138 | | ---------- | --------- | 139 | | Max | Planck | 140 | | Marie | Curie | 141 | 142 | The vertical bars on either side of the table are optional. 143 | 144 | Cells don't need to be perfectly aligned with the columns. Each header row must have at least two hyphens. 145 | 146 | ```markdown 147 | First name | Last name 148 | -- | -- 149 | Max | Planck 150 | Marie | Curie 151 | ``` 152 | 153 | First name | Last name 154 | -- | -- 155 | Max | Planck 156 | Marie | Curie 157 | 158 | ## Mermaid diagrams 159 | 160 | Some text. 161 | 162 | ```mermaid 163 | graph TB 164 | A --> B 165 | B --> C 166 | ``` 167 | 168 | ```mermaid 169 | flowchart LR 170 | 171 | 172 | 173 | A[Osaka 7-8] --> B[Tokyo 9-11] 174 | B -.Nagano .-> C[Matsumoto] 175 | C -.Nagano & Toyama.-> D[Takayama 12] <--> D1(Hida no Sato village) 176 | B -.Nagano & Toyama.-> D 177 | C <-.bus.-> D 178 | D --Toyama---> E <--> D2([Onsen 14]) --> F 179 | E[Kanazawa 13] ---> F[Kyoto 15-18] <--> F2(Uji) <--> F1(Nara) 180 | F <-.-> F4(Himeji) 181 | ``` 182 | 183 | ### Large chart 184 | 185 | ```mermaid 186 | timeline 187 | section .NET Framework 188 | 2000 - 2005 189 | : .NET Framework 1.0 190 | : .NET Framework 1.0 SP1 191 | : .NET Framework 1.0 SP2 192 | : .NET Framework 1.1 193 | : .NET Framework 1.0 SP3 194 | : .NET Framework 2.0 195 | 2006 - 2009 196 | : .NET Framework 3.0 197 | : .NET Framework 3.5 198 | : .NET Framework 2.0 SP 1 199 | : .NET Framework 3.0 SP 1 200 | : .NET Framework 2.0 SP 2 201 | : .NET Framework 3.0 SP 2 202 | : .NET Framework 3.5 SP 1 203 | 2010 - 2015 204 | : .NET Framework 4.0 205 | : .NET Framework 4.5 206 | : .NET Framework 4.5.1 207 | : .NET Framework 4.5.2 208 | : .NET Framework 4.6 209 | : .NET Framework 4.6.1 210 | section .NET Core 211 | 2016 - 2017 212 | : .NET Core 1.0 213 | : .NET Core 1.1 214 | : .NET Framework 4.6.2 215 | : .NET Core 2.0 216 | : .NET Framework 4.7 217 | : .NET Framework 4.7.1 218 | 2018 - 2019 219 | : .NET Core 2.1 220 | : .NET Core 2.2 221 | : .NET Framework 4.7.2 222 | : .NET Core 3.0 223 | : .NET Core 3.1 224 | : .NET Framework 4.8 225 | section Modern .NET 226 | 2020 : .NET 5 227 | 2021 : .NET 6 228 | 2022 : .NET 7 229 | : .NET Framework 4.8.1 230 | 231 | ``` 232 | 233 | ## Callouts 234 | 235 | > [!abstract] 236 | > Lorem ipsum dolor sit amet 237 | 238 | > [!info] 239 | > Lorem ipsum dolor sit amet 240 | 241 | > [!todo] 242 | > Lorem ipsum dolor sit amet 243 | 244 | > [!tip] 245 | > Lorem ipsum dolor sit amet 246 | 247 | > [!success] 248 | > Lorem ipsum dolor sit amet 249 | 250 | > [!question] 251 | > Lorem ipsum dolor sit amet 252 | 253 | > [!warning] 254 | > Lorem ipsum dolor sit amet 255 | 256 | > [!failure] 257 | > Lorem ipsum dolor sit amet 258 | 259 | > [!danger] 260 | > Lorem ipsum dolor sit amet 261 | 262 | > [!bug] 263 | > Lorem ipsum dolor sit amet 264 | 265 | > [!example] 266 | > Lorem ipsum dolor sit amet 267 | 268 | > [!quote] 269 | > Lorem ipsum dolor sit amet 270 | 271 | > [!tip] Title-only callout 272 | 273 | [^1]: This is the referenced text. 274 | [^2]: Add 2 spaces at the start of each new line. 275 | This lets you write footnotes that span multiple lines. 276 | [^note]: Named footnotes still appear as numbers, but can make it easier to identify and link references. 277 | -------------------------------------------------------------------------------- /docs/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/docs/image.png -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | date: 09-24-2024 4 | tags: 5 | - install 6 | - docker 7 | --- 8 | 9 | Installing Markopolis involves two steps. First deploying the server. Second 10 | installing the CLI tool. The CLI tool provides a utility command to upload 11 | your markdown files to the server. The articles are published as soon as 12 | this command is run. 13 | 14 | ## Server installation 15 | 16 | We will be using Docker for deploying Markopolis. 17 | Create a docker-compose and configuring environment variables. 18 | Make sure to generate and add a secure `API_KEY`. 19 | Allocate persistent storage for the Markdown files. 20 | 21 | First create a `.env` file to configure the following environment variables. 22 | 23 | ```bash 24 | POCKETBASE_URL=http://127.0.0.1:8090 25 | API_KEY= Self-hostable Obsidian Publish 12 | 13 | ## Why 14 | Markdown files are my preferred choice for storing information. It's simple and is future proof. 15 | Having used Obsidian and liking it a lot, I moved back to using my text editor as Obsidian was 16 | too distracting. The customizability is endless and I found myself frequently caught down rabbit 17 | holes, trying to optimize for the perfect setup. I had to end the insanity. 18 | 19 | I have been using Markdown-Oxide along with my editor and that keeps it super simple. However, I 20 | miss some of the features offered by Obsidian via plugins like 1-click Publish, auto-tagging, 21 | notes discovery, etc. I decided to build something that would help me to easily publish my notes 22 | online, and can be self-hosted on my own hardware. This got me thinking about using REST APIs as 23 | an interface to work with markdown files. That way, I can implement my own features around my notes. 24 | Hence, Markopolis. 25 | 26 | ## Features 27 | 28 | - **Easy setup** Extremely simple to deploy and use 29 | - **Easy publish** Publish notes online with a single command 30 | - **Markdown API interface** Interact with aspecs of markdown using REST APIs 31 | - **Extensible** Extendable using exposed APIs 32 | - **Instand rendering** Article is available online as soon as ypu publish 33 | - **Full text search** Fuzzy search across your entire notes vault 34 | - **Obsidian markdown flavor** Maintains compatibility with obsidian markdown syntax. Supports 35 | callouts, equations, code highlighting etc. 36 | - **Dark & Light modes** Supports toggling between light and dark themes 37 | - **Easy maintenance** Requires very little to no maintenance 38 | - **Docker support** Available as docker images to self host 39 | 40 | ## Demo 41 | 42 | This website is hosted using Markopolis and is a live demo. These notes are used to demonstrate 43 | the various aspects of Markopolis. Checkout the [[Markdown Syntax]] page for a full showcase 44 | of all supported markdown syntax. 45 | 46 | ## About me 47 | Hi, 48 | I'm [Rishi](https://rishikanth.me), a recent PhD graduate and soon to start as an Applied Researcher. 49 | I'm an avid self-hoster and a strong proponent of open-source software. I'm based 50 | out of Washington and enjoy solving practical problems with code. 51 | -------------------------------------------------------------------------------- /docs/roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Development Roadmap 3 | date: 09-23-2024 4 | publish: true 5 | --- 6 | 7 | This page lists a bunch of features and improvements that I plan to 8 | take on. These are based on my own needs, but I'm more than happy to 9 | take feature requests. If you face an issue or want a particular feature 10 | feel free to open a Github issue and I'll address it. 11 | 12 | ## UX & UI improvements 13 | 14 | - **Better blockquote styling** Fix the odd alignment of quotes and text 15 | - **Nested checkbox lists** Handle formatting of nested to-do / checkbox lists 16 | - **Upload only changed files** Currently overwriting all files irrespective 17 | 18 | ## Features 19 | - **Password protection** Hide specific pages behind a password 20 | - **Selective AI chat** Chat with selected notes using OpenAI / Claude 21 | - **Graph view** Visualize the backlinks network as a 3D graph 22 | - **Graph Navigation** navigate notes via graphs 23 | - **Graph interactions** Interact with your notes using 3D graphs 24 | - advanced filtering 25 | - AI chat with sub-graph as context 26 | - **Estimated reading time** 27 | - **ToDo management** Show, manage and edit ToDos 28 | - **Daily notes** Render daily notes under a personal login 29 | - **Auto tagging** Auto tagging using graph community detection 30 | 31 | ## Bug fixes 32 | - **Cleanup dangling tags** Delete unused tags left behind by file deletion 33 | - **Highlight.js** Something weird is going and syntax highlighting doesn't work as intended 34 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | date: 09-24-2024 4 | --- 5 | 6 | Initially the app starts with an empty database as there are no notes. So we 7 | will begin by uploading some notes. 8 | 9 | ### Uploading files to server 10 | 11 | Open a terminal and `cd` to the root directory of your notes vault. This is the directory 12 | of your notes. Then run `mdsync` in the vault directory. The command will scan for 13 | all markdown and image files. 14 | 15 | > [!note] 16 | > You can delete files on the server by simply deleting the file locally in your 17 | > vault and then running `mdsync` command. 18 | 19 | 20 | ### WikiLinks and Images 21 | 22 | Markopolis supports Obsidian style WikiLinks and Images. Previously 23 | Markopolis only supported absolute path from note vault. However, from 3.0.0 24 | Markopolis now supports relative path as well. 25 | 26 | 27 | > [!Tip] 28 | > Checkout [[Markdown Syntax]] to see how different markdown syntax renders. 29 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import ts from 'typescript-eslint'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import prettier from 'eslint-config-prettier'; 5 | import globals from 'globals'; 6 | 7 | /** @type {import('eslint').Linter.Config[]} */ 8 | export default [ 9 | js.configs.recommended, 10 | ...ts.configs.recommended, 11 | ...svelte.configs['flat/recommended'], 12 | prettier, 13 | ...svelte.configs['flat/prettier'], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node 19 | } 20 | } 21 | }, 22 | { 23 | files: ['**/*.svelte'], 24 | languageOptions: { 25 | parserOptions: { 26 | parser: ts.parser 27 | } 28 | } 29 | }, 30 | { 31 | ignores: ['build/', '.svelte-kit/', 'dist/'] 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "godamn", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --check . && eslint .", 12 | "format": "prettier --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-auto": "^3.0.0", 16 | "@sveltejs/adapter-node": "^5.2.4", 17 | "@sveltejs/kit": "^2.0.0", 18 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 19 | "@tailwindcss/typography": "^0.5.14", 20 | "@types/eslint": "^9.6.0", 21 | "autoprefixer": "^10.4.20", 22 | "eslint": "^9.0.0", 23 | "eslint-config-prettier": "^9.1.0", 24 | "eslint-plugin-svelte": "^2.36.0", 25 | "globals": "^15.0.0", 26 | "prettier": "^3.1.1", 27 | "prettier-plugin-svelte": "^3.1.2", 28 | "prettier-plugin-tailwindcss": "^0.6.5", 29 | "remark-footnotes": "2.0", 30 | "remark-math": "^3.0.0", 31 | "svelte": "^4.2.7", 32 | "svelte-check": "^4.0.0", 33 | "tailwindcss": "^3.4.9", 34 | "typescript": "^5.0.0", 35 | "typescript-eslint": "^8.0.0", 36 | "vite": "^5.0.3" 37 | }, 38 | "type": "module", 39 | "dependencies": { 40 | "@leeoniya/ufuzzy": "^1.0.14", 41 | "@pondorasti/remark-img-links": "^1.0.8", 42 | "@threlte/core": "^7.3.1", 43 | "@threlte/extras": "^8.11.5", 44 | "3d-force-graph": "^1.73.3", 45 | "bits-ui": "^0.21.15", 46 | "clsx": "^2.1.1", 47 | "d3-force": "^3.0.0", 48 | "d3-force-3d": "^3.0.5", 49 | "formsnap": "^1.0.1", 50 | "highlight.js": "^11.10.0", 51 | "js-yaml": "^4.1.0", 52 | "katex": "^0.16.11", 53 | "lodash-es": "^4.17.21", 54 | "lucide-svelte": "^0.441.0", 55 | "marked": "^14.1.3", 56 | "marked-admonition-extension": "^0.0.4", 57 | "marked-alert": "^2.1.0", 58 | "mdsvex": "^0.12.3", 59 | "mdsvex-relative-images": "^1.0.3", 60 | "mermaid": "^11.2.1", 61 | "mode-watcher": "^0.4.1", 62 | "pocketbase": "^0.21.5", 63 | "rehype-autolink-headings": "^7.1.0", 64 | "rehype-callouts": "^1.0.3", 65 | "rehype-highlight": "^7.0.0", 66 | "rehype-katex": "^7.0.1", 67 | "rehype-katex-svelte": "^1.2.0", 68 | "rehype-mermaid": "^2.1.0", 69 | "remark-gfm": "^4.0.0", 70 | "remark-wiki-link": "^0.0.4", 71 | "svelte-markdown": "^0.4.1", 72 | "svelte-radix": "^1.1.1", 73 | "sveltekit-superforms": "^2.19.0", 74 | "tailwind-merge": "^2.5.2", 75 | "tailwind-variants": "^0.2.1", 76 | "threlte": "^3.13.1", 77 | "unist-util-visit": "^5.0.0", 78 | "zod": "^3.23.8" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/src/.DS_Store -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 0 0% 3.9%; 9 | 10 | --muted: 0 0% 96.1%; 11 | --muted-foreground: 0 0% 45.1%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 0 0% 3.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 0 0% 3.9%; 18 | 19 | --border: 0 0% 89.8%; 20 | --input: 0 0% 89.8%; 21 | 22 | --primary: 0 0% 9%; 23 | --primary-foreground: 0 0% 98%; 24 | 25 | --secondary: 0 0% 96.1%; 26 | --secondary-foreground: 0 0% 9%; 27 | 28 | --accent: 0 0% 96.1%; 29 | --accent-foreground: 0 0% 9%; 30 | 31 | --destructive: 0 72.2% 50.6%; 32 | --destructive-foreground: 0 0% 98%; 33 | 34 | --ring: 0 0% 3.9%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | /* --background: 0 0% 3.9%; */ 41 | /* --foreground: 0 0% 98%; */ 42 | 43 | --background: 0 0% 9%; 44 | --foreground: 30 0% 96%; 45 | /* --foreground: 30 0% 78%; */ 46 | 47 | /* --muted: 0 0% 14.9%; 48 | --muted-foreground: 0 0% 63.9%; */ 49 | --muted: 0 0% 15%; 50 | --muted-foreground: 30 0% 78%; 51 | 52 | --popover: 0 0% 3.9%; 53 | --popover-foreground: 0 0% 98%; 54 | 55 | --card: 0 0% 3.9%; 56 | --card-foreground: 0 0% 98%; 57 | 58 | --border: 0 0% 14.9%; 59 | --input: 0 0% 14.9%; 60 | 61 | --primary: 0 0% 98%; 62 | --primary-foreground: 0 0% 9%; 63 | 64 | --secondary: 0 0% 14.9%; 65 | --secondary-foreground: 0 0% 98%; 66 | 67 | --accent: 0 0% 14.9%; 68 | --accent-foreground: 0 0% 98%; 69 | 70 | --destructive: 0 62.8% 30.6%; 71 | --destructive-foreground: 0 0% 98%; 72 | 73 | --ring: 0 0% 83.1%; 74 | } 75 | 76 | h1 { 77 | @apply text-6xl; 78 | @apply mb-4 mt-10; 79 | } 80 | h2 { 81 | @apply text-4xl; 82 | @apply mb-3 mt-8; 83 | } 84 | h3 { 85 | @apply text-2xl; 86 | @apply mb-2 mt-6; 87 | } 88 | h4 { 89 | @apply text-xl; 90 | @apply mb-1 mt-4; 91 | } 92 | p { 93 | @apply my-2; 94 | } 95 | a { 96 | @apply text-[#0f62fe] dark:text-[#78a9ff]; 97 | } 98 | a:hover { 99 | @apply text-[#0043ce] dark:text-[#a6c8ff]; 100 | } 101 | 102 | * { 103 | @apply border-border; 104 | } 105 | body { 106 | @apply bg-background text-foreground; 107 | } 108 | 109 | @font-face { 110 | font-family: 'IBM Plex Sans'; 111 | src: url('/fonts/IBMPlexSans-Bold.ttf') format('truetype'); 112 | font-weight: bold; 113 | font-style: normal; 114 | } 115 | 116 | @font-face { 117 | font-family: 'IBM Plex Sans'; 118 | src: url('/fonts/IBMPlexSans-BoldItalic.ttf') format('truetype'); 119 | font-weight: bold; 120 | font-style: italic; 121 | } 122 | 123 | @font-face { 124 | font-family: 'IBM Plex Sans'; 125 | src: url('/fonts/IBMPlexSans-ExtraLight.ttf') format('truetype'); 126 | font-weight: 200; 127 | font-style: normal; 128 | } 129 | 130 | @font-face { 131 | font-family: 'IBM Plex Sans'; 132 | src: url('/fonts/IBMPlexSans-ExtraLightItalic.ttf') format('truetype'); 133 | font-weight: 200; 134 | font-style: italic; 135 | } 136 | 137 | @font-face { 138 | font-family: 'IBM Plex Sans'; 139 | src: url('/fonts/IBMPlexSans-Italic.ttf') format('truetype'); 140 | font-weight: normal; 141 | font-style: italic; 142 | } 143 | 144 | @font-face { 145 | font-family: 'IBM Plex Sans'; 146 | src: url('/fonts/IBMPlexSans-Light.ttf') format('truetype'); 147 | font-weight: 300; 148 | font-style: normal; 149 | } 150 | 151 | @font-face { 152 | font-family: 'IBM Plex Sans'; 153 | src: url('/fonts/IBMPlexSans-LightItalic.ttf') format('truetype'); 154 | font-weight: 300; 155 | font-style: italic; 156 | } 157 | 158 | @font-face { 159 | font-family: 'IBM Plex Sans'; 160 | src: url('/fonts/IBMPlexSans-Medium.ttf') format('truetype'); 161 | font-weight: 500; 162 | font-style: normal; 163 | } 164 | 165 | @font-face { 166 | font-family: 'IBM Plex Sans'; 167 | src: url('/fonts/IBMPlexSans-MediumItalic.ttf') format('truetype'); 168 | font-weight: 500; 169 | font-style: italic; 170 | } 171 | 172 | @font-face { 173 | font-family: 'IBM Plex Sans'; 174 | src: url('/fonts/IBMPlexSans-Regular.ttf') format('truetype'); 175 | font-weight: normal; 176 | font-style: normal; 177 | } 178 | 179 | @font-face { 180 | font-family: 'IBM Plex Sans'; 181 | src: url('/fonts/IBMPlexSans-SemiBold.ttf') format('truetype'); 182 | font-weight: 600; 183 | font-style: normal; 184 | } 185 | 186 | @font-face { 187 | font-family: 'IBM Plex Sans'; 188 | src: url('/fonts/IBMPlexSans-SemiBoldItalic.ttf') format('truetype'); 189 | font-weight: 600; 190 | font-style: italic; 191 | } 192 | 193 | @font-face { 194 | font-family: 'IBM Plex Sans'; 195 | src: url('/fonts/IBMPlexSans-Text.ttf') format('truetype'); 196 | font-weight: 400; 197 | font-style: normal; 198 | } 199 | 200 | @font-face { 201 | font-family: 'IBM Plex Sans'; 202 | src: url('/fonts/IBMPlexSans-TextItalic.ttf') format('truetype'); 203 | font-weight: 400; 204 | font-style: italic; 205 | } 206 | 207 | @font-face { 208 | font-family: 'IBM Plex Sans'; 209 | src: url('/fonts/IBMPlexSans-Thin.ttf') format('truetype'); 210 | font-weight: 100; 211 | font-style: normal; 212 | } 213 | 214 | @font-face { 215 | font-family: 'IBM Plex Sans'; 216 | src: url('/fonts/IBMPlexSans-ThinItalic.ttf') format('truetype'); 217 | font-weight: 100; 218 | font-style: italic; 219 | } 220 | 221 | @font-face { 222 | font-family: 'Megrim'; 223 | src: url('/fonts/Megrim-Regular.ttf') format('truetype'); 224 | font-weight: normal; 225 | font-style: normal; 226 | } 227 | 228 | /* ---- IBM Plex Mono ---- */ 229 | @font-face { 230 | font-family: 'IBM Plex Mono'; 231 | font-weight: 700; 232 | font-style: normal; 233 | src: url('/fonts/IBMPlexMono-Bold.ttf') format('truetype'); 234 | } 235 | 236 | @font-face { 237 | font-family: 'IBM Plex Mono'; 238 | font-weight: 700; 239 | font-style: italic; 240 | src: url('/fonts/IBMPlexMono-BoldItalic.ttf') format('truetype'); 241 | } 242 | 243 | @font-face { 244 | font-family: 'IBM Plex Mono'; 245 | font-weight: 200; 246 | font-style: normal; 247 | src: url('/fonts/IBMPlexMono-ExtraLight.ttf') format('truetype'); 248 | } 249 | 250 | @font-face { 251 | font-family: 'IBM Plex Mono'; 252 | font-weight: 200; 253 | font-style: italic; 254 | src: url('/fonts/IBMPlexMono-ExtraLightItalic.ttf') format('truetype'); 255 | } 256 | 257 | @font-face { 258 | font-family: 'IBM Plex Mono'; 259 | font-weight: 400; 260 | font-style: italic; 261 | src: url('/fonts/IBMPlexMono-Italic.ttf') format('truetype'); 262 | } 263 | 264 | @font-face { 265 | font-family: 'IBM Plex Mono'; 266 | font-weight: 300; 267 | font-style: normal; 268 | src: url('/fonts/IBMPlexMono-Light.ttf') format('truetype'); 269 | } 270 | 271 | @font-face { 272 | font-family: 'IBM Plex Mono'; 273 | font-weight: 300; 274 | font-style: italic; 275 | src: url('/fonts/IBMPlexMono-LightItalic.ttf') format('truetype'); 276 | } 277 | 278 | @font-face { 279 | font-family: 'IBM Plex Mono'; 280 | font-weight: 500; 281 | font-style: normal; 282 | src: url('/fonts/IBMPlexMono-Medium.ttf') format('truetype'); 283 | } 284 | 285 | @font-face { 286 | font-family: 'IBM Plex Mono'; 287 | font-weight: 500; 288 | font-style: italic; 289 | src: url('/fonts/IBMPlexMono-MediumItalic.ttf') format('truetype'); 290 | } 291 | 292 | @font-face { 293 | font-family: 'IBM Plex Mono'; 294 | font-weight: 400; 295 | font-style: normal; 296 | src: url('/fonts/IBMPlexMono-Regular.ttf') format('truetype'); 297 | } 298 | 299 | @font-face { 300 | font-family: 'IBM Plex Mono'; 301 | font-weight: 600; 302 | font-style: normal; 303 | src: url('/fonts/IBMPlexMono-SemiBold.ttf') format('truetype'); 304 | } 305 | 306 | @font-face { 307 | font-family: 'IBM Plex Mono'; 308 | font-weight: 600; 309 | font-style: italic; 310 | src: url('/fonts/IBMPlexMono-SemiBoldItalic.ttf') format('truetype'); 311 | } 312 | 313 | @font-face { 314 | font-family: 'IBM Plex Mono'; 315 | font-weight: 400; 316 | font-style: normal; 317 | src: url('/fonts/IBMPlexMono-Text.ttf') format('truetype'); 318 | } 319 | 320 | @font-face { 321 | font-family: 'IBM Plex Mono'; 322 | font-weight: 400; 323 | font-style: italic; 324 | src: url('/fonts/IBMPlexMono-TextItalic.ttf') format('truetype'); 325 | } 326 | 327 | @font-face { 328 | font-family: 'IBM Plex Mono'; 329 | font-weight: 100; 330 | font-style: normal; 331 | src: url('/fonts/IBMPlexMono-Thin.ttf') format('truetype'); 332 | } 333 | 334 | @font-face { 335 | font-family: 'IBM Plex Mono'; 336 | font-weight: 100; 337 | font-style: italic; 338 | src: url('/fonts/IBMPlexMono-ThinItalic.ttf') format('truetype'); 339 | } 340 | 341 | /* Lombok */ 342 | @font-face { 343 | font-family: 'Lombok'; 344 | font-weight: 400; 345 | font-style: normal; 346 | src: url('/fonts/Lombok.otf') format('opentype'); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | interface Locals { 6 | pb: import('pocketbase').default; 7 | } 8 | // interface Error {} 9 | // interface Locals {} 10 | // interface PageData {} 11 | // interface PageState {} 12 | // interface Platform {} 13 | } 14 | } 15 | 16 | export {}; 17 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | %sveltekit.head% 22 | 23 | 24 |
%sveltekit.body%
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/src/lib/.DS_Store -------------------------------------------------------------------------------- /src/lib/components/FileTree.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 | {#if node} 25 |
26 |
e.key === "Enter" && toggleExpand()} 30 | role="button" 31 | tabindex="0" 32 | > 33 | {#if isFolder} 34 | {#if isExpanded} 35 | 36 | {:else} 37 | 38 | {/if} 39 | {:else} 40 | 41 | {/if} 42 | 43 | {#if isFolder} 44 | {node.name} 45 | {:else if node.url} 46 | {node.title} 49 | {:else} 50 | {node.title} 51 | 52 | {/if} 53 |
54 | 55 | {#if isFolder && isExpanded} 56 |
57 | {#each node.children as childNode} 58 | 59 | {/each} 60 |
61 | {/if} 62 |
63 | {:else} 64 |
65 | {#each fileTree as rootNode} 66 | 67 | {/each} 68 |
69 | {/if} 70 | -------------------------------------------------------------------------------- /src/lib/components/GraphScene.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | { 37 | ref.lookAt(0, 0, 0); 38 | }} 39 | > 40 | 41 | 42 | 43 | 44 | 45 | 46 | scale.set(1.2)} on:pointerleave={() => scale.set(1)}> 47 | {#each nodes as node} 48 | 49 | 50 | 51 | 52 | {/each} 53 | 54 | {#each edges as edge} 55 | 56 | {/each} 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/lib/components/LoginForm.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | Login 22 | 23 | 24 |
25 | 26 | 27 | Username 28 | 29 | 30 | This is your public display name. 31 | 32 | 33 | 34 | 35 | Password 36 | 37 | 38 | This is your public display name. 39 | 40 | 41 | Submit 42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /src/lib/components/MDGraph.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 | {#if browser} 41 |
42 | {/if} 43 |
44 | 45 | 47 | -------------------------------------------------------------------------------- /src/lib/components/MDsvexRenderer.svelte: -------------------------------------------------------------------------------- 1 | 2 | 64 | 65 |
66 | 67 |
68 | 69 | 293 | -------------------------------------------------------------------------------- /src/lib/components/MarkdownGraph.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
39 |
40 | 41 | 42 | 43 |
44 |
45 | 46 | 58 | -------------------------------------------------------------------------------- /src/lib/components/Scene.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | { 15 | ref.lookAt(0, 0, 0); 16 | }} 17 | > 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | scale.set(1.5)} on:pointerleave={() => scale.set(1)}> 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/lib/components/SearchComponent.svelte: -------------------------------------------------------------------------------- 1 | 118 | 119 | 120 | 121 | 122 | 123 | 126 | 127 | 128 | 129 | 130 | Search 131 | 132 | 133 |
134 | 135 |
136 | {#if isLoading} 137 | 138 | {:else if searchQuery} 139 | 146 | {:else} 147 | 148 | {/if} 149 |
150 |
151 | 152 | {#if searchResults.length > 0} 153 |
154 | {#each searchResults as result, index} 155 |
161 | 172 |
173 | {/each} 174 |
175 | {:else if searchQuery && !isLoading} 176 |

No results found

177 | {/if} 178 |
179 |
180 | -------------------------------------------------------------------------------- /src/lib/components/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |
15 | {title} 16 |
17 |
18 |
{captions[0]} / {captions[1]}
19 |
{captions[2]}
20 |
21 | 22 | 36 | 37 | 38 |
39 | 40 | 45 | -------------------------------------------------------------------------------- /src/lib/components/TagBar.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | 23 | 26 |
27 |
TAGS
28 | {#each tags as tag (tag.name)} 29 |
30 | #{tag.name} 34 |
35 | {/each} 36 |
37 |
38 | -------------------------------------------------------------------------------- /src/lib/components/TopBar.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 16 | {#if title} 17 |
{title}
18 | {/if} 19 |
20 | 21 | 26 | -------------------------------------------------------------------------------- /src/lib/components/award.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /src/lib/components/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const formSchema = z.object({ 4 | username: z.string(), 5 | password: z.string() 6 | }); 7 | 8 | export type FormSchema = typeof formSchema; 9 | -------------------------------------------------------------------------------- /src/lib/components/ui/button/button.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import type { Button as ButtonPrimitive } from "bits-ui"; 2 | import { type VariantProps, tv } from "tailwind-variants"; 3 | import Root from "./button.svelte"; 4 | 5 | const buttonVariants = tv({ 6 | base: "focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50", 7 | variants: { 8 | variant: { 9 | default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow", 10 | destructive: 11 | "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm", 12 | outline: 13 | "border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm", 14 | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm", 15 | ghost: "hover:bg-accent hover:text-accent-foreground", 16 | link: "text-primary underline-offset-4 hover:underline", 17 | }, 18 | size: { 19 | default: "h-9 px-4 py-2", 20 | sm: "h-8 rounded-md px-3 text-xs", 21 | lg: "h-10 rounded-md px-8", 22 | icon: "h-9 w-9", 23 | }, 24 | }, 25 | defaultVariants: { 26 | variant: "default", 27 | size: "default", 28 | }, 29 | }); 30 | 31 | type Variant = VariantProps["variant"]; 32 | type Size = VariantProps["size"]; 33 | 34 | type Props = ButtonPrimitive.Props & { 35 | variant?: Variant; 36 | size?: Size; 37 | }; 38 | 39 | type Events = ButtonPrimitive.Events; 40 | 41 | export { 42 | Root, 43 | type Props, 44 | type Events, 45 | // 46 | Root as Button, 47 | type Props as ButtonProps, 48 | type Events as ButtonEvents, 49 | buttonVariants, 50 | }; 51 | -------------------------------------------------------------------------------- /src/lib/components/ui/card/card-content.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/ui/card/card-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |

12 | 13 |

14 | -------------------------------------------------------------------------------- /src/lib/components/ui/card/card-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/ui/card/card-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/ui/card/card-title.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/card/card.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /src/lib/components/ui/card/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./card.svelte"; 2 | import Content from "./card-content.svelte"; 3 | import Description from "./card-description.svelte"; 4 | import Footer from "./card-footer.svelte"; 5 | import Header from "./card-header.svelte"; 6 | import Title from "./card-title.svelte"; 7 | 8 | export { 9 | Root, 10 | Content, 11 | Description, 12 | Footer, 13 | Header, 14 | Title, 15 | // 16 | Root as Card, 17 | Content as CardContent, 18 | Description as CardDescription, 19 | Footer as CardFooter, 20 | Header as CardHeader, 21 | Title as CardTitle, 22 | }; 23 | 24 | export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; 25 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-content.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 28 | 29 | 32 | 33 | Close 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
15 | 16 |
17 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-overlay.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-portal.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/dialog-title.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/dialog/index.ts: -------------------------------------------------------------------------------- 1 | import { Dialog as DialogPrimitive } from "bits-ui"; 2 | 3 | import Title from "./dialog-title.svelte"; 4 | import Portal from "./dialog-portal.svelte"; 5 | import Footer from "./dialog-footer.svelte"; 6 | import Header from "./dialog-header.svelte"; 7 | import Overlay from "./dialog-overlay.svelte"; 8 | import Content from "./dialog-content.svelte"; 9 | import Description from "./dialog-description.svelte"; 10 | 11 | const Root = DialogPrimitive.Root; 12 | const Trigger = DialogPrimitive.Trigger; 13 | const Close = DialogPrimitive.Close; 14 | 15 | export { 16 | Root, 17 | Title, 18 | Portal, 19 | Footer, 20 | Header, 21 | Trigger, 22 | Overlay, 23 | Content, 24 | Description, 25 | Close, 26 | // 27 | Root as Dialog, 28 | Title as DialogTitle, 29 | Portal as DialogPortal, 30 | Footer as DialogFooter, 31 | Header as DialogHeader, 32 | Trigger as DialogTrigger, 33 | Overlay as DialogOverlay, 34 | Content as DialogContent, 35 | Description as DialogDescription, 36 | Close as DialogClose, 37 | }; 38 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-button.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-description.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-element-field.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 22 |
23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-field-errors.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | 22 | {#each errors as error} 23 |
{error}
24 | {/each} 25 |
26 |
27 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-field.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 22 |
23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-fieldset.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-label.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/form-legend.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/form/index.ts: -------------------------------------------------------------------------------- 1 | import * as FormPrimitive from "formsnap"; 2 | import Description from "./form-description.svelte"; 3 | import Label from "./form-label.svelte"; 4 | import FieldErrors from "./form-field-errors.svelte"; 5 | import Field from "./form-field.svelte"; 6 | import Button from "./form-button.svelte"; 7 | import Fieldset from "./form-fieldset.svelte"; 8 | import Legend from "./form-legend.svelte"; 9 | import ElementField from "./form-element-field.svelte"; 10 | 11 | const Control = FormPrimitive.Control; 12 | 13 | export { 14 | Field, 15 | Control, 16 | Label, 17 | FieldErrors, 18 | Description, 19 | Fieldset, 20 | Legend, 21 | ElementField, 22 | Button, 23 | // 24 | Field as FormField, 25 | Control as FormControl, 26 | Description as FormDescription, 27 | Label as FormLabel, 28 | FieldErrors as FormFieldErrors, 29 | Fieldset as FormFieldset, 30 | Legend as FormLegend, 31 | ElementField as FormElementField, 32 | Button as FormButton, 33 | }; 34 | -------------------------------------------------------------------------------- /src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./input.svelte"; 2 | 3 | export type FormInputEvent = T & { 4 | currentTarget: EventTarget & HTMLInputElement; 5 | }; 6 | export type InputEvents = { 7 | blur: FormInputEvent; 8 | change: FormInputEvent; 9 | click: FormInputEvent; 10 | focus: FormInputEvent; 11 | focusin: FormInputEvent; 12 | focusout: FormInputEvent; 13 | keydown: FormInputEvent; 14 | keypress: FormInputEvent; 15 | keyup: FormInputEvent; 16 | mouseover: FormInputEvent; 17 | mouseenter: FormInputEvent; 18 | mouseleave: FormInputEvent; 19 | mousemove: FormInputEvent; 20 | paste: FormInputEvent; 21 | input: FormInputEvent; 22 | wheel: FormInputEvent; 23 | }; 24 | 25 | export { 26 | Root, 27 | // 28 | Root as Input, 29 | }; 30 | -------------------------------------------------------------------------------- /src/lib/components/ui/input/input.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | 43 | -------------------------------------------------------------------------------- /src/lib/components/ui/label/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./label.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Label, 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/components/ui/label/label.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | import Scrollbar from "./scroll-area-scrollbar.svelte"; 2 | import Root from "./scroll-area.svelte"; 3 | 4 | export { 5 | Root, 6 | Scrollbar, 7 | //, 8 | Root as ScrollArea, 9 | Scrollbar as ScrollAreaScrollbar, 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /src/lib/components/ui/scroll-area/scroll-area.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {#if orientation === "vertical" || orientation === "both"} 26 | 27 | {/if} 28 | {#if orientation === "horizontal" || orientation === "both"} 29 | 30 | {/if} 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/lib/components/ui/separator/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./separator.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Separator, 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/components/ui/separator/separator.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/lib/highlightCode.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import { mode } from 'mode-watcher'; 3 | 4 | function createHighlightStore() { 5 | const { subscribe, set } = writable('github'); 6 | 7 | return { 8 | subscribe, 9 | setTheme: (isDark: boolean) => { 10 | set(isDark ? 'github-dark' : 'github'); 11 | } 12 | }; 13 | } 14 | 15 | export const highlightTheme = createHighlightStore(); 16 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /src/lib/md.ts: -------------------------------------------------------------------------------- 1 | // src/lib/md.ts 2 | 3 | import yaml from 'js-yaml'; 4 | 5 | /** 6 | * Function to add or update frontmatter in Markdown content. 7 | * 8 | * @param fileContent - The content of the Markdown file as a string. 9 | * @param url - The URL to be added to the frontmatter. 10 | * @returns The modified Markdown content with updated frontmatter. 11 | */ 12 | export function addFrontmatterToMarkdown(fileContent: string, url: string): string { 13 | // Regular expression to detect existing YAML frontmatter 14 | const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/; 15 | const match = fileContent.match(frontmatterRegex); 16 | 17 | let newContent: string; 18 | 19 | if (match) { 20 | // YAML frontmatter exists, parse the existing frontmatter 21 | const existingFrontmatter = yaml.load(match[1]) as Record || {}; 22 | 23 | // Add or update the 'url' field in the frontmatter 24 | existingFrontmatter['mdpath'] = url; 25 | 26 | // Convert the updated frontmatter back to YAML format 27 | const updatedFrontmatter = yaml.dump(existingFrontmatter); 28 | 29 | // Replace the old frontmatter with the updated one 30 | newContent = fileContent.replace(frontmatterRegex, `---\n${updatedFrontmatter}---\n`); 31 | } else { 32 | // No frontmatter exists, create new frontmatter 33 | const newFrontmatter = yaml.dump({ url }); 34 | 35 | // Prepend the new frontmatter to the file content 36 | newContent = `---\n${newFrontmatter}---\n${fileContent}`; 37 | } 38 | 39 | // Return the updated content 40 | return newContent; 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/pbStore.ts: -------------------------------------------------------------------------------- 1 | import PocketBase from 'pocketbase'; 2 | import { writable } from 'svelte/store'; 3 | import { browser } from '$app/environment'; 4 | 5 | // Client-side PocketBase instance 6 | export const pb = new PocketBase('http://127.0.0.1:8090'); // Replace with your PocketBase URL 7 | 8 | export const currentUser = writable(pb.authStore.model); 9 | 10 | if (browser) { 11 | pb.authStore.onChange((auth) => { 12 | console.log('Client AuthStore changed', auth); 13 | currentUser.set(pb.authStore.model); 14 | }); 15 | } 16 | 17 | export async function login(email: string, password: string) { 18 | try { 19 | const authData = await pb.admins.authWithPassword(email, password); 20 | console.log('Logged in successfully', authData); 21 | return authData; 22 | } catch (error) { 23 | console.error('Login failed', error); 24 | throw error; 25 | } 26 | } 27 | 28 | export function logout() { 29 | pb.authStore.clear(); 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/pocketbase.ts: -------------------------------------------------------------------------------- 1 | import PocketBase from 'pocketbase'; 2 | import { 3 | POCKETBASE_URL, 4 | POCKETBASE_ADMIN_EMAIL, 5 | POCKETBASE_ADMIN_PASSWORD 6 | } from '$env/static/private'; 7 | 8 | let serverPb: PocketBase | null = null; 9 | 10 | export async function getAuthenticatedPocketBase() { 11 | if (!serverPb) { 12 | serverPb = new PocketBase(POCKETBASE_URL); 13 | serverPb.autoCancellation(false); 14 | } 15 | 16 | // Check if already authenticated and try refreshing the token 17 | if (serverPb.authStore.isValid) { 18 | try { 19 | await serverPb.collection('users').authRefresh(); 20 | console.log('Using existing server authentication'); 21 | return serverPb; 22 | } catch (error) { 23 | console.log('Server token refresh failed, re-authenticating'); 24 | } 25 | } 26 | 27 | // If not authenticated or refresh failed, login as admin 28 | try { 29 | await serverPb.admins.authWithPassword(POCKETBASE_ADMIN_EMAIL, POCKETBASE_ADMIN_PASSWORD); 30 | console.log('New server authentication successful'); 31 | return serverPb; 32 | } catch (error) { 33 | console.error('Server authentication failed:', error); 34 | throw error; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/remark-plugins/footNotes.js: -------------------------------------------------------------------------------- 1 | // src/lib/plugins/remark-footnote-html.js 2 | import { visit } from 'unist-util-visit'; 3 | 4 | 5 | function remarkFootnoteHTML() { 6 | return (tree) => { 7 | const footnotes = []; 8 | const footnoteMap = {}; 9 | 10 | // Collect footnote definitions 11 | visit(tree, 'footnoteDefinition', (node) => { 12 | const identifier = node.identifier; 13 | const content = node.children; 14 | footnoteMap[identifier] = content; 15 | console.log(node) 16 | }); 17 | 18 | // Replace footnote references with custom HTML 19 | visit(tree, 'footnoteReference', (node, index, parent) => { 20 | const identifier = node.identifier; 21 | const footnoteNumber = Object.keys(footnoteMap).indexOf(identifier) + 1; 22 | 23 | if (footnoteNumber === 0) return; 24 | 25 | const sup = { 26 | type: 'html', 27 | value: `${footnoteNumber}`, 28 | }; 29 | 30 | parent.children.splice(index, 1, sup); 31 | }); 32 | 33 | // Remove footnote definitions from the tree 34 | tree.children = tree.children.filter((node) => node.type !== 'footnoteDefinition'); 35 | 36 | // Append the footnotes section at the end 37 | const footnotesSection = { 38 | type: 'html', 39 | value: '
    ', 40 | }; 41 | 42 | tree.children.push(footnotesSection); 43 | 44 | Object.keys(footnoteMap).forEach((identifier, idx) => { 45 | const footnoteNumber = idx + 1; 46 | const content = footnoteMap[identifier] 47 | .map((child) => { 48 | if (child.type === 'text') { 49 | return child.value; 50 | } else { 51 | // Handle other node types as needed 52 | return ''; 53 | } 54 | }) 55 | .join(''); 56 | 57 | const footnoteItem = { 58 | type: 'html', 59 | value: `
  1. ${content} ↩︎
  2. `, 60 | }; 61 | 62 | tree.children.push(footnoteItem); 63 | }); 64 | 65 | // Close the ordered list and section 66 | tree.children.push({ 67 | type: 'html', 68 | value: '
', 69 | }); 70 | }; 71 | } 72 | 73 | export default remarkFootnoteHTML; 74 | -------------------------------------------------------------------------------- /src/lib/remark-plugins/highlightSyn.js: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit' 2 | 3 | function remarkHighlight() { 4 | return (tree) => { 5 | visit(tree, 'text', (node, index, parent) => { 6 | const matches = node.value.match(/==(.*?)==/g) 7 | if (!matches) return 8 | 9 | const children = [] 10 | let lastIndex = 0 11 | 12 | matches.forEach((match) => { 13 | const startIndex = node.value.indexOf(match, lastIndex) 14 | const endIndex = startIndex + match.length 15 | 16 | // Add text before the highlight 17 | if (startIndex > lastIndex) { 18 | children.push({ 19 | type: 'text', 20 | value: node.value.slice(lastIndex, startIndex) 21 | }) 22 | } 23 | 24 | // Add the highlighted text with a span and class 25 | children.push({ 26 | type: 'span', 27 | data: { 28 | hName: 'span', 29 | hProperties: { 30 | className: ['highlight'] 31 | } 32 | }, 33 | children: [ 34 | { 35 | type: 'text', 36 | value: match.slice(2, -2) // Remove '==' from the start and end 37 | } 38 | ] 39 | }) 40 | 41 | lastIndex = endIndex 42 | }) 43 | 44 | // Add any remaining text after the last highlight 45 | if (lastIndex < node.value.length) { 46 | children.push({ 47 | type: 'text', 48 | value: node.value.slice(lastIndex) 49 | }) 50 | } 51 | 52 | // Replace the original node with the new children 53 | parent.children.splice(index, 1, ...children) 54 | }) 55 | } 56 | } 57 | 58 | export default remarkHighlight 59 | -------------------------------------------------------------------------------- /src/lib/remark-plugins/imgRel.js: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit'; 2 | import path from 'path'; 3 | 4 | export default function remarkLogImages() { 5 | return function transformer(tree, file) { 6 | if (!file || !file.data || !file.data.fm || !file.data.fm.mdpath) { 7 | throw new Error('File metadata with url is missing.'); 8 | } 9 | 10 | 11 | const url = file.data.fm.mdpath; // e.g., '/writing/f2/test' 12 | 13 | visit(tree, 'image', (node) => { 14 | // Extract the link part before any pipe (e.g., [[link|alias]]) 15 | const rawLink = node.url.trim(); // e.g., '../f1/test' 16 | 17 | console.log(node) 18 | 19 | if (!rawLink.includes('/api/img') && !rawLink.includes('://')) { 20 | const folder = path.dirname(url.split('.')[0]); 21 | const absPath = path.join(folder, rawLink); // e.g., 'mdpath/f1/test' 22 | console.log("============>", rawLink, absPath) 23 | node.url = `/api/img/${absPath}`; 24 | } 25 | 26 | 27 | }); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/remark-plugins/mermaidDiag.js: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit'; 2 | 3 | // Create the plugin 4 | const remarkMermaid = () => { 5 | return (tree) => { 6 | visit(tree, 'code', (node) => { 7 | if (node.lang === 'mermaid') { 8 | // Replace the code block with a custom HTML structure 9 | node.type = 'html'; 10 | node.value = `
${node.value}
`; 11 | } 12 | }); 13 | }; 14 | }; 15 | 16 | export default remarkMermaid; 17 | -------------------------------------------------------------------------------- /src/lib/remark-plugins/obsidianImage.js: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit'; 2 | 3 | function obsidianImagePlugin() { 4 | return (tree) => { 5 | visit(tree, 'paragraph', (node) => { 6 | const newChildren = []; 7 | let i = 0; 8 | 9 | while (i < node.children.length) { 10 | const currentNode = node.children[i]; 11 | 12 | // Check if the current node is a 'text' node with a '!' 13 | if (currentNode.type === 'text' && currentNode.value === '!') { 14 | // Check if the next node is a 'wikiLink' node with an image file extension 15 | const nextNode = node.children[i + 1]; 16 | if ( 17 | nextNode && 18 | nextNode.type === 'wikiLink' && 19 | /\.(png|jpe?g|gif|svg|webp)$/.test(nextNode.value) 20 | ) { 21 | // Replace the '!' and 'wikiLink' with an 'image' node 22 | let newUrl = '/api/img/' + nextNode.value; 23 | newChildren.push({ 24 | type: 'image', 25 | url: newUrl, 26 | alt: nextNode.value.split('/').pop() // Use the filename as the alt text 27 | }); 28 | i += 2; // Skip both the 'text' and 'wikiLink' nodes 29 | continue; 30 | } 31 | } 32 | 33 | // If no match, just push the current node as-is 34 | newChildren.push(currentNode); 35 | i++; 36 | } 37 | 38 | // Replace the old children with the new set of children 39 | node.children = newChildren; 40 | }); 41 | }; 42 | } 43 | 44 | export default obsidianImagePlugin; 45 | -------------------------------------------------------------------------------- /src/lib/remark-plugins/remarkTags.ts: -------------------------------------------------------------------------------- 1 | import { visit } from 'unist-util-visit'; 2 | 3 | function remarkTags() { 4 | return (tree) => { 5 | visit(tree, 'text', (node, index, parent) => { 6 | const matches = node.value.match(/#[a-zA-Z0-9_-]+/g); 7 | if (!matches) return; 8 | 9 | const children = []; 10 | let lastIndex = 0; 11 | 12 | matches.forEach((match) => { 13 | const startIndex = node.value.indexOf(match, lastIndex); 14 | const endIndex = startIndex + match.length; 15 | 16 | // Add text before the tag 17 | if (startIndex > lastIndex) { 18 | children.push({ 19 | type: 'text', 20 | value: node.value.slice(lastIndex, startIndex) 21 | }); 22 | } 23 | 24 | // Add the tag with a span and class, including an anchor tag 25 | const tagName = match.slice(1); // Remove '#' from the start 26 | children.push({ 27 | type: 'span', 28 | data: { 29 | hName: 'span', 30 | hProperties: { 31 | className: ['tag'] 32 | } 33 | }, 34 | children: [ 35 | { 36 | type: 'element', 37 | data: { 38 | hName: 'a', 39 | hProperties: { 40 | href: `/tags/${tagName}`, 41 | className: ['tag-link'] 42 | } 43 | }, 44 | children: [ 45 | { 46 | type: 'text', 47 | value: tagName 48 | } 49 | ] 50 | } 51 | ] 52 | }); 53 | 54 | lastIndex = endIndex; 55 | }); 56 | 57 | // Add any remaining text after the last tag 58 | if (lastIndex < node.value.length) { 59 | children.push({ 60 | type: 'text', 61 | value: node.value.slice(lastIndex) 62 | }); 63 | } 64 | 65 | // Replace the original node with the new children 66 | parent.children.splice(index, 1, ...children); 67 | }); 68 | }; 69 | } 70 | 71 | export default remarkTags; 72 | -------------------------------------------------------------------------------- /src/lib/server/auth.ts: -------------------------------------------------------------------------------- 1 | import PocketBase from 'pocketbase'; 2 | import { 3 | POCKETBASE_URL, 4 | POCKETBASE_ADMIN_EMAIL, 5 | POCKETBASE_ADMIN_PASSWORD 6 | } from '$env/static/private'; 7 | 8 | let pocketBaseInstance: PocketBase | null = null; 9 | 10 | export async function getAuthenticatedPocketBase() { 11 | if (!pocketBaseInstance) { 12 | pocketBaseInstance = new PocketBase(POCKETBASE_URL); 13 | pocketBaseInstance.autoCancellation(false); // Prevent cancellation of overlapping requests 14 | } 15 | 16 | // Check if the current authentication is valid 17 | if (pocketBaseInstance.authStore.isValid) { 18 | try { 19 | console.log('login valid'); 20 | 21 | // Check if logged in as a user (not admin) and refresh token 22 | if (pocketBaseInstance.authStore.model?.email !== POCKETBASE_ADMIN_EMAIL) { 23 | // Only refresh tokens for non-admin users 24 | await pocketBaseInstance.collection('users').authRefresh(); 25 | console.log('Token refreshed successfully'); 26 | } 27 | 28 | return pocketBaseInstance; 29 | } catch (error) { 30 | console.error('Token refresh failed:', error); 31 | } 32 | } 33 | 34 | // Login as admin if token is invalid or refresh failed 35 | try { 36 | console.log('Attempting to log in as admin...'); 37 | await pocketBaseInstance.admins.authWithPassword( 38 | POCKETBASE_ADMIN_EMAIL, 39 | POCKETBASE_ADMIN_PASSWORD 40 | ); 41 | console.log('New admin authentication successful'); 42 | return pocketBaseInstance; 43 | } catch (error) { 44 | // Log any error encountered during login 45 | console.error('Admin login failed:', error); 46 | throw error; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/lib/stores/sidebarStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const isSidebarVisible = writable(true); 4 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { cubicOut } from "svelte/easing"; 4 | import type { TransitionConfig } from "svelte/transition"; 5 | 6 | export function cn(...inputs: ClassValue[]) { 7 | return twMerge(clsx(inputs)); 8 | } 9 | 10 | type FlyAndScaleParams = { 11 | y?: number; 12 | x?: number; 13 | start?: number; 14 | duration?: number; 15 | }; 16 | 17 | export const flyAndScale = ( 18 | node: Element, 19 | params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } 20 | ): TransitionConfig => { 21 | const style = getComputedStyle(node); 22 | const transform = style.transform === "none" ? "" : style.transform; 23 | 24 | const scaleConversion = ( 25 | valueA: number, 26 | scaleA: [number, number], 27 | scaleB: [number, number] 28 | ) => { 29 | const [minA, maxA] = scaleA; 30 | const [minB, maxB] = scaleB; 31 | 32 | const percentage = (valueA - minA) / (maxA - minA); 33 | const valueB = percentage * (maxB - minB) + minB; 34 | 35 | return valueB; 36 | }; 37 | 38 | const styleToString = ( 39 | style: Record 40 | ): string => { 41 | return Object.keys(style).reduce((str, key) => { 42 | if (style[key] === undefined) return str; 43 | return str + `${key}:${style[key]};`; 44 | }, ""); 45 | }; 46 | 47 | return { 48 | duration: params.duration ?? 200, 49 | delay: 0, 50 | css: (t) => { 51 | const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); 52 | const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); 53 | const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); 54 | 55 | return styleToString({ 56 | transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, 57 | opacity: t 58 | }); 59 | }, 60 | easing: cubicOut 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import { json } from "@sveltejs/kit"; 2 | import type { RequestHandler } from "./$types"; 3 | import { superValidate } from "sveltekit-superforms"; 4 | import { formSchema } from "$lib/components/schema"; 5 | import { zod } from "sveltekit-superforms/adapters"; 6 | import { 7 | TITLE, 8 | POCKETBASE_ADMIN_EMAIL, 9 | POCKETBASE_ADMIN_PASSWORD, 10 | } from "$env/static/private"; 11 | import { CAP1, CAP2, CAP3 } from "$env/static/private"; 12 | 13 | export async function load({ fetch, params }) { 14 | const ftree = await fetch("/api/ls"); 15 | const tagresp = await fetch("/api/tags"); 16 | const tags = await tagresp.json(); 17 | const filetree = await ftree.json(); 18 | const siteTitle = TITLE; 19 | const captions = [CAP1, CAP2, CAP3]; 20 | 21 | console.log( 22 | "logged in with: ", 23 | POCKETBASE_ADMIN_EMAIL, 24 | POCKETBASE_ADMIN_PASSWORD, 25 | CAP1, 26 | CAP2, 27 | ); 28 | 29 | return { filetree, siteTitle, tags, captions }; 30 | } 31 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 |
57 |
58 |
59 | 62 |
{siteTitle}
63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 | 71 | 75 | 76 |
80 | 81 | {#if fileTree} 82 | 83 | {:else} 84 | Loading... 85 | {/if} 86 | 87 | 88 |
89 | 90 | 91 |
92 | 93 | 94 |
95 | 96 | 108 | -------------------------------------------------------------------------------- /src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import PocketBase from "pocketbase"; 2 | import { getAuthenticatedPocketBase } from "$lib/server/auth"; 3 | 4 | const pb = await getAuthenticatedPocketBase(); 5 | 6 | export async function load({ params }) { 7 | try { 8 | const mdbase = await pb.collections.getOne("mdbase"); 9 | 10 | const records = await pb.collection("mdbase").getList(1, 1, { 11 | filter: "frontmatter.home = true", 12 | sort: "-created", 13 | }); 14 | 15 | let post = null; 16 | if (records.items.length > 0) { 17 | post = records.items[0]; 18 | } 19 | 20 | if (post) { 21 | const backlinks = await getBacklinks(`${post.frontmatter.mdpath}`); 22 | 23 | const tags = post.expand?.tags.map((tag) => { 24 | return { 25 | name: tag.tag, 26 | }; 27 | }); 28 | return { post, title: post.title, backlinks, tags }; 29 | } else { 30 | return { post: null, title: "", backlinks: [], tags: [] }; 31 | } 32 | } catch (error) { 33 | console.error(`Failed to fetch post: ${error}`); 34 | return { message: `Failed to fetch post: ${error}` }; 35 | } 36 | } 37 | 38 | async function getBacklinks(url) { 39 | const mdbaseCollection = pb.collection("mdbase"); 40 | const documentUrl = url; 41 | try { 42 | if (!documentUrl) { 43 | return new Response( 44 | JSON.stringify({ message: "URL parameter is required" }), 45 | { 46 | status: 400, 47 | }, 48 | ); 49 | } 50 | 51 | const documents = await mdbaseCollection.getList(1, 1, { 52 | filter: `url="${documentUrl}"`, 53 | expand: "backlinks", 54 | }); 55 | 56 | if (documents.items.length === 0) { 57 | return new Response(JSON.stringify({ message: "Document not found" }), { 58 | status: 404, 59 | }); 60 | } 61 | 62 | const document = documents.items[0]; 63 | 64 | const backLinks = (document.expand?.backlinks || []).map((link) => ({ 65 | id: link.id, 66 | title: link.title, 67 | url: link.url, 68 | })); 69 | 70 | return backLinks; 71 | } catch (error: any) { 72 | console.error("Error in backlinks API:", error); 73 | return {}; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | {#if data.post} 13 |
14 |
15 | {data.title} 16 |
17 | 18 |
19 | {#if data?.post?.frontmatter?.date} 20 |
21 | 22 |
{data.post.frontmatter.date.split(' ')[0]}
23 |
24 | {/if} 25 | {#if tags} 26 |
27 | {#each tags as tag (tag.name)} 28 | {tag.name} 33 | {/each} 34 |
35 | {/if} 36 |
37 |
38 |
39 | 40 |
41 | {#if data?.backlinks?.length > 0} 42 |
43 |
BACKLINKS
44 | {/if} 45 | {#each data?.backlinks || [] as bl (bl.id)} 46 | 47 | {/each} 48 |
49 | {:else} 50 |
51 | Set homepage in the frontmatter of one of your markdown files to display it as home 52 |
53 | {/if} 54 | 55 | 60 | -------------------------------------------------------------------------------- /src/routes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/src/routes/.DS_Store -------------------------------------------------------------------------------- /src/routes/[...post].md/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { json } from "@sveltejs/kit"; 2 | import type { RequestHandler } from "./$types"; 3 | import PocketBase from "pocketbase"; 4 | import { promises as fs } from "fs"; 5 | import { getAuthenticatedPocketBase } from "$lib/server/auth"; 6 | 7 | const pb = await getAuthenticatedPocketBase(); 8 | 9 | async function getBacklinks(url) { 10 | const mdbaseCollection = pb.collection("mdbase"); 11 | const documentUrl = url; 12 | try { 13 | if (!documentUrl) { 14 | return new Response( 15 | JSON.stringify({ message: "URL parameter is required" }), 16 | { 17 | status: 400, 18 | }, 19 | ); 20 | } 21 | 22 | const documents = await mdbaseCollection.getList(1, 1, { 23 | filter: `url="${documentUrl}"`, 24 | expand: "backlinks", 25 | }); 26 | 27 | if (documents.items.length === 0) { 28 | return new Response(JSON.stringify({ message: "Document not found" }), { 29 | status: 404, 30 | }); 31 | } 32 | 33 | const document = documents.items[0]; 34 | 35 | const backLinks = (document.expand?.backlinks || []).map((link) => ({ 36 | id: link.id, 37 | title: link.title, 38 | url: link.url, 39 | })); 40 | 41 | return backLinks; 42 | } catch (error: any) { 43 | console.error("Error in backlinks API:", error); 44 | return {}; 45 | } 46 | } 47 | 48 | async function computeGraphData(fileUrl) { 49 | const currentPage = await pb 50 | .collection("mdbase") 51 | .getFirstListItem(`url="${fileUrl}"`); 52 | const relatedPages = await pb.collection("mdbase").getList(1, 50, { 53 | filter: `id ?~ "${currentPage.backlinks}" || id ?~ "${currentPage.links}"`, 54 | }); 55 | 56 | // Use a Set to store unique node IDs 57 | const uniqueNodeIds = new Set([currentPage.id]); 58 | 59 | // Create nodes array with current page 60 | const nodes = [ 61 | { id: currentPage.id, label: currentPage.title, color: "#ff0000" }, 62 | ]; 63 | 64 | // Add related pages to nodes array, avoiding duplicates 65 | relatedPages.items.forEach((p) => { 66 | if (!uniqueNodeIds.has(p.id)) { 67 | uniqueNodeIds.add(p.id); 68 | nodes.push({ id: p.id, label: p.title, color: "#00ff00" }); 69 | } 70 | }); 71 | 72 | // Create edges array 73 | const edges = [ 74 | ...currentPage.links.map((link) => ({ from: currentPage.id, to: link })), 75 | ...currentPage.backlinks.map((backlink) => ({ 76 | from: backlink, 77 | to: currentPage.id, 78 | })), 79 | ]; 80 | 81 | return { nodes, edges }; 82 | } 83 | // Main load function 84 | export async function load({ params, fetch, locals }) { 85 | try { 86 | // Step 1: Authenticate 87 | /* console.log(pb); */ 88 | console.log(params.post); 89 | const post = await pb 90 | .collection("mdbase") 91 | .getFirstListItem(`url="${params.post}.md"`, { expand: "tags" }); 92 | const backlinks = await getBacklinks(`${params.post}.md`); 93 | // const graphData = await computeGraphData(`${params.post}.md`); 94 | 95 | const tags = post.expand?.tags.map((tag) => { 96 | return { 97 | name: tag.tag, 98 | }; 99 | }); 100 | console.log(tags); 101 | 102 | return { post, title: post.title, backlinks, tags }; 103 | } catch (error) { 104 | console.error(`Failed to fetch post: ${error}`); 105 | return { message: `Failed to fetch post: ${error}` }; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/routes/[...post].md/+page.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |
14 | {data.title} 15 |
16 | 17 |
18 | {#if data?.post?.frontmatter?.date} 19 |
20 | 21 |
{data.post.frontmatter.date.split(' ')[0]}
22 |
23 | {/if} 24 | {#if tags} 25 |
26 | {#each tags as tag (tag.name)} 27 | {tag.name} 32 | {/each} 33 |
34 | {/if} 35 |
36 |
37 |
38 | 39 |
40 | {#if data?.backlinks?.length > 0} 41 |
42 |
BACKLINKS
43 | {/if} 44 | {#each data?.backlinks || [] as bl (bl.id)} 45 | 46 | {/each} 47 |
48 | 49 | 54 | -------------------------------------------------------------------------------- /src/routes/about/+page.svelte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/src/routes/about/+page.svelte -------------------------------------------------------------------------------- /src/routes/api/backlinks/+server.ts: -------------------------------------------------------------------------------- 1 | import type { RequestHandler } from '@sveltejs/kit'; 2 | import PocketBase from 'pocketbase'; 3 | import { 4 | POCKETBASE_ADMIN_PASSWORD, 5 | POCKETBASE_ADMIN_EMAIL, 6 | POCKETBASE_URL, 7 | API_KEY 8 | } from '$env/static/private'; 9 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 10 | 11 | const pb = await getAuthenticatedPocketBase(); 12 | 13 | export const GET: RequestHandler = async ({ url, request }) => { 14 | try { 15 | const mdbaseCollection = pb.collection('mdbase'); 16 | const documentUrl = url.searchParams.get('url'); 17 | 18 | if (!documentUrl) { 19 | return new Response(JSON.stringify({ message: 'URL parameter is required' }), { 20 | status: 400 21 | }); 22 | } 23 | 24 | const documents = await mdbaseCollection.getList(1, 1, { 25 | filter: `url="${documentUrl}"`, 26 | expand: 'backlinks' 27 | }); 28 | 29 | if (documents.items.length === 0) { 30 | return new Response(JSON.stringify({ message: 'Document not found' }), { status: 404 }); 31 | } 32 | 33 | const document = documents.items[0]; 34 | 35 | const backLinks = (document.expand?.backlinks || []).map((link) => ({ 36 | id: link.id, 37 | title: link.title, 38 | url: link.url 39 | })); 40 | 41 | return new Response(JSON.stringify({ backLinks }), { 42 | status: 200, 43 | headers: { 'Content-Type': 'application/json' } 44 | }); 45 | } catch (error: any) { 46 | console.error('Error in backlinks API:', error); 47 | return new Response( 48 | JSON.stringify({ 49 | message: 'Failed to retrieve backlinks', 50 | error: error.message || 'Unknown error', 51 | details: error.data ? JSON.stringify(error.data) : 'No additional details' 52 | }), 53 | { status: 500 } 54 | ); 55 | } 56 | }; 57 | 58 | export const OPTIONS: RequestHandler = async () => { 59 | return new Response(null, { 60 | status: 204, 61 | headers: { 62 | 'Access-Control-Allow-Origin': '*', 63 | 'Access-Control-Allow-Methods': 'GET, OPTIONS', 64 | 'Access-Control-Allow-Headers': 'Content-Type, X-API-Key' 65 | } 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /src/routes/api/graph/+server.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@sveltejs/kit'; 2 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 3 | import type { RequestHandler } from './$types'; 4 | 5 | export const GET: RequestHandler = async ({ url, request }) => { 6 | const fileUrl = url.searchParams.get('url'); 7 | console.log(fileUrl); 8 | 9 | if (!fileUrl) { 10 | return json({ error: 'Missing URL parameter' }, { status: 400 }); 11 | } 12 | 13 | try { 14 | const pb = await getAuthenticatedPocketBase(); 15 | const currentPage = await pb.collection('mdbase').getFirstListItem(`url="${fileUrl}"`); 16 | const graphData = await computeGraphData(currentPage); 17 | return json(graphData); 18 | } catch (error) { 19 | console.error('Error computing graph:', error); 20 | return json({ error: error }, { status: 500 }); 21 | } 22 | }; 23 | 24 | async function computeGraphData(currentPage) { 25 | const pb = await getAuthenticatedPocketBase(); 26 | const relatedPages = await pb.collection('mdbase').getList(1, 50, { 27 | filter: `id ?~ "${currentPage.backlinks}" || id ?~ "${currentPage.links}"` 28 | }); 29 | 30 | const nodes = [ 31 | { id: currentPage.id, label: currentPage.title, color: '#ff0000' }, // Current page (red) 32 | ...relatedPages.items.map((p) => ({ id: p.id, label: p.title, color: '#00ff00' })) // Related pages (green) 33 | ]; 34 | 35 | const edges = [ 36 | ...currentPage.links.map((link) => ({ from: currentPage.id, to: link })), 37 | ...currentPage.backlinks.map((backlink) => ({ from: backlink, to: currentPage.id })) 38 | ]; 39 | 40 | return { nodes, edges }; 41 | } 42 | -------------------------------------------------------------------------------- /src/routes/api/hello/+server.ts: -------------------------------------------------------------------------------- 1 | // src/routes/api/hello/+server.ts 2 | import type { RequestHandler } from './$types'; 3 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 4 | 5 | export const GET: RequestHandler = async ({ request }) => { 6 | const pb = await getAuthenticatedPocketBase(); 7 | 8 | // Ensure the server is authenticated 9 | if (!pb.authStore.isValid) { 10 | return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 }); 11 | } 12 | 13 | // Example of accessing PocketBase data 14 | const users = await pb.collection('users').getFullList(); 15 | 16 | return new Response(JSON.stringify({ data: users }), { status: 200 }); 17 | }; 18 | -------------------------------------------------------------------------------- /src/routes/api/img/[...path]/+server.ts: -------------------------------------------------------------------------------- 1 | import { error } from '@sveltejs/kit'; 2 | import type { RequestHandler } from './$types'; 3 | import PocketBase from 'pocketbase'; 4 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 5 | 6 | export const GET: RequestHandler = async ({ params }) => { 7 | try { 8 | const pb = await getAuthenticatedPocketBase(); 9 | const imagePath = params.path; 10 | console.log('Requested image path:', imagePath); 11 | 12 | const record = await pb.collection('attachments').getFirstListItem(`url="${imagePath}"`); 13 | console.log('Found record:', record); 14 | 15 | if (!record) { 16 | throw error(404, 'Image not found'); 17 | } 18 | 19 | const fileUrl = pb.files.getUrl(record, record.file); 20 | console.log('File URL:', fileUrl); 21 | 22 | const fileResponse = await fetch(fileUrl); 23 | 24 | if (!fileResponse.ok) { 25 | throw error(500, 'Failed to fetch the image file'); 26 | } 27 | 28 | const contentType = fileResponse.headers.get('content-type') || getContentType(imagePath); 29 | console.log('Content Type:', contentType); 30 | 31 | // Get the filename from the record or use a default 32 | const filename = record.filename || 'image'; 33 | 34 | return new Response(fileResponse.body, { 35 | status: 200, 36 | headers: { 37 | 'Content-Type': contentType, 38 | 'Content-Disposition': `attachment; filename="${filename}"`, 39 | 'Cache-Control': 'public, max-age=3600' 40 | } 41 | }); 42 | } catch (err) { 43 | console.error('Error serving image:', err); 44 | throw error(500, 'Internal server error'); 45 | } 46 | }; 47 | 48 | function getContentType(filename: string): string { 49 | const ext = filename.split('.').pop()?.toLowerCase(); 50 | switch (ext) { 51 | case 'webp': 52 | return 'image/webp'; 53 | case 'jpg': 54 | case 'jpeg': 55 | return 'image/jpeg'; 56 | case 'png': 57 | return 'image/png'; 58 | case 'gif': 59 | return 'image/gif'; 60 | case 'svg': 61 | return 'image/svg+xml'; 62 | default: 63 | return 'application/octet-stream'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/routes/api/links/+server.ts: -------------------------------------------------------------------------------- 1 | import type { RequestHandler } from '@sveltejs/kit'; 2 | import PocketBase from 'pocketbase'; 3 | import { 4 | POCKETBASE_ADMIN_PASSWORD, 5 | POCKETBASE_ADMIN_EMAIL, 6 | POCKETBASE_URL, 7 | API_KEY 8 | } from '$env/static/private'; 9 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 10 | 11 | const pb = getAuthenticatedPocketBase(); 12 | 13 | export const GET: RequestHandler = async ({ url, request }) => { 14 | try { 15 | const mdbaseCollection = pb.collection('mdbase'); 16 | const documentUrl = url.searchParams.get('url'); 17 | 18 | if (!documentUrl) { 19 | return new Response(JSON.stringify({ message: 'URL parameter is required' }), { 20 | status: 400 21 | }); 22 | } 23 | 24 | const documents = await mdbaseCollection.getList(1, 1, { 25 | filter: `url="${documentUrl}"`, 26 | expand: 'links,backlinks' 27 | }); 28 | 29 | if (documents.items.length === 0) { 30 | return new Response(JSON.stringify({ message: 'Document not found' }), { status: 404 }); 31 | } 32 | 33 | const document = documents.items[0]; 34 | 35 | const forwardLinks = (document.expand?.links || []).map((link) => ({ 36 | id: link.id, 37 | title: link.title, 38 | url: link.url 39 | })); 40 | 41 | const backLinks = (document.expand?.backlinks || []).map((link) => ({ 42 | id: link.id, 43 | title: link.title, 44 | url: link.url 45 | })); 46 | 47 | return new Response(JSON.stringify({ forwardLinks, backLinks }), { 48 | status: 200, 49 | headers: { 'Content-Type': 'application/json' } 50 | }); 51 | } catch (error: any) { 52 | console.error('Error in links API:', error); 53 | return new Response( 54 | JSON.stringify({ 55 | message: 'Failed to retrieve links', 56 | error: error.message || 'Unknown error', 57 | details: error.data ? JSON.stringify(error.data) : 'No additional details' 58 | }), 59 | { status: 500 } 60 | ); 61 | } 62 | }; 63 | 64 | export const OPTIONS: RequestHandler = async () => { 65 | return new Response(null, { 66 | status: 204, 67 | headers: { 68 | 'Access-Control-Allow-Origin': '*', 69 | 'Access-Control-Allow-Methods': 'GET, OPTIONS', 70 | 'Access-Control-Allow-Headers': 'Content-Type, X-API-Key' 71 | } 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /src/routes/api/ls/+server.ts: -------------------------------------------------------------------------------- 1 | import { json } from "@sveltejs/kit"; 2 | import type { RequestHandler } from "./$types"; 3 | import { getAuthenticatedPocketBase } from "$lib/server/auth"; 4 | 5 | interface FileNode { 6 | id: string; 7 | title: string; 8 | name: string; 9 | url: string; 10 | children: FileNode[]; 11 | } 12 | 13 | function buildFileTree(records: any[]): FileNode[] { 14 | const tree: FileNode[] = []; 15 | 16 | records.forEach((record) => { 17 | const pathParts = record.url.split("/").filter(Boolean); // Split the URL into parts 18 | let currentLevel = tree; 19 | 20 | pathParts.forEach((part: string, index: number) => { 21 | let existingNode = currentLevel.find((node) => node.name === part); 22 | 23 | // If the node doesn't exist, create a new one 24 | if (!existingNode) { 25 | existingNode = { 26 | id: "", // Only set if it's a file (at the last level) 27 | title: "", 28 | name: part, // The name of the folder or file 29 | url: "", // Only set if it's a file (at the last level) 30 | children: [], // This will hold the children (for folders) 31 | }; 32 | currentLevel.push(existingNode); 33 | } 34 | 35 | // If it's the last part of the path (a file), assign file properties 36 | if (index === pathParts.length - 1) { 37 | existingNode.id = record.id; 38 | existingNode.title = record.title; 39 | existingNode.url = record.url; 40 | } 41 | 42 | // Move to the next level in the tree 43 | currentLevel = existingNode.children; 44 | }); 45 | }); 46 | 47 | return tree; 48 | } 49 | 50 | 51 | export const GET: RequestHandler = async () => { 52 | try { 53 | const pb = await getAuthenticatedPocketBase(); 54 | const pageSize = 200; // Adjust this value based on your needs 55 | let page = 1; 56 | let allRecords: any[] = []; 57 | 58 | while (true) { 59 | const result = await pb.collection("mdbase").getList(page, pageSize, { 60 | sort: "url", 61 | }); 62 | 63 | allRecords = allRecords.concat(result.items); 64 | 65 | if (!result.items.length || result.items.length < pageSize) { 66 | break; 67 | } 68 | 69 | page++; 70 | } 71 | 72 | const fileTree = buildFileTree(allRecords); 73 | return json(fileTree); 74 | } catch (error) { 75 | console.error("Error fetching records:", error); 76 | return json({ error: "Failed to fetch file tree" }, { status: 500 }); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /src/routes/api/search/+server.ts: -------------------------------------------------------------------------------- 1 | // api/search/+server.ts 2 | import { json } from '@sveltejs/kit'; 3 | import type { RequestHandler } from './$types'; 4 | import uFuzzy from '@leeoniya/ufuzzy'; 5 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 6 | 7 | function extractSnippet(content: string, query: string, snippetLength: number = 150) { 8 | const lowerContent = content.toLowerCase(); 9 | const lowerQuery = query.toLowerCase(); 10 | const index = lowerContent.indexOf(lowerQuery); 11 | 12 | if (index === -1) return content.slice(0, snippetLength); 13 | 14 | const start = Math.max(0, index - snippetLength / 2); 15 | const end = Math.min(content.length, index + query.length + snippetLength / 2); 16 | 17 | let snippet = content.slice(start, end); 18 | 19 | if (start > 0) snippet = '...' + snippet; 20 | if (end < content.length) snippet = snippet + '...'; 21 | 22 | return snippet; 23 | } 24 | 25 | export const GET: RequestHandler = async ({ url }) => { 26 | const query = url.searchParams.get('query'); 27 | if (!query) { 28 | return json({ error: 'Query parameter is required' }, { status: 400 }); 29 | } 30 | 31 | try { 32 | /* await authenticateAdmin(); */ 33 | const pb = await getAuthenticatedPocketBase(); 34 | 35 | const pbResults = await pb.collection('mdbase').getList(1, 1000, { 36 | fields: 'id,title,content,url' 37 | }); 38 | console.log('searched'); 39 | 40 | const haystack = pbResults.items.map((item) => item.title + ' ' + item.content); 41 | const uf = new uFuzzy(); 42 | 43 | let idxs = uf.filter(haystack, query); 44 | 45 | if (idxs != null && idxs.length > 0) { 46 | let info = uf.info(idxs, haystack, query); 47 | let order = uf.sort(info, haystack, query); 48 | 49 | const results = order.map((i) => { 50 | const item = pbResults.items[info.idx[i]]; 51 | return { 52 | title: item.title, 53 | url: `${item.url}`, 54 | snippet: extractSnippet(item.content, query) 55 | }; 56 | }); 57 | 58 | return json(results); 59 | } else { 60 | return json([]); 61 | } 62 | } catch (error) { 63 | console.error('Search error:', error); 64 | return json({ error: 'An error occurred during search' }, { status: 500 }); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/routes/api/tags/+server.ts: -------------------------------------------------------------------------------- 1 | // src/routes/api/hello/+server.ts 2 | import { json } from '@sveltejs/kit'; 3 | import type { RequestHandler } from './$types'; 4 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 5 | 6 | export const GET: RequestHandler = async ({ request }) => { 7 | const pb = await getAuthenticatedPocketBase(); 8 | 9 | const records = await pb.collection('tags').getFullList(); 10 | const tags = records.map((tag) => { 11 | return { 12 | name: tag.tag 13 | }; 14 | }); 15 | 16 | return json(tags); 17 | }; 18 | -------------------------------------------------------------------------------- /src/routes/login/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad, Actions } from './$types.js'; 2 | import { fail, redirect } from '@sveltejs/kit'; 3 | import { superValidate } from 'sveltekit-superforms'; 4 | import { zod } from 'sveltekit-superforms/adapters'; 5 | import { formSchema } from '$lib/components/schema'; 6 | 7 | export const load: PageServerLoad = async () => { 8 | return { 9 | form: await superValidate(zod(formSchema)) 10 | }; 11 | }; 12 | 13 | export const actions: Actions = { 14 | default: async (event) => { 15 | const data = await superValidate(event, zod(formSchema)); 16 | if (!data.valid) { 17 | return fail(400, { 18 | data 19 | }); 20 | } 21 | console.log(data); 22 | const email = data.data.username; 23 | const password = data.data.password; 24 | console.log('Login action called'); 25 | if (!email || !password) { 26 | return fail(400, { emailRequired: !email, passwordRequired: !password }); 27 | } 28 | try { 29 | const authData = await event.locals.pb.collection('users').authWithPassword(email, password); 30 | console.log('Logged in successfully. Auth state:', event.locals.pb.authStore.isValid); 31 | 32 | // Ensure the auth data is saved to the auth store 33 | event.locals.pb.authStore.save(authData.token, authData.record); 34 | 35 | // Set the auth cookie 36 | const cookieOptions = { 37 | httpOnly: true, 38 | secure: process.env.NODE_ENV === 'production', 39 | sameSite: 'lax', 40 | path: '/', 41 | maxAge: 60 * 60 * 24 * 30 // 30 days 42 | }; 43 | const cookie = event.locals.pb.authStore.exportToCookie(cookieOptions); 44 | 45 | console.log('Auth state before redirect:', event.locals.pb.authStore.isValid); 46 | console.log('Attempting to redirect to /dashboard'); 47 | 48 | // Use throw redirect instead of return 49 | // throw redirect(303, '/login/success'); 50 | } catch (error) { 51 | console.error('Login error:', error); 52 | const errorObj = error as ClientResponseError; 53 | return fail(500, { form: data }); 54 | } 55 | 56 | throw redirect(303, '/login/success'); 57 | /* return { 58 | form 59 | }; */ 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/routes/login/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/routes/login/success/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad } from './$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load: PageServerLoad = async ({ locals }) => { 5 | console.log('Dashboard load function. Auth state:', locals.pb.authStore.isValid); 6 | 7 | if (!locals.pb.authStore.isValid) { 8 | console.log('User not authenticated, redirecting to login'); 9 | throw redirect(303, '/login'); 10 | } 11 | 12 | // You can fetch additional data for the dashboard here 13 | return { 14 | user: locals.pb.authStore.model 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/routes/login/success/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |

Welcome to your dashboard, {data.user?.email}

7 | 8 |
Logged in Successfully !!
9 | -------------------------------------------------------------------------------- /src/routes/publications/+page.svelte: -------------------------------------------------------------------------------- 1 | 108 | 109 |
110 | {#each pubs as pub (pub.title)} 111 |
112 |
113 | {pub.title} 114 | 115 | {#if pub.award} 116 |
117 | 118 |
{pub.award}
119 |
120 | {/if} 121 |
122 |
{pub.auth}
123 |
{pub.venue}
124 |
125 | {/each} 126 |
127 | -------------------------------------------------------------------------------- /src/routes/tags/[tag]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import PocketBase from 'pocketbase'; 2 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 3 | import type { PageServerLoad } from './$types'; 4 | 5 | export const load: PageServerLoad = async ({ params }) => { 6 | try { 7 | const pb = await getAuthenticatedPocketBase(); 8 | const record = await pb.collection('tags').getFirstListItem(`tag="${params.tag}"`, { 9 | expand: 'links' 10 | }); 11 | console.log('TAG =====>', record); 12 | 13 | // Extract the expanded 'links' data 14 | const posts = 15 | record.expand?.links?.map((link: any) => ({ 16 | id: link.id, 17 | title: link.title, 18 | url: link.url 19 | })) || []; 20 | 21 | return { 22 | tag: record.tag, 23 | posts: posts, 24 | error: null 25 | }; 26 | } catch (error) { 27 | console.error('Error fetching tag data:', error); 28 | return { 29 | tag: params.tag, 30 | posts: [], 31 | error: error instanceof Error ? error.message : 'An unknown error occurred' 32 | }; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/routes/tags/[tag]/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | Tag: {data.tag} 7 |
8 | 9 |
10 |
Posts tagged with {data.tag}
11 |
12 | {#each data.posts as post} 13 | 14 | {/each} 15 |
16 |
17 | -------------------------------------------------------------------------------- /src/writing/+page.server.ts: -------------------------------------------------------------------------------- 1 | import PocketBase from 'pocketbase'; 2 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 3 | 4 | const pb = await getAuthenticatedPocketBase(); 5 | 6 | export async function load({ params }) { 7 | try { 8 | const mdbase = await pb.collections.getOne('mdbase'); 9 | 10 | const records = await pb.collection('mdbase').getList(1, 10, { sort: '-created' }); 11 | const posts = Object.values(records.items).map((item) => ({ 12 | title: item.title, 13 | id: item.id, 14 | date: item.created, 15 | url: item.url 16 | })); 17 | return { posts }; 18 | } catch (error) { 19 | return { posts: [], err: error }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/writing/+page.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
Latest
16 |
17 | {#if data && data.posts.length > 0} 18 | {#each data.posts as p, id (id)} 19 |
20 | 21 |
{formatDate(p.date)}
22 |
23 | {/each} 24 | {:else} 25 |
Please upload markdown files to begin
26 | {/if} 27 |
28 | -------------------------------------------------------------------------------- /src/writing/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/src/writing/.DS_Store -------------------------------------------------------------------------------- /src/writing/[...dir]/+page.server.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/src/writing/[...dir]/+page.server.ts -------------------------------------------------------------------------------- /src/writing/[...dir]/+page.svelte: -------------------------------------------------------------------------------- 1 |
This is a directory
2 | -------------------------------------------------------------------------------- /src/writing/[...post].md/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@sveltejs/kit'; 2 | import type { RequestHandler } from './$types'; 3 | import PocketBase from 'pocketbase'; 4 | import { promises as fs } from 'fs'; 5 | import { getAuthenticatedPocketBase } from '$lib/server/auth'; 6 | 7 | const pb = await getAuthenticatedPocketBase(); 8 | 9 | async function getBacklinks(url) { 10 | const mdbaseCollection = pb.collection('mdbase'); 11 | const documentUrl = url; 12 | try { 13 | if (!documentUrl) { 14 | return new Response(JSON.stringify({ message: 'URL parameter is required' }), { 15 | status: 400 16 | }); 17 | } 18 | 19 | const documents = await mdbaseCollection.getList(1, 1, { 20 | filter: `url="${documentUrl}"`, 21 | expand: 'backlinks' 22 | }); 23 | 24 | if (documents.items.length === 0) { 25 | return new Response(JSON.stringify({ message: 'Document not found' }), { status: 404 }); 26 | } 27 | 28 | const document = documents.items[0]; 29 | 30 | const backLinks = (document.expand?.backlinks || []).map((link) => ({ 31 | id: link.id, 32 | title: link.title, 33 | url: link.url 34 | })); 35 | 36 | return backLinks; 37 | } catch (error: any) { 38 | console.error('Error in backlinks API:', error); 39 | return {}; 40 | } 41 | } 42 | 43 | async function computeGraphData(fileUrl) { 44 | const currentPage = await pb.collection('mdbase').getFirstListItem(`url="${fileUrl}"`); 45 | const relatedPages = await pb.collection('mdbase').getList(1, 50, { 46 | filter: `id ?~ "${currentPage.backlinks}" || id ?~ "${currentPage.links}"` 47 | }); 48 | 49 | // Use a Set to store unique node IDs 50 | const uniqueNodeIds = new Set([currentPage.id]); 51 | 52 | // Create nodes array with current page 53 | const nodes = [{ id: currentPage.id, label: currentPage.title, color: '#ff0000' }]; 54 | 55 | // Add related pages to nodes array, avoiding duplicates 56 | relatedPages.items.forEach((p) => { 57 | if (!uniqueNodeIds.has(p.id)) { 58 | uniqueNodeIds.add(p.id); 59 | nodes.push({ id: p.id, label: p.title, color: '#00ff00' }); 60 | } 61 | }); 62 | 63 | // Create edges array 64 | const edges = [ 65 | ...currentPage.links.map((link) => ({ from: currentPage.id, to: link })), 66 | ...currentPage.backlinks.map((backlink) => ({ from: backlink, to: currentPage.id })) 67 | ]; 68 | 69 | return { nodes, edges }; 70 | } 71 | // Main load function 72 | export async function load({ params, fetch, locals }) { 73 | try { 74 | // Step 1: Authenticate 75 | /* console.log(pb); */ 76 | console.log(params.post); 77 | const post = await pb.collection('mdbase').getFirstListItem(`url="${params.post}.md"`); 78 | const backlinks = await getBacklinks(`${params.post}.md`); 79 | const graphData = await computeGraphData(`${params.post}.md`); 80 | 81 | return { post, title: post.title, backlinks, graphData }; 82 | } catch (error) { 83 | console.error(`Failed to fetch post: ${error}`); 84 | return { message: `Failed to fetch post: ${error}` }; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/writing/[...post].md/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | {data.title} 12 |
13 | 14 |
15 | {#if data?.backlinks?.length > 0} 16 |
BACKLINKS
17 | {/if} 18 | {#each data?.backlinks || [] as bl (bl.id)} 19 | 20 | {/each} 21 |
22 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Ensure the environment variables are correctly set 4 | echo "Creating admin with email: ${POCKETBASE_ADMIN_EMAIL}" 5 | echo "PocketBase URL: ${POCKETBASE_URL}" 6 | 7 | # Start PocketBase in the background 8 | /pb/pocketbase serve --http=0.0.0.0:8080 --dir /app/db & 9 | 10 | # Wait for PocketBase to start (adjust the sleep time if necessary) 11 | sleep 5 12 | 13 | # Create the admin user using environment variables 14 | /pb/pocketbase admin create "${POCKETBASE_ADMIN_EMAIL}" "${POCKETBASE_ADMIN_PASSWORD}" --dir /app/db 15 | 16 | sleep 2 17 | 18 | # Build the SvelteKit app (requires PocketBase to be running) 19 | npm run build 20 | 21 | # # Optional: Log environment variables for debugging 22 | # echo "PocketBase URL: ${POCKETBASE_URL}" 23 | # echo "API Key: ${API_KEY}" 24 | # echo "Title: ${TITLE}" 25 | -------------------------------------------------------------------------------- /start_services.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Ensure the environment variables are correctly set 4 | echo "Creating admin with email: ${POCKETBASE_ADMIN_EMAIL}" 5 | echo "PocketBase URL: ${POCKETBASE_URL}" 6 | 7 | # Start PocketBase in the background 8 | /pb/pocketbase serve --http=0.0.0.0:8080 --dir /app/db & 9 | 10 | # Wait for PocketBase to start (adjust the sleep time if necessary) 11 | sleep 5 12 | 13 | # Create the admin user using environment variables 14 | /pb/pocketbase admin create "${POCKETBASE_ADMIN_EMAIL}" "${POCKETBASE_ADMIN_PASSWORD}" --dir /app/db 15 | 16 | sleep 2 17 | 18 | npm run build 19 | 20 | # Build the SvelteKit app (requires PocketBase to be running) 21 | node build 22 | 23 | # # Optional: Log environment variables for debugging 24 | # echo "PocketBase URL: ${POCKETBASE_URL}" 25 | # echo "API Key: ${API_KEY}" 26 | # echo "Title: ${TITLE}" 27 | -------------------------------------------------------------------------------- /static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/.DS_Store -------------------------------------------------------------------------------- /static/a11y-dark.min.css: -------------------------------------------------------------------------------- 1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! 2 | Theme: a11y-dark 3 | Author: @ericwbailey 4 | Maintainer: @ericwbailey 5 | 6 | Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css 7 | */.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}} 8 | -------------------------------------------------------------------------------- /static/a11y-light.min.css: -------------------------------------------------------------------------------- 1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! 2 | Theme: a11y-light 3 | Author: @ericwbailey 4 | Maintainer: @ericwbailey 5 | 6 | Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css 7 | */.hljs{background:#fefefe;color:#545454}.hljs-comment,.hljs-quote{color:#696969}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#d91e18}.hljs-attribute,.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#aa5d00}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:green}.hljs-section,.hljs-title{color:#007faa}.hljs-keyword,.hljs-selector-tag{color:#7928a1}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}} 8 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/favicon.png -------------------------------------------------------------------------------- /static/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'IBM Plex Sans'; 3 | src: url('/fonts/IBMPlexSans-Bold.ttf') format('truetype'); 4 | font-weight: bold; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'IBM Plex Sans'; 10 | src: url('/fonts/IBMPlexSans-BoldItalic.ttf') format('truetype'); 11 | font-weight: bold; 12 | font-style: italic; 13 | } 14 | 15 | @font-face { 16 | font-family: 'IBM Plex Sans'; 17 | src: url('/fonts/IBMPlexSans-ExtraLight.ttf') format('truetype'); 18 | font-weight: 200; 19 | font-style: normal; 20 | } 21 | 22 | @font-face { 23 | font-family: 'IBM Plex Sans'; 24 | src: url('/fonts/IBMPlexSans-ExtraLightItalic.ttf') format('truetype'); 25 | font-weight: 200; 26 | font-style: italic; 27 | } 28 | 29 | @font-face { 30 | font-family: 'IBM Plex Sans'; 31 | src: url('/fonts/IBMPlexSans-Italic.ttf') format('truetype'); 32 | font-weight: normal; 33 | font-style: italic; 34 | } 35 | 36 | @font-face { 37 | font-family: 'IBM Plex Sans'; 38 | src: url('/fonts/IBMPlexSans-Light.ttf') format('truetype'); 39 | font-weight: 300; 40 | font-style: normal; 41 | } 42 | 43 | @font-face { 44 | font-family: 'IBM Plex Sans'; 45 | src: url('/fonts/IBMPlexSans-LightItalic.ttf') format('truetype'); 46 | font-weight: 300; 47 | font-style: italic; 48 | } 49 | 50 | @font-face { 51 | font-family: 'IBM Plex Sans'; 52 | src: url('/fonts/IBMPlexSans-Medium.ttf') format('truetype'); 53 | font-weight: 500; 54 | font-style: normal; 55 | } 56 | 57 | @font-face { 58 | font-family: 'IBM Plex Sans'; 59 | src: url('/fonts/IBMPlexSans-MediumItalic.ttf') format('truetype'); 60 | font-weight: 500; 61 | font-style: italic; 62 | } 63 | 64 | @font-face { 65 | font-family: 'IBM Plex Sans'; 66 | src: url('/fonts/IBMPlexSans-Regular.ttf') format('truetype'); 67 | font-weight: normal; 68 | font-style: normal; 69 | } 70 | 71 | @font-face { 72 | font-family: 'IBM Plex Sans'; 73 | src: url('/fonts/IBMPlexSans-SemiBold.ttf') format('truetype'); 74 | font-weight: 600; 75 | font-style: normal; 76 | } 77 | 78 | @font-face { 79 | font-family: 'IBM Plex Sans'; 80 | src: url('/fonts/IBMPlexSans-SemiBoldItalic.ttf') format('truetype'); 81 | font-weight: 600; 82 | font-style: italic; 83 | } 84 | 85 | @font-face { 86 | font-family: 'IBM Plex Sans'; 87 | src: url('/fonts/IBMPlexSans-Text.ttf') format('truetype'); 88 | font-weight: 400; 89 | font-style: normal; 90 | } 91 | 92 | @font-face { 93 | font-family: 'IBM Plex Sans'; 94 | src: url('/fonts/IBMPlexSans-TextItalic.ttf') format('truetype'); 95 | font-weight: 400; 96 | font-style: italic; 97 | } 98 | 99 | @font-face { 100 | font-family: 'IBM Plex Sans'; 101 | src: url('/fonts/IBMPlexSans-Thin.ttf') format('truetype'); 102 | font-weight: 100; 103 | font-style: normal; 104 | } 105 | 106 | @font-face { 107 | font-family: 'IBM Plex Sans'; 108 | src: url('/fonts/IBMPlexSans-ThinItalic.ttf') format('truetype'); 109 | font-weight: 100; 110 | font-style: italic; 111 | } 112 | 113 | @font-face { 114 | font-family: 'Megrim'; 115 | src: url('/fonts/Megrim-Regular.ttf') format('truetype'); 116 | font-weight: normal; 117 | font-style: normal; 118 | } 119 | 120 | /* ---- IBM Plex Mono ---- */ 121 | @font-face { 122 | font-family: 'IBM Plex Mono'; 123 | font-weight: 700; 124 | font-style: normal; 125 | src: url('/fonts/IBMPlexMono-Bold.ttf') format('truetype'); 126 | } 127 | 128 | @font-face { 129 | font-family: 'IBM Plex Mono'; 130 | font-weight: 700; 131 | font-style: italic; 132 | src: url('/fonts/IBMPlexMono-BoldItalic.ttf') format('truetype'); 133 | } 134 | 135 | @font-face { 136 | font-family: 'IBM Plex Mono'; 137 | font-weight: 200; 138 | font-style: normal; 139 | src: url('/fonts/IBMPlexMono-ExtraLight.ttf') format('truetype'); 140 | } 141 | 142 | @font-face { 143 | font-family: 'IBM Plex Mono'; 144 | font-weight: 200; 145 | font-style: italic; 146 | src: url('/fonts/IBMPlexMono-ExtraLightItalic.ttf') format('truetype'); 147 | } 148 | 149 | @font-face { 150 | font-family: 'IBM Plex Mono'; 151 | font-weight: 400; 152 | font-style: italic; 153 | src: url('/fonts/IBMPlexMono-Italic.ttf') format('truetype'); 154 | } 155 | 156 | @font-face { 157 | font-family: 'IBM Plex Mono'; 158 | font-weight: 300; 159 | font-style: normal; 160 | src: url('/fonts/IBMPlexMono-Light.ttf') format('truetype'); 161 | } 162 | 163 | @font-face { 164 | font-family: 'IBM Plex Mono'; 165 | font-weight: 300; 166 | font-style: italic; 167 | src: url('/fonts/IBMPlexMono-LightItalic.ttf') format('truetype'); 168 | } 169 | 170 | @font-face { 171 | font-family: 'IBM Plex Mono'; 172 | font-weight: 500; 173 | font-style: normal; 174 | src: url('/fonts/IBMPlexMono-Medium.ttf') format('truetype'); 175 | } 176 | 177 | @font-face { 178 | font-family: 'IBM Plex Mono'; 179 | font-weight: 500; 180 | font-style: italic; 181 | src: url('/fonts/IBMPlexMono-MediumItalic.ttf') format('truetype'); 182 | } 183 | 184 | @font-face { 185 | font-family: 'IBM Plex Mono'; 186 | font-weight: 400; 187 | font-style: normal; 188 | src: url('/fonts/IBMPlexMono-Regular.ttf') format('truetype'); 189 | } 190 | 191 | @font-face { 192 | font-family: 'IBM Plex Mono'; 193 | font-weight: 600; 194 | font-style: normal; 195 | src: url('/fonts/IBMPlexMono-SemiBold.ttf') format('truetype'); 196 | } 197 | 198 | @font-face { 199 | font-family: 'IBM Plex Mono'; 200 | font-weight: 600; 201 | font-style: italic; 202 | src: url('/fonts/IBMPlexMono-SemiBoldItalic.ttf') format('truetype'); 203 | } 204 | 205 | @font-face { 206 | font-family: 'IBM Plex Mono'; 207 | font-weight: 400; 208 | font-style: normal; 209 | src: url('/fonts/IBMPlexMono-Text.ttf') format('truetype'); 210 | } 211 | 212 | @font-face { 213 | font-family: 'IBM Plex Mono'; 214 | font-weight: 400; 215 | font-style: italic; 216 | src: url('/fonts/IBMPlexMono-TextItalic.ttf') format('truetype'); 217 | } 218 | 219 | @font-face { 220 | font-family: 'IBM Plex Mono'; 221 | font-weight: 100; 222 | font-style: normal; 223 | src: url('/fonts/IBMPlexMono-Thin.ttf') format('truetype'); 224 | } 225 | 226 | @font-face { 227 | font-family: 'IBM Plex Mono'; 228 | font-weight: 100; 229 | font-style: italic; 230 | src: url('/fonts/IBMPlexMono-ThinItalic.ttf') format('truetype'); 231 | } 232 | 233 | /* Lombok */ 234 | @font-face { 235 | font-family: 'Lombok'; 236 | font-weight: 400; 237 | font-style: normal; 238 | src: url('/fonts/Lombok.otf') format('opentype'); 239 | } 240 | -------------------------------------------------------------------------------- /static/fonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/.DS_Store -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-Bold.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-BoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-ExtraLight.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-Italic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-Light.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-LightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-Medium.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-MediumItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-Regular.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-SemiBold.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-Text.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-Text.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-TextItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-TextItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-Thin.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexMono-ThinItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-Bold.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-BoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-ExtraLight.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-Italic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-Light.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-LightItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-Medium.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-MediumItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-Regular.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-SemiBold.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-Text.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-Text.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-TextItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-TextItalic.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-Thin.ttf -------------------------------------------------------------------------------- /static/fonts/IBMPlexSans-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/IBMPlexSans-ThinItalic.ttf -------------------------------------------------------------------------------- /static/fonts/Lombok.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/Lombok.otf -------------------------------------------------------------------------------- /static/fonts/Megrim-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rishikanthc/markopolis/ed4147787f896d237125e8f9872e9e6e8352f5c2/static/fonts/Megrim-Regular.ttf -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:pocketbase] 5 | command=/pb/pocketbase serve --http=0.0.0.0:8090 --dev --dir /app/db 6 | stdout_logfile=/dev/stdout 7 | stdout_logfile_maxbytes=0 8 | stderr_logfile=/dev/stderr 9 | stderr_logfile_maxbytes=0 10 | 11 | [program:node] 12 | command=/usr/local/bin/node build 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | stderr_logfile=/dev/stderr 16 | stderr_logfile_maxbytes=0 17 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | /* import adapter from '@sveltejs/adapter-auto'; */ 2 | import adapter from '@sveltejs/adapter-node'; 3 | 4 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 5 | 6 | /** @type {import('@sveltejs/kit').Config} */ 7 | const config = { 8 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 9 | // for more information about preprocessors 10 | preprocess: vitePreprocess(), 11 | 12 | kit: { 13 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 14 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 15 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 16 | adapter: adapter(), 17 | csrf: { 18 | checkOrigin: false, 19 | } 20 | } 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { fontFamily } from 'tailwindcss/defaultTheme'; 2 | import type { Config } from 'tailwindcss'; 3 | 4 | const config: Config = { 5 | darkMode: ['class'], 6 | content: ['./src/**/*.{html,js,svelte,ts}'], 7 | safelist: ['dark'], 8 | theme: { 9 | fontFamily: { 10 | sans: ['IBM Plex Sans'], 11 | body: ['IBM Plex Sans'], 12 | display: ['IBM Plex Sans'], 13 | serif: ['IBM Plex Serif'], 14 | mono: ['IBM Plex Mono'] 15 | }, 16 | container: { 17 | center: true, 18 | padding: '2rem', 19 | screens: { 20 | '2xl': '1400px' 21 | } 22 | }, 23 | extend: { 24 | colors: { 25 | carbongray: { 26 | 50: '#f4f4f4', 27 | 100: '#e0e0e0', 28 | 200: '#c6c6c6', 29 | 300: '#a8a8a8', 30 | 400: '#8d8d8d', 31 | 500: '#6f6f6f', 32 | 600: '#525252', 33 | 700: '#262626', 34 | 800: '#161616', 35 | 900: '#000000' 36 | }, 37 | carbonblue: { 38 | 50: '#ecf5ff', 39 | 100: '#d0e2ff', 40 | 200: '#a6c8ff', 41 | 300: '#77a9fe', 42 | 400: '#4589ff', 43 | 500: '#0e61fe', 44 | 600: '#0043ce', 45 | 700: '#012d9c', 46 | 800: '#001d6c', 47 | 900: '#001141' 48 | }, 49 | carbonborder: { 50 | 300: '#393939', 51 | 200: '#525252', 52 | 100: '#6f6f6f' 53 | }, 54 | border: 'hsl(var(--border) / )', 55 | input: 'hsl(var(--input) / )', 56 | ring: 'hsl(var(--ring) / )', 57 | background: 'hsl(var(--background) / )', 58 | foreground: 'hsl(var(--foreground) / )', 59 | primary: { 60 | DEFAULT: 'hsl(var(--primary) / )', 61 | foreground: 'hsl(var(--primary-foreground) / )' 62 | }, 63 | secondary: { 64 | DEFAULT: 'hsl(var(--secondary) / )', 65 | foreground: 'hsl(var(--secondary-foreground) / )' 66 | }, 67 | destructive: { 68 | DEFAULT: 'hsl(var(--destructive) / )', 69 | foreground: 'hsl(var(--destructive-foreground) / )' 70 | }, 71 | muted: { 72 | DEFAULT: 'hsl(var(--muted) / )', 73 | foreground: 'hsl(var(--muted-foreground) / )' 74 | }, 75 | accent: { 76 | DEFAULT: 'hsl(var(--accent) / )', 77 | foreground: 'hsl(var(--accent-foreground) / )' 78 | }, 79 | popover: { 80 | DEFAULT: 'hsl(var(--popover) / )', 81 | foreground: 'hsl(var(--popover-foreground) / )' 82 | }, 83 | card: { 84 | DEFAULT: 'hsl(var(--card) / )', 85 | foreground: 'hsl(var(--card-foreground) / )' 86 | } 87 | }, 88 | borderRadius: { 89 | lg: 'var(--radius)', 90 | md: 'calc(var(--radius) - 2px)', 91 | sm: 'calc(var(--radius) - 4px)' 92 | }, 93 | fontFamily: { 94 | sans: [...fontFamily.sans] 95 | } 96 | } 97 | } 98 | }; 99 | 100 | export default config; 101 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | --------------------------------------------------------------------------------