29 Commits
v1.0.0 ... main

Author SHA1 Message Date
9c06401557 up to 8 cores
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 50s
Release / build (push) Successful in 2m51s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 47s
Release / publish_head (push) Successful in 2m29s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 3m32s
2025-07-02 15:54:23 -04:00
9b1b84e5be CI Bump
All checks were successful
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 2m23s
Release / build (push) Successful in 2m59s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 3m40s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 4m56s
Release / publish_head (push) Successful in 2m40s
2025-07-02 15:33:29 -04:00
9e62a84843 no check for line
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 50s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 1m5s
Release / build (push) Successful in 2m36s
Release / publish_head (push) Successful in 2m18s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 3m17s
2025-04-10 12:08:45 -04:00
dda3be0101 Update README.md
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 43s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 58s
Release / build (push) Successful in 1m31s
Release / publish_head (push) Successful in 1m27s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 3m11s
2025-03-25 05:06:12 -04:00
3fd24c75fc Update README.md
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 44s
Release / build (push) Successful in 1m35s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 2m1s
Release / publish_head (push) Successful in 1m29s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 4m13s
2025-03-25 05:00:11 -04:00
07bb33006e Update main.py
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 14s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 17s
Release / build (push) Successful in 39s
Release / publish_head (push) Successful in 36s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 1m18s
2025-03-21 14:01:34 -04:00
aab53f1e54 Update .gitea/workflows/datadog-static-analysis.yml
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 18s
Release / build (push) Successful in 34s
Release / publish_head (push) Successful in 34s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 1m10s
2025-03-21 12:56:49 -04:00
0e6ca5859a Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 15s
Datadog Static Analysis / Datadog Static Analyzer (push) Failing after 1m8s
Release / publish_head (push) Successful in 1m15s
Release / build (push) Successful in 1m33s
2025-03-21 12:53:30 -04:00
7986ad2f88 Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 21s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 23s
Datadog Static Analysis / Datadog Static Analyzer (push) Failing after 1m20s
Release / publish_head (push) Successful in 1m19s
Release / build (push) Successful in 1m41s
2025-03-21 12:46:06 -04:00
7c4c20b3ce Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 16s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 17s
Release / build (push) Successful in 40s
Datadog Static Analysis / Datadog Static Analyzer (push) Failing after 1m8s
Release / publish_head (push) Successful in 33s
2025-03-21 12:41:14 -04:00
b407497713 nvm save me!
Some checks failed
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 15s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 16s
Release / build (push) Successful in 37s
Datadog Static Analysis / Datadog Static Analyzer (push) Failing after 58s
Release / publish_head (push) Successful in 35s
2025-03-21 12:39:16 -04:00
90d20978b1 Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 14s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Release / build (push) Successful in 37s
Release / publish_head (push) Successful in 35s
Datadog Static Analysis / Datadog Static Analyzer (push) Failing after 1m33s
2025-03-21 12:36:10 -04:00
1a26b0b3fb Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 16s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 15s
Release / build (push) Successful in 38s
Datadog Static Analysis / Datadog Static Analyzer (push) Failing after 43s
Release / publish_head (push) Successful in 38s
2025-03-21 12:20:22 -04:00
71efbfcc83 Update .gitea/workflows/datadog-static-analysis.yml
All checks were successful
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 13s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Release / build (push) Successful in 39s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 47s
Release / publish_head (push) Successful in 39s
2025-03-21 11:08:24 -04:00
27ef2d4ca3 Delete .gitea/workflows/semgrep-ce.yaml
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 14s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 14s
Release / build (push) Successful in 36s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 43s
Release / publish_head (push) Successful in 38s
2025-03-21 11:01:30 -04:00
1aa1964853 Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Static Analysis / Datadog Static Analyzer (push) Waiting to run
Release / build (push) Waiting to run
Release / publish_head (push) Waiting to run
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Has been cancelled
Datadog Secrets Scanning / Datadog Static Analyzer (push) Has been cancelled
Semgrep CE scan / semgrep-oss/scan (push) Failing after 2s
2025-03-21 11:01:17 -04:00
aae43a0001 Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 13s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 14s
Release / build (push) Successful in 36s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 37s
Semgrep CE scan / semgrep-oss/scan (push) Failing after 2s
Release / publish_head (push) Successful in 37s
2025-03-21 10:57:35 -04:00
61392e296c Update .gitea/workflows/datadog-static-analysis.yml
Some checks failed
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 14s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 14s
Datadog Static Analysis / Datadog Static Analyzer (push) Failing after 21s
Release / build (push) Successful in 34s
Semgrep CE scan / semgrep-oss/scan (push) Failing after 8s
Release / publish_head (push) Successful in 35s
2025-03-21 10:32:29 -04:00
997afcdd9e Add Semgrep Scanning
Some checks failed
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 14s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 24s
Release / build (push) Successful in 39s
Semgrep CE scan / semgrep-oss/scan (push) Failing after 21s
Release / publish_head (push) Successful in 39s
2025-03-21 10:23:09 -04:00
5a611dd893 Update config.toml
All checks were successful
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 51s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 1m15s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 24s
Release / publish_head (push) Successful in 43s
Release / build (push) Successful in 43s
2025-03-20 22:04:51 -04:00
2adde253c9 start updating docs, other changes to file upload
All checks were successful
Release / build (push) Successful in 34s
Release / publish_head (push) Successful in 33s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 10s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 20s
Release / publish_head (release) Has been skipped
Release / build (release) Successful in 37s
2025-03-16 21:22:28 -04:00
744693a5f1 Update to resolve GHSA-cpwx-vrp4-4pq7
All checks were successful
Release / build (push) Successful in 37s
Release / publish_head (push) Successful in 34s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 10s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 20s
Release / publish_head (release) Has been skipped
Release / build (release) Successful in 36s
2025-03-16 13:11:23 -04:00
102c7a2b94 more fixes
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 15s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 25s
Release / build (push) Successful in 36s
Release / publish_head (push) Successful in 34s
2025-03-15 20:08:29 -04:00
74a010e82a more fixes
All checks were successful
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 15s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 25s
Release / build (push) Successful in 36s
Release / publish_head (push) Successful in 34s
2025-03-15 20:03:58 -04:00
23ce3be362 more fixes
All checks were successful
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 14s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 16s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 24s
Release / build (push) Successful in 35s
Release / publish_head (push) Successful in 35s
2025-03-15 20:01:07 -04:00
f12247b0b1 more fixes
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 14s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 20s
Release / build (push) Successful in 33s
Release / publish_head (push) Successful in 35s
2025-03-15 19:58:45 -04:00
2605f7db37 more fixes
All checks were successful
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 14s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 20s
Release / build (push) Successful in 35s
Release / publish_head (push) Successful in 35s
2025-03-15 19:56:40 -04:00
3898a198bd add testing builds
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 15s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 24s
Release / build (push) Successful in 37s
Release / publish_head (push) Successful in 37s
2025-03-15 19:49:12 -04:00
d901f00c1f test fix image rotation
All checks were successful
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 15s
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 15s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 23s
2025-03-15 19:47:44 -04:00
32 changed files with 512 additions and 276 deletions

View File

@ -18,4 +18,4 @@ jobs:
dd_site: datadoghq.com
secrets_enabled: true
static_analysis_enabled: false
cpu_count: 2
cpu_count: 8

View File

@ -17,3 +17,25 @@ jobs:
dd_app_key: ${{ secrets.DD_APP_KEY }}
dd_site: datadoghq.com
cpu_count: 2
- name: Run Semgrep
run: |
python3 -m pip install --break-system-package semgrep
semgrep scan --sarif -o /tmp/semgrep.sarif
cat /tmp/semgrep.sarif
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"
# Download and install Node.js:
nvm install 22
# Verify the Node.js version:
node -v # Should print "v22.14.0".
nvm current # Should print "v22.14.0".
# Verify npm version:
npm -v # Should print "10.9.2".
npm install -g @datadog/datadog-ci
datadog-ci sarif upload /tmp/semgrep.sarif
env:
DD_API_KEY: ${{ secrets.DD_API_KEY }}
DD_APP_KEY: ${{ secrets.DD_APP_KEY }}
DD_SITE: datadoghq.com

View File

@ -3,6 +3,9 @@ name: Release
on:
release:
types: [published]
push:
branches:
- main
jobs:
build:
@ -42,3 +45,28 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
publish_head:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Gitea Container Registry
uses: docker/login-action@v2
with:
registry: git.dws.rip
username: ${{ github.actor }}
password: ${{ secrets.GLOBAL_KEY }}
- name: Build and push "head" image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: git.dws.rip/${{ github.repository }}:head

View File

@ -167,6 +167,7 @@ COPY . .
CMD ["python", "main.py"]
```
## Docker Compose Example
Below is an example `docker-compose.yml` file to deploy Foldsite using Docker Compose:

16
config.toml Normal file
View File

@ -0,0 +1,16 @@
[paths]
content_dir = "/home/dubey/projects/foldsite/docs/content"
templates_dir = "/home/dubey/projects/foldsite/docs/templates"
styles_dir = "/home/dubey/projects/foldsite/docs/styles"
[server]
listen_address = "0.0.0.0"
listen_port = 8080
enable_admin_browser = false
admin_password = "password"
max_threads = 4
debug = false
access_log = true

View File

@ -0,0 +1,35 @@
# Configuration
## Configuration file
Foldsite is configured using a TOML file (default: `config.toml`). This file specifies paths to content, templates, and styles, as well as server settings.
Example `config.toml`:
```toml
[paths]
content_dir = "/home/myuser/site/content"
templates_dir = "/home/myuser/site/themes/cobalt/templates"
styles_dir = "/home/myuser/site/themes/cobalt/styles"
[server]
listen_address = "127.0.0.1"
listen_port = 8080
debug = false
access_log = true
max_threads = 4
admin_browser = false
admin_password = "your_admin_password"
```
## Server Settings
- **`listen_address`**: The IP address the server listens on (default: `127.0.0.1`).
- **`listen_port`**: The port the server listens on (default: `8080`).
- **`debug`**: Enables or disables debug mode (default: `false`). In debug mode, the server will automatically reload when code changes are detected, and more detailed error messages will be shown.
- **`access_log`**: Enables or disables access logging (default: `true`). If enabled, access logs will be written to standard output.
- **`max_threads`**: The maximum number of threads to use for handling requests (default: `4`). This setting directly impacts the concurrency of the server.
- **`admin_browser`**: Enables or disables the built-in file manager (default: `false`).
- **`admin_password`**: Sets the password for the file manager. Required if `admin_browser` is `true`.
The `Configuration` class (`/foldsite/src/config/config.py`) is responsible for loading and parsing this configuration file. It also sets global variables (`CONTENT_DIR`, `TEMPLATES_DIR`, `STYLES_DIR`) for easy access to these directories throughout the application. Errors are raised if the config file is missing, invalid, or lacks required sections (like `paths` or `server`).

View File

@ -0,0 +1,56 @@
## Rendering Process
The `RouteManager` class (`/foldsite/src/routes/routes.py`) and `render_page` function (`/foldsite/src/rendering/renderer.py`) are central to the rendering process.
### How Foldsite Determines File Types
The `determine_type` function (in `renderer.py`) is crucial for figuring out how to handle a given file or directory. It examines file extensions and directory contents to classify files into broad categories (defined in `GENERIC_FILE_MAPPING` in `/foldsite/src/rendering/__init__.py`):
* **`document`**: Files with extensions like `.md`, `.txt`, and `.html`.
* **`image`**: Files with extensions like `.png`, `.jpg`, `.jpeg`, `.gif`, and `.svg`.
* **`directory`**: Directories. If a directory contains files, the most common file extension within that directory is used to infer the directory's "type".
* **`other`**: Files that don't match any of the above categories.
* **`multimedia`**: This is a combination that contains `image`.
### Template Search
When a request comes in, Foldsite searches for an appropriate template in the `templates` directory. The search logic is implemented in `render_page` and follows a specific order, prioritizing more specific templates:
1. **Exact Path Match:** If a template exists with the exact same path relative to the `templates` directory as the requested content file (but with a `.html` extension), it's used. For example, if the request is for `/about/team.md`, and a template exists at `templates/about/team.md.html`, that template will be used.
2. **Folder-Specific Template:** If the requested path is a directory, Foldsite looks for a `__folder.html` template within that directory. For example, if the request is for `/blog/`, and `templates/blog/__folder.html` exists, it will be used.
3. **Type and Extension-Specific Templates:** Foldsite searches for templates named `__{type}.{extension}.html` within the requested directory and its parent directories, moving upwards. For instance, if requesting `/blog/post1.md`, it would look for:
* `templates/blog/__file.md.html`
* `templates/__file.md.html`
4. **Type and Category-Specific Templates:** Similar to the above, but searches for `__{type}.{category}.html`. If requesting an image at `/images/logo.png`, it looks for:
* `templates/images/__file.image.html`
* `templates/__file.image.html`
5. **Base Template:** Finally, if no other template is found, `templates/base.html` is used as a fallback. This template *must* exist; otherwise, an exception is raised.
### Style Search
CSS styles are searched similarly to templates, prioritizing specificity:
1. **Exact Path Match:** A CSS file with the exact same path as the requested content file (relative to the `styles` directory) is used. For example, `/about/team.md` would look for `styles/about/team.md.css`.
2. **Type and Extension-Specific Styles:** Searches for `__{type}.{extension}.css` in the requested directory and its parent directories. For example, `/blog/post1.md` would look for:
* `styles/blog/__file.md.css`
* `styles/__file.md.css`
3. **Type and Category-Specific Styles:** Similar to the above, but searches for `__{type}.{category}.css`.
* `styles/images/__file.image.css`
* `styles/__file.image.css`
4. **Base Style:** `styles/base.css` is always included.
The discovered styles are added to the `styles` variable, which is passed to the Jinja2 template. The order ensures that more specific styles override general ones.
### Error Handling
The `render_error_page` function (in `renderer.py`) handles errors. If a requested resource is not found (404 error) or if an exception occurs during processing, this function is called. It looks for a template named `__error.html` in the `templates` directory. If found, it's used to render the error page; otherwise, a default error page is generated. The error code, message, and description are passed to the template.

View File

@ -1,3 +0,0 @@
# Added Tools for the Template
Foldsite provides additional tools for templates, such as functions to get sibling content files, text document previews, and folder contents.

View File

@ -1,19 +0,0 @@
# Configuration
The configuration file is written in TOML format and contains various settings for the application. Below is an example configuration file (`config.toml`):
```toml
[paths]
content_dir = "example/content"
templates_dir = "templates"
styles_dir = "styles"
[server]
listen_address = "127.0.0.1"
listen_port = 8080
debug = false
access_log = true
max_threads = 4
admin_browser = false
admin_password = "your_admin_password"
```

View File

@ -1,12 +0,0 @@
# Deployment
To deploy Foldsite, you can use Docker. Below is an example Dockerfile:
```dockerfile
FROM python:3.13.2-bookworm
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
```

View File

@ -1,16 +0,0 @@
# Docker Compose Example
Below is an example `docker-compose.yml` file to deploy Foldsite using Docker Compose:
```yaml
version: '3.8'
services:
foldsite:
build: .
ports:
- "8080:8080"
volumes:
- .:/app
environment:
- CONFIG_PATH=config.toml
```

View File

@ -1,39 +0,0 @@
# Example Usages for Tools and Types
### Example Usage of `get_sibling_content_files`
```html
<ul>
{% for file in get_sibling_content_files('path/to/directory') %}
<li>{{ file[0] }} - {{ file[1] }}</li>
{% endfor %}
</ul>
```
### Example Usage of `get_text_document_preview`
```html
<div>
{{ get_text_document_preview('path/to/document.md') }}
</div>
```
### Example Usage of `get_sibling_content_folders`
```html
<ul>
{% for folder in get_sibling_content_folders('path/to/directory') %}
<li>{{ folder[0] }} - {{ folder[1] }}</li>
{% endfor %}
</ul>
```
### Example Usage of `get_folder_contents`
```html
<ul>
{% for item in get_folder_contents('path/to/directory') %}
<li>{{ item.name }} - {{ item.path }}</li>
{% endfor %}
</ul>
```

View File

@ -1,23 +0,0 @@
# How a Template is Written
Templates are written in HTML and use Jinja2 syntax for dynamic content. Below is an example template (`base.html`):
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='base.css') }}">
{% for style in styles %}
<link rel="stylesheet" href="{{ style }}">
{% endfor %}
</head>
<body>
<div class="content">
{{ content }}
</div>
</body>
</html>
```

View File

@ -1,20 +1,3 @@
# Foldsite Documentation
Welcome to the Foldsite documentation. This site will guide you through the setup, configuration, and usage of Foldsite.
## Table of Contents
1. [Introduction](introduction.md)
2. [Configuration](configuration.md)
3. [Template Setup](template-setup.md)
4. [Site Setup](site-setup.md)
5. [Style Setup](style-setup.md)
6. [Template and Style Search](template-style-search.md)
7. [How a Template is Written](how-template-written.md)
8. [Jinja Primer](jinja-primer.md)
9. [Added Tools for the Template](added-tools.md)
10. [Tool Input and Return Types](tool-input-return-types.md)
11. [Example Usages for Tools and Types](example-usages.md)
12. [Deployment](deployment.md)
13. [Docker Compose Example](docker-compose-example.md)
14. [Folder Layout](folder-layout.md)
Foldsite acts as a dynamic site generator. It takes content primarily from Markdown files, combines it with HTML templates, applies CSS styles, and serves the resulting pages. It supports features like image thumbnails, Markdown rendering with frontmatter, and a built-in file manager for administrative tasks. Foldsite is dynamic in the sense that content is rendered on demand, rather than generating static HTML up-front.

View File

@ -1,5 +0,0 @@
# Introduction
Foldsite is a dynamic site generator built with Python and Flask. It allows you to create and manage a website using Markdown content, HTML templates, and CSS styles.
This documentation will guide you through the setup, configuration, and usage of Foldsite.

View File

@ -1,8 +0,0 @@
# Jinja Primer
Jinja2 is a templating engine for Python. It allows you to include dynamic content in your HTML templates. Below are some basic Jinja2 syntax examples:
- Variables: `{{ variable }}`
- Loops: `{% for item in list %} ... {% endfor %}`
- Conditionals: `{% if condition %} ... {% endif %}`
- Includes: `{% include 'template.html' %}`

View File

@ -1,3 +0,0 @@
# Site Setup
The site content is stored in the `content` directory. Each Markdown file represents a page on your site. The directory structure of the `content` directory determines the URL structure of your site.

View File

@ -1,3 +0,0 @@
# Style Setup
Styles are CSS files that define the appearance of your web pages. They are stored in the `styles` directory. You can create specific styles for different types of content and categories.

View File

@ -1,3 +0,0 @@
# Template Setup
Templates are HTML files that define the structure of your web pages. They are stored in the `templates` directory. Each template can include other templates and use Jinja2 syntax for dynamic content.

View File

@ -1,3 +0,0 @@
# Template and Style Search
Templates and styles are searched in a specific order to apply the most specific styles first, followed by more general styles, and finally the base style.

View File

@ -1,13 +0,0 @@
# Tool Input and Return Types
### `get_sibling_content_files(path: str) -> list`
Returns a list of sibling content files in the specified directory.
### `get_text_document_preview(path: str) -> str`
Generates a preview of the text document located at the given path.
### `get_sibling_content_folders(path: str) -> list`
Returns a list of sibling content folders within a specified directory.
### `get_folder_contents(path: str) -> list`
Retrieves the contents of a folder and returns a list of `TemplateFile` objects.

View File

@ -3,7 +3,12 @@ article {
margin: 0 auto;
}
article h1, article h2, article h3, article h4, article h5, article h6 {
article h1,
article h2,
article h3,
article h4,
article h5,
article h6 {
margin-top: 1.5rem;
}
@ -11,14 +16,8 @@ article p {
margin: 1rem 0;
}
article pre {
background: #f4f4f4;
padding: 1rem;
overflow-x: auto;
}
article code {
background: #f4f4f4;
background: var(--code-background-color);
padding: 0.2rem 0.4rem;
border-radius: 3px;
}

View File

@ -1,50 +1,280 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
line-height: 1;
}
header {
background: #333;
color: #fff;
padding: 1rem 0;
text-align: center;
}
header h1 {
margin: 0;
}
nav ul {
ol,
ul {
list-style: none;
padding: 0;
}
nav ul li {
display: inline;
margin-right: 1rem;
blockquote,
q {
quotes: none;
}
nav ul li a {
color: #fff;
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
@property --font-color {
syntax: "<color>";
inherits: true;
initial-value: oklch(25.11% 0.006 258.36);
}
@property --background-color {
syntax: "<color>";
inherits: true;
initial-value: #F6F0F0;
}
@property --code-background-color {
syntax: "<color>";
inherits: true;
initial-value: #c7c1c1;
}
@property --hover-color {
syntax: "<color>";
inherits: true;
initial-value: #A4B465;
}
@property --url-color {
syntax: "<color>";
inherits: true;
initial-value: #626F47;
}
@media (prefers-color-scheme: dark) {
:root {
--font-color: oklch(91.87% 0.003 264.54);
--background-color: #29261f;
--hover-color: #626F47;
--url-color: #A4B465;
--code-background-color: #3d392e;
}
}
body {
font-family: "Open Sans", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
font-variation-settings:
"wdth" 100;
display: flex;
justify-content: center;
background-color: var(--background-color);
color: var(--font-color);
}
a {
color: var(--url-color);
text-decoration: none;
transition: all 0.25s ease-in-out;
}
nav ul li a:hover {
text-decoration: underline;
a:hover {
color: var(--hover-color);
transition: all 0.25s ease-in-out;
}
main {
padding: 2rem;
a:visited {
color: #C14600;
transition: all 0.25s ease-in-out;
}
footer {
background: #333;
color: #fff;
text-align: center;
padding: 1rem 0;
position: fixed;
bottom: 0;
width: 100%;
a:visited:hover {
color: var(--hover-color);
transition: all 0.25s ease-in-out;
}
@supports (font-size-adjust: 1) {
.content {
font-size-adjust: 0.5;
}
}
ul {
list-style: square;
}
li {
line-height: 160%;
margin-bottom: 0.5rem;
}
.content {
line-height: calc(1ex / 0.32);
text-rendering: optimizeLegibility;
max-width: 80ch;
padding-left: 1rem;
}
.content h1 {
font-size: 2.5em;
line-height: calc(1ex / 0.42);
margin: calc(1ex / 0.42) 0;
}
.content h2 {
font-size: 2em;
line-height: calc(1ex / 0.42);
margin: calc(1ex / 0.42) 0;
}
.content h3 {
font-size: 1.75em;
line-height: calc(1ex / 0.38);
margin: calc(1ex / 0.38) 0;
}
.content h4 {
font-size: 1.5em;
line-height: calc(1ex / 0.37);
margin: calc(1ex / 0.37) 0;
}
.content p {
font-size: 1em;
line-height: calc(1ex / 0.32);
margin: calc(1ex / 0.32) 0;
text-align: justify;
hyphens: auto;
}
.sidebar {
padding-top: 4rem;
line-height: calc(1ex / 0.32);
text-rendering: optimizeLegibility;
}
.holder {
display: flex;
flex-direction: row;
margin: auto;
}

View File

@ -1,4 +1,7 @@
<article>
{{ content|safe }}
</article>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dockerfile.min.js"></script>
<script>hljs.highlightAll();</script>

View File

@ -1,42 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Outfit:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='base.css') }}">
{% for style in styles %}
<link rel="stylesheet" href="{{ style }}">
<link rel="stylesheet" href="/styles{{ style }}" type="text/css">
{% endfor %}
</head>
<body>
<header>
<h1>Foldsite Documentation</h1>
<nav>
<div class="holder">
<div class="sidebar"> <!-- Changed <sidebar> to <div> -->
<ul>
<li><a href="{{ url_for('default_route', path='index.md') }}">Home</a></li>
<li><a href="{{ url_for('default_route', path='introduction.md') }}">Introduction</a></li>
<li><a href="{{ url_for('default_route', path='configuration.md') }}">Configuration</a></li>
<li><a href="{{ url_for('default_route', path='template-setup.md') }}">Template Setup</a></li>
<li><a href="{{ url_for('default_route', path='site-setup.md') }}">Site Setup</a></li>
<li><a href="{{ url_for('default_route', path='style-setup.md') }}">Style Setup</a></li>
<li><a href="{{ url_for('default_route', path='template-style-search.md') }}">Template and Style Search</a></li>
<li><a href="{{ url_for('default_route', path='how-template-written.md') }}">How a Template is Written</a></li>
<li><a href="{{ url_for('default_route', path='jinja-primer.md') }}">Jinja Primer</a></li>
<li><a href="{{ url_for('default_route', path='added-tools.md') }}">Added Tools</a></li>
<li><a href="{{ url_for('default_route', path='tool-input-return-types.md') }}">Tool Input and Return Types</a></li>
<li><a href="{{ url_for('default_route', path='example-usages.md') }}">Example Usages</a></li>
<li><a href="{{ url_for('default_route', path='deployment.md') }}">Deployment</a></li>
<li><a href="{{ url_for('default_route', path='docker-compose-example.md') }}">Docker Compose Example</a></li>
<li><a href="{{ url_for('default_route', path='folder-layout.md') }}">Folder Layout</a></li>
<li><a href="/">Home</a></li>
<hr>
{% for f in get_folder_contents() %}
{% if not f.is_dir %}
{% if f.proper_name == "index" %}
{% else %}
<li><a href="/{{ f.path }}">{{ f.proper_name }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
</nav>
</header>
<main>
</div>
<div class="content"> <!-- <main> tag remains the same -->
{{ content|safe }}
</main>
<footer>
<p>&copy; 2023 Foldsite</p>
</footer>
<div class="footer">
<p>&copy; DWS</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -10,6 +10,9 @@ AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
PASSWORD = "YiaysZ4g8QX1R8R"
AWS_ACCESS_KEY_ID = "AIDAJQABLZS4A3QDU576"
import hashlib
hashlib.sha1(1)
def main():
parser = create_parser()

View File

@ -8,6 +8,7 @@ dependencies = [
"bs4>=0.0.2",
"flask>=3.1.0",
"gunicorn>=23.0.0",
"jinja2>=3.1.6",
"mistune>=3.1.1",
"pillow>=10.4.0",
"python-frontmatter>=1.1.0",

View File

@ -16,8 +16,10 @@ gunicorn==23.0.0
# via foldsite (pyproject.toml)
itsdangerous==2.2.0
# via flask
jinja2==3.1.5
# via flask
jinja2==3.1.6
# via
# foldsite (pyproject.toml)
# flask
markdown-it-py==3.0.0
# via rich
markupsafe==3.0.2

View File

@ -30,15 +30,16 @@ def generate_thumbnail(image_path, resize_percent, min_width, max_width):
try:
exif = img.info['exif']
orientation = img._getexif().get(0x0112, 1) # 0x0112 is the EXIF orientation tag
print(f"EXIF orientation: {orientation}, {image_path}")
if orientation == 3:
img = img.rotate(180, expand=True)
elif orientation == 6:
img = img.rotate(270, expand=True)
img = img.rotate(0, expand=True)
elif orientation == 8:
img = img.rotate(90, expand=True)
img = img.rotate(180, expand=True)
except (AttributeError, KeyError, IndexError):
# cases: image don't have getexif
exif = None
exif = b""
# Save the thumbnail to a BytesIO object
thumbnail_io = BytesIO()

View File

@ -40,6 +40,7 @@ def create_filemanager_blueprint(base_dir, url_prefix='/files', auth_password=No
return redirect(next_url)
else:
flash("Incorrect password")
#no-dd-sa
return render_template_string('''
<!doctype html>
<html>
@ -149,10 +150,10 @@ def create_filemanager_blueprint(base_dir, url_prefix='/files', auth_password=No
</ul>
<button onclick="bulkCut()">Bulk Cut Selected</button>
<hr>
<h2>Upload File</h2>
<h2>Upload File(s)</h2>
<form action="{{ url_for('filemanager.upload') }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="path" value="{{ rel_path }}">
<input type="file" name="file">
<input type="file" name="file" multiple>
<input type="submit" value="Upload">
</form>
<hr>
@ -276,11 +277,13 @@ def create_filemanager_blueprint(base_dir, url_prefix='/files', auth_password=No
return "Invalid path", 400
if not os.path.isdir(abs_path):
return "Not a directory", 400
file = request.files.get('file')
if file:
files = request.files.getlist('file')
if files:
for file in files:
if file and file.filename:
filename = secure_filename(file.filename)
file.save(os.path.join(abs_path, filename))
flash("Uploaded successfully")
flash("Uploaded files successfully")
return redirect(url_for('filemanager.index', path=rel_path))
@filemanager.route('/rename', methods=['POST'])

20
uv.lock generated
View File

@ -81,6 +81,7 @@ dependencies = [
{ name = "bs4" },
{ name = "flask" },
{ name = "gunicorn" },
{ name = "jinja2" },
{ name = "mistune" },
{ name = "pillow" },
{ name = "python-frontmatter" },
@ -94,6 +95,7 @@ requires-dist = [
{ name = "bs4", specifier = ">=0.0.2" },
{ name = "flask", specifier = ">=3.1.0" },
{ name = "gunicorn", specifier = ">=23.0.0" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "mistune", specifier = ">=3.1.1" },
{ name = "pillow", specifier = ">=10.4.0" },
{ name = "python-frontmatter", specifier = ">=1.1.0" },
@ -125,14 +127,14 @@ wheels = [
[[package]]
name = "jinja2"
version = "3.1.5"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 }
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 },
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 },
]
[[package]]
@ -186,11 +188,11 @@ wheels = [
[[package]]
name = "mistune"
version = "3.1.1"
version = "3.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/1d/6b2b634e43bacc3239006e61800676aa6c41ac1836b2c57497ed27a7310b/mistune-3.1.1.tar.gz", hash = "sha256:e0740d635f515119f7d1feb6f9b192ee60f0cc649f80a8f944f905706a21654c", size = 94645 }
sdist = { url = "https://files.pythonhosted.org/packages/80/f7/f6d06304c61c2a73213c0a4815280f70d985429cda26272f490e42119c1a/mistune-3.1.2.tar.gz", hash = "sha256:733bf018ba007e8b5f2d3a9eb624034f6ee26c4ea769a98ec533ee111d504dff", size = 94613 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/02/c66bdfdadbb021adb642ca4e8a5ed32ada0b4a3e4b39c5d076d19543452f/mistune-3.1.1-py3-none-any.whl", hash = "sha256:02106ac2aa4f66e769debbfa028509a275069dcffce0dfa578edd7b991ee700a", size = 53696 },
{ url = "https://files.pythonhosted.org/packages/12/92/30b4e54c4d7c48c06db61595cffbbf4f19588ea177896f9b78f0fbe021fd/mistune-3.1.2-py3-none-any.whl", hash = "sha256:4b47731332315cdca99e0ded46fc0004001c1299ff773dfb48fbe1fd226de319", size = 53696 },
]
[[package]]
@ -305,7 +307,7 @@ wheels = [
[[package]]
name = "typer"
version = "0.15.1"
version = "0.15.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
@ -313,9 +315,9 @@ dependencies = [
{ name = "shellingham" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 }
sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 },
{ url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 },
]
[[package]]