diff --git a/README.md b/README.md
index e69de29..3b65f1a 100644
--- a/README.md
+++ b/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
+
+
+
+
+
+ {{ title }}
+
+ {% for style in styles %}
+
+ {% endfor %}
+
+
+
+ {{ content }}
+
+
+
+```
+
+## 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
+
+ {% for file in get_sibling_content_files('path/to/directory') %}
+
{{ file[0] }} - {{ file[1] }}
+ {% endfor %}
+
+```
+
+### Example Usage of `get_text_document_preview`
+
+```html
+
+```
+
+### Example Usage of `get_sibling_content_folders`
+
+```html
+
+ {% for folder in get_sibling_content_folders('path/to/directory') %}
+
{{ folder[0] }} - {{ folder[1] }}
+ {% endfor %}
+
+```
+
+### Example Usage of `get_folder_contents`
+
+```html
+
+ {% for item in get_folder_contents('path/to/directory') %}
+
{{ item.name }} - {{ item.path }}
+ {% endfor %}
+
+```
diff --git a/docs/content/folder-layout.md b/docs/content/folder-layout.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/content/how-template-written.md b/docs/content/how-template-written.md
new file mode 100644
index 0000000..47b28bc
--- /dev/null
+++ b/docs/content/how-template-written.md
@@ -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
+
+
+
+
+
+ {{ title }}
+
+ {% for style in styles %}
+
+ {% endfor %}
+
+
+
+ {{ content }}
+
+
+
+```
diff --git a/docs/content/index.md b/docs/content/index.md
new file mode 100644
index 0000000..086712f
--- /dev/null
+++ b/docs/content/index.md
@@ -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)
diff --git a/docs/content/introduction.md b/docs/content/introduction.md
new file mode 100644
index 0000000..9721717
--- /dev/null
+++ b/docs/content/introduction.md
@@ -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.
diff --git a/docs/content/jinja-primer.md b/docs/content/jinja-primer.md
new file mode 100644
index 0000000..4309c66
--- /dev/null
+++ b/docs/content/jinja-primer.md
@@ -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' %}`
diff --git a/docs/content/site-setup.md b/docs/content/site-setup.md
new file mode 100644
index 0000000..856ca18
--- /dev/null
+++ b/docs/content/site-setup.md
@@ -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.
diff --git a/docs/content/style-setup.md b/docs/content/style-setup.md
new file mode 100644
index 0000000..a33fbbf
--- /dev/null
+++ b/docs/content/style-setup.md
@@ -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.
diff --git a/docs/content/template-setup.md b/docs/content/template-setup.md
new file mode 100644
index 0000000..ebc33fa
--- /dev/null
+++ b/docs/content/template-setup.md
@@ -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.
diff --git a/docs/content/template-style-search.md b/docs/content/template-style-search.md
new file mode 100644
index 0000000..6fe8a43
--- /dev/null
+++ b/docs/content/template-style-search.md
@@ -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.
diff --git a/docs/content/tool-input-return-types.md b/docs/content/tool-input-return-types.md
new file mode 100644
index 0000000..f2896d8
--- /dev/null
+++ b/docs/content/tool-input-return-types.md
@@ -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.
diff --git a/docs/styles/__file.md.css b/docs/styles/__file.md.css
new file mode 100644
index 0000000..e471e26
--- /dev/null
+++ b/docs/styles/__file.md.css
@@ -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;
+}
diff --git a/docs/styles/base.css b/docs/styles/base.css
new file mode 100644
index 0000000..2361ccf
--- /dev/null
+++ b/docs/styles/base.css
@@ -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%;
+}
diff --git a/docs/templates/__file.md.html b/docs/templates/__file.md.html
new file mode 100644
index 0000000..bad5d05
--- /dev/null
+++ b/docs/templates/__file.md.html
@@ -0,0 +1,4 @@
+
+ {{ content|safe }}
+
+
diff --git a/docs/templates/base.html b/docs/templates/base.html
new file mode 100644
index 0000000..024b756
--- /dev/null
+++ b/docs/templates/base.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ {{ title }}
+
+ {% for style in styles %}
+
+ {% endfor %}
+
+
+
+
Foldsite Documentation
+
+
+
+ {{ content|safe }}
+
+
+
+
diff --git a/src/rendering/image.py b/src/rendering/image.py
index 556206e..506c320 100644
--- a/src/rendering/image.py
+++ b/src/rendering/image.py
@@ -1,12 +1,9 @@
from PIL import Image
from io import BytesIO
-from functools import 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}"
+from functools import lru_cache
+@lru_cache(maxsize=512)
+def generate_thumbnail(image_path, resize_percent, min_width, max_width):
# Open the image file
with Image.open(image_path) as img:
# Calculate the new size based on the resize percentage
@@ -20,13 +17,19 @@ def generate_thumbnail(image_path, resize_percent, min_width):
new_width = min_width
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
img.thumbnail((new_width, new_height))
# Rotate the image based on the EXIF orientation tag
try:
- exif = img._getexif()
- orientation = exif.get(0x0112, 1) # 0x0112 is the EXIF orientation tag
+ exif = img.info['exif']
+ orientation = img._getexif().get(0x0112, 1) # 0x0112 is the EXIF orientation tag
if orientation == 3:
img = img.rotate(180, expand=True)
elif orientation == 6:
@@ -35,12 +38,12 @@ def generate_thumbnail(image_path, resize_percent, min_width):
img = img.rotate(90, expand=True)
except (AttributeError, KeyError, IndexError):
# cases: image don't have getexif
- pass
+ exif = None
# Save the thumbnail to a BytesIO object
thumbnail_io = BytesIO()
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)
return (thumbnail_io.getvalue(), img_format)
\ No newline at end of file
diff --git a/src/routes/routes.py b/src/routes/routes.py
index 4d507ea..24254e6 100644
--- a/src/routes/routes.py
+++ b/src/routes/routes.py
@@ -1,9 +1,8 @@
from pathlib import Path
from src.config.config import Configuration
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 functools import lru_cache
import os
@@ -114,13 +113,15 @@ class RouteManager:
if file_path.exists():
# 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"]:
+ max_width = request.args.get("max_width", default=2048, type=int)
thumbnail_bytes, img_format = generate_thumbnail(
- str(file_path), 10, 2048
+ str(file_path), 10, 2048, max_width
)
return (
thumbnail_bytes,
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)
else: