Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
744693a5f1 | |||
102c7a2b94 | |||
74a010e82a | |||
23ce3be362 | |||
f12247b0b1 | |||
2605f7db37 | |||
3898a198bd | |||
d901f00c1f | |||
fc211edc77 | |||
df0610284d |
@ -3,6 +3,9 @@ name: Release
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -24,6 +27,7 @@ jobs:
|
|||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}.{{patch}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
type=sha,format=long
|
type=sha,format=long
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
|
|
||||||
@ -41,3 +45,28 @@ jobs:
|
|||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
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
|
185
README.md
185
README.md
@ -0,0 +1,185 @@
|
|||||||
|
# Foldsite
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Foldsite](#foldsite)
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Template Setup](#template-setup)
|
||||||
|
- [Site Setup](#site-setup)
|
||||||
|
- [Style Setup](#style-setup)
|
||||||
|
- [Template and Style Search](#template-and-style-search)
|
||||||
|
- [How a Template is Written](#how-a-template-is-written)
|
||||||
|
- [Jinja Primer](#jinja-primer)
|
||||||
|
- [Added Tools for the Template](#added-tools-for-the-template)
|
||||||
|
- [Tool Input and Return Types](#tool-input-and-return-types)
|
||||||
|
- [`get_sibling_content_files(path: str) -> list`](#get_sibling_content_filespath-str---list)
|
||||||
|
- [`get_text_document_preview(path: str) -> str`](#get_text_document_previewpath-str---str)
|
||||||
|
- [`get_sibling_content_folders(path: str) -> list`](#get_sibling_content_folderspath-str---list)
|
||||||
|
- [`get_folder_contents(path: str) -> list`](#get_folder_contentspath-str---list)
|
||||||
|
- [Example Usages for Tools and Types](#example-usages-for-tools-and-types)
|
||||||
|
- [Example Usage of `get_sibling_content_files`](#example-usage-of-get_sibling_content_files)
|
||||||
|
- [Example Usage of `get_text_document_preview`](#example-usage-of-get_text_document_preview)
|
||||||
|
- [Example Usage of `get_sibling_content_folders`](#example-usage-of-get_sibling_content_folders)
|
||||||
|
- [Example Usage of `get_folder_contents`](#example-usage-of-get_folder_contents)
|
||||||
|
- [Deployment](#deployment)
|
||||||
|
- [Docker Compose Example](#docker-compose-example)
|
||||||
|
|
||||||
|
## 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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' %}`
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
@ -1,7 +1,7 @@
|
|||||||
[paths]
|
[paths]
|
||||||
content_dir = "/home/dubey/projects/foldsite/example/content"
|
content_dir = "/home/dubey/projects/foldsite/docs/content"
|
||||||
templates_dir = "/home/dubey/projects/foldsite/example/templates"
|
templates_dir = "/home/dubey/projects/foldsite/docs/templates"
|
||||||
styles_dir = "/home/dubey/projects/foldsite/example/styles"
|
styles_dir = "/home/dubey/projects/foldsite/docs/styles"
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
listen_address = "0.0.0.0"
|
listen_address = "0.0.0.0"
|
||||||
|
3
docs/content/added-tools.md
Normal file
3
docs/content/added-tools.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 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.
|
19
docs/content/configuration.md
Normal file
19
docs/content/configuration.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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"
|
||||||
|
```
|
12
docs/content/deployment.md
Normal file
12
docs/content/deployment.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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"]
|
||||||
|
```
|
16
docs/content/docker-compose-example.md
Normal file
16
docs/content/docker-compose-example.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 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
|
||||||
|
```
|
39
docs/content/example-usages.md
Normal file
39
docs/content/example-usages.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 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>
|
||||||
|
```
|
0
docs/content/folder-layout.md
Normal file
0
docs/content/folder-layout.md
Normal file
23
docs/content/how-template-written.md
Normal file
23
docs/content/how-template-written.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 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>
|
||||||
|
```
|
20
docs/content/index.md
Normal file
20
docs/content/index.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# 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)
|
5
docs/content/introduction.md
Normal file
5
docs/content/introduction.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 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.
|
8
docs/content/jinja-primer.md
Normal file
8
docs/content/jinja-primer.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 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' %}`
|
3
docs/content/site-setup.md
Normal file
3
docs/content/site-setup.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 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.
|
3
docs/content/style-setup.md
Normal file
3
docs/content/style-setup.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 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.
|
3
docs/content/template-setup.md
Normal file
3
docs/content/template-setup.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 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.
|
3
docs/content/template-style-search.md
Normal file
3
docs/content/template-style-search.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 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.
|
13
docs/content/tool-input-return-types.md
Normal file
13
docs/content/tool-input-return-types.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# 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.
|
24
docs/styles/__file.md.css
Normal file
24
docs/styles/__file.md.css
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
article {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
article h1, article h2, article h3, article h4, article h5, article h6 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article p {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article pre {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
article code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
50
docs/styles/base.css
Normal file
50
docs/styles/base.css
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
padding: 1rem 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li {
|
||||||
|
display: inline;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li a {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul li a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem 0;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
4
docs/templates/__file.md.html
vendored
Normal file
4
docs/templates/__file.md.html
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<article>
|
||||||
|
{{ content|safe }}
|
||||||
|
</article>
|
||||||
|
|
42
docs/templates/base.html
vendored
Normal file
42
docs/templates/base.html
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<!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>
|
||||||
|
<header>
|
||||||
|
<h1>Foldsite Documentation</h1>
|
||||||
|
<nav>
|
||||||
|
<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>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{{ content|safe }}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© 2023 Foldsite</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -8,6 +8,7 @@ dependencies = [
|
|||||||
"bs4>=0.0.2",
|
"bs4>=0.0.2",
|
||||||
"flask>=3.1.0",
|
"flask>=3.1.0",
|
||||||
"gunicorn>=23.0.0",
|
"gunicorn>=23.0.0",
|
||||||
|
"jinja2>=3.1.6",
|
||||||
"mistune>=3.1.1",
|
"mistune>=3.1.1",
|
||||||
"pillow>=10.4.0",
|
"pillow>=10.4.0",
|
||||||
"python-frontmatter>=1.1.0",
|
"python-frontmatter>=1.1.0",
|
||||||
|
@ -16,8 +16,10 @@ gunicorn==23.0.0
|
|||||||
# via foldsite (pyproject.toml)
|
# via foldsite (pyproject.toml)
|
||||||
itsdangerous==2.2.0
|
itsdangerous==2.2.0
|
||||||
# via flask
|
# via flask
|
||||||
jinja2==3.1.5
|
jinja2==3.1.6
|
||||||
# via flask
|
# via
|
||||||
|
# foldsite (pyproject.toml)
|
||||||
|
# flask
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
# via rich
|
# via rich
|
||||||
markupsafe==3.0.2
|
markupsafe==3.0.2
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from functools import cache
|
from functools import lru_cache
|
||||||
|
|
||||||
@cache
|
|
||||||
def generate_thumbnail(image_path, resize_percent, min_width):
|
|
||||||
# Generate a unique key based on the image path, resize percentage, and minimum width
|
|
||||||
key = f"{image_path}_{resize_percent}_{min_width}"
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=512)
|
||||||
|
def generate_thumbnail(image_path, resize_percent, min_width, max_width):
|
||||||
# Open the image file
|
# Open the image file
|
||||||
with Image.open(image_path) as img:
|
with Image.open(image_path) as img:
|
||||||
# Calculate the new size based on the resize percentage
|
# Calculate the new size based on the resize percentage
|
||||||
@ -20,27 +17,34 @@ def generate_thumbnail(image_path, resize_percent, min_width):
|
|||||||
new_width = min_width
|
new_width = min_width
|
||||||
new_height = int(new_height * scale_factor)
|
new_height = int(new_height * scale_factor)
|
||||||
|
|
||||||
|
# Ensure the maximum width is not exceeded
|
||||||
|
if new_width > max_width:
|
||||||
|
scale_factor = max_width / new_width
|
||||||
|
new_width = max_width
|
||||||
|
new_height = int(new_height * scale_factor)
|
||||||
|
|
||||||
# Resize the image while maintaining the aspect ratio
|
# Resize the image while maintaining the aspect ratio
|
||||||
img.thumbnail((new_width, new_height))
|
img.thumbnail((new_width, new_height))
|
||||||
|
|
||||||
# Rotate the image based on the EXIF orientation tag
|
# Rotate the image based on the EXIF orientation tag
|
||||||
try:
|
try:
|
||||||
exif = img._getexif()
|
exif = img.info['exif']
|
||||||
orientation = exif.get(0x0112, 1) # 0x0112 is the EXIF orientation tag
|
orientation = img._getexif().get(0x0112, 1) # 0x0112 is the EXIF orientation tag
|
||||||
|
print(f"EXIF orientation: {orientation}, {image_path}")
|
||||||
if orientation == 3:
|
if orientation == 3:
|
||||||
img = img.rotate(180, expand=True)
|
img = img.rotate(180, expand=True)
|
||||||
elif orientation == 6:
|
elif orientation == 6:
|
||||||
img = img.rotate(270, expand=True)
|
img = img.rotate(0, expand=True)
|
||||||
elif orientation == 8:
|
elif orientation == 8:
|
||||||
img = img.rotate(90, expand=True)
|
img = img.rotate(180, expand=True)
|
||||||
except (AttributeError, KeyError, IndexError):
|
except (AttributeError, KeyError, IndexError):
|
||||||
# cases: image don't have getexif
|
# cases: image don't have getexif
|
||||||
pass
|
exif = b""
|
||||||
|
|
||||||
# Save the thumbnail to a BytesIO object
|
# Save the thumbnail to a BytesIO object
|
||||||
thumbnail_io = BytesIO()
|
thumbnail_io = BytesIO()
|
||||||
img_format = img.format if img.format in ["JPEG", "JPG", "PNG"] else "JPEG"
|
img_format = img.format if img.format in ["JPEG", "JPG", "PNG"] else "JPEG"
|
||||||
img.save(thumbnail_io, format=img_format)
|
img.save(thumbnail_io, format=img_format, exif=exif)
|
||||||
thumbnail_io.seek(0)
|
thumbnail_io.seek(0)
|
||||||
|
|
||||||
return (thumbnail_io.getvalue(), img_format)
|
return (thumbnail_io.getvalue(), img_format)
|
@ -1,9 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from src.config.config import Configuration
|
from src.config.config import Configuration
|
||||||
from src.rendering.renderer import render_page, render_error_page
|
from src.rendering.renderer import render_page, render_error_page
|
||||||
from flask import send_file
|
from flask import send_file, request
|
||||||
from src.rendering.image import generate_thumbnail
|
from src.rendering.image import generate_thumbnail
|
||||||
from functools import lru_cache
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
@ -114,13 +113,15 @@ class RouteManager:
|
|||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
# Check to see if the file is an image, if it is, render a thumbnail
|
# Check to see if the file is an image, if it is, render a thumbnail
|
||||||
if file_path.suffix.lower() in [".jpg", ".jpeg", ".png", ".gif"]:
|
if file_path.suffix.lower() in [".jpg", ".jpeg", ".png", ".gif"]:
|
||||||
|
max_width = request.args.get("max_width", default=2048, type=int)
|
||||||
thumbnail_bytes, img_format = generate_thumbnail(
|
thumbnail_bytes, img_format = generate_thumbnail(
|
||||||
str(file_path), 10, 2048
|
str(file_path), 10, 2048, max_width
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
thumbnail_bytes,
|
thumbnail_bytes,
|
||||||
200,
|
200,
|
||||||
{"Content-Type": f"image/{img_format.lower()}"},
|
{"Content-Type": f"image/{img_format.lower()}",
|
||||||
|
"cache-control": "public, max-age=31536000"},
|
||||||
)
|
)
|
||||||
return send_file(file_path)
|
return send_file(file_path)
|
||||||
else:
|
else:
|
||||||
|
20
uv.lock
generated
20
uv.lock
generated
@ -81,6 +81,7 @@ dependencies = [
|
|||||||
{ name = "bs4" },
|
{ name = "bs4" },
|
||||||
{ name = "flask" },
|
{ name = "flask" },
|
||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
|
{ name = "jinja2" },
|
||||||
{ name = "mistune" },
|
{ name = "mistune" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
{ name = "python-frontmatter" },
|
{ name = "python-frontmatter" },
|
||||||
@ -94,6 +95,7 @@ requires-dist = [
|
|||||||
{ name = "bs4", specifier = ">=0.0.2" },
|
{ name = "bs4", specifier = ">=0.0.2" },
|
||||||
{ name = "flask", specifier = ">=3.1.0" },
|
{ name = "flask", specifier = ">=3.1.0" },
|
||||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||||
|
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||||
{ name = "mistune", specifier = ">=3.1.1" },
|
{ name = "mistune", specifier = ">=3.1.1" },
|
||||||
{ name = "pillow", specifier = ">=10.4.0" },
|
{ name = "pillow", specifier = ">=10.4.0" },
|
||||||
{ name = "python-frontmatter", specifier = ">=1.1.0" },
|
{ name = "python-frontmatter", specifier = ">=1.1.0" },
|
||||||
@ -125,14 +127,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.5"
|
version = "3.1.6"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "markupsafe" },
|
{ 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 = [
|
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]]
|
[[package]]
|
||||||
@ -186,11 +188,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mistune"
|
name = "mistune"
|
||||||
version = "3.1.1"
|
version = "3.1.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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 = [
|
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]]
|
[[package]]
|
||||||
@ -305,7 +307,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typer"
|
name = "typer"
|
||||||
version = "0.15.1"
|
version = "0.15.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
@ -313,9 +315,9 @@ dependencies = [
|
|||||||
{ name = "shellingham" },
|
{ name = "shellingham" },
|
||||||
{ name = "typing-extensions" },
|
{ 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 = [
|
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]]
|
[[package]]
|
||||||
|
Reference in New Issue
Block a user