Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
856f88b15e | |||
c6ed9d8416 | |||
cc25eb73c1 | |||
c8862dfb59 | |||
4d0d5bd767 | |||
4c420a7f57 | |||
5ce4a0f4b3 | |||
9cecc2f30b | |||
f71152cc78 | |||
4352633eba | |||
4a93a0387f |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "distribusi"]
|
|
||||||
path = distribusi
|
|
||||||
url = https://git.vvvvvvaria.org/crunk/distribusi
|
|
@ -1 +0,0 @@
|
|||||||
Subproject commit e291e7497e40211c2ebd54ca32a1f4bdaed71230
|
|
0
distribusi/__init__.py
Normal file
0
distribusi/__init__.py
Normal file
236
distribusi/distribusi.py
Normal file
236
distribusi/distribusi.py
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import magic
|
||||||
|
from PIL import Image
|
||||||
|
from distribusi.templates.page_template import html_footer, html_head
|
||||||
|
from distribusi.templates.image_templates import (
|
||||||
|
image_no_description,
|
||||||
|
image_with_description,
|
||||||
|
image_with_alttext,
|
||||||
|
image_full_figure,
|
||||||
|
)
|
||||||
|
from distribusi.mappings import CODE_TYPES, FILE_TYPES, SUB_TYPES
|
||||||
|
|
||||||
|
MIME_TYPE = magic.Magic(mime=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_alttext(full_path_image):
|
||||||
|
try:
|
||||||
|
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||||
|
alttext_filename = f"{image_filename_no_ext}_alttext.txt"
|
||||||
|
return _read_matching_text_file(
|
||||||
|
image_filename_no_ext, alttext_filename
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"exception {e} raised while making alttext")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _add_description(full_path_image):
|
||||||
|
try:
|
||||||
|
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||||
|
description_filename = f"{image_filename_no_ext}_dv_description.txt"
|
||||||
|
return _read_matching_text_file(
|
||||||
|
image_filename_no_ext, description_filename
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"exception {e} raised while adding description")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _read_matching_text_file(image_filename_no_ext, filename):
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
return
|
||||||
|
print(f"{image_filename_no_ext} has {filename}")
|
||||||
|
with open(filename, "r") as text_file:
|
||||||
|
return text_file.read()
|
||||||
|
|
||||||
|
|
||||||
|
def _make_thumbnail(full_path_image, name):
|
||||||
|
if full_path_image.endswith("_thumbnail.jpg"):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
size = (450, 450)
|
||||||
|
thumbnail_image = Image.open(full_path_image)
|
||||||
|
thumbnail_image.thumbnail(size)
|
||||||
|
|
||||||
|
if thumbnail_image.mode == "RGBA":
|
||||||
|
bg = Image.new("RGBA", thumbnail_image.size, (255, 255, 255))
|
||||||
|
composite = Image.alpha_composite(bg, thumbnail_image)
|
||||||
|
thumbnail_image = composite.convert("RGB")
|
||||||
|
|
||||||
|
image_filename_no_ext = os.path.splitext(full_path_image)[0]
|
||||||
|
thumbnail_filename = f"{image_filename_no_ext}_thumbnail.jpg"
|
||||||
|
thumbnail_image.save(thumbnail_filename, format="JPEG")
|
||||||
|
return os.path.basename(thumbnail_filename)
|
||||||
|
except Exception as e:
|
||||||
|
print("Thumbnailer:", e)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _format_div(type_, subtype, tag, name):
|
||||||
|
id_name = name.split(".")[0].replace(" ", "_")
|
||||||
|
filename = f'<span class="filename">{name}</span>'
|
||||||
|
|
||||||
|
if "image" in type_:
|
||||||
|
html = '<div id="{}" class="{}">{}</div>'
|
||||||
|
elif "pdf" in subtype:
|
||||||
|
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||||
|
elif "dir" in type_ or "html" in subtype or "unkown-file" in subtype:
|
||||||
|
html = '<div id="{}" class="{}">{}</div>'
|
||||||
|
else:
|
||||||
|
html = '<div id="{}" class="{}">{}' + filename + "</div>"
|
||||||
|
|
||||||
|
return html.format(id_name, subtype, tag)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_distribusi_index(index):
|
||||||
|
"""
|
||||||
|
check whether a index.html file is generated by distribusi
|
||||||
|
"""
|
||||||
|
with open(index, "r") as f:
|
||||||
|
if '<meta name="generator" content="distribusi" />' in f.read():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _write_index_html(index, html, cssfile):
|
||||||
|
with open(index, "w") as index_file:
|
||||||
|
styled_html_head = html_head.format(cssfile=cssfile)
|
||||||
|
index_file.write(styled_html_head)
|
||||||
|
for line in html:
|
||||||
|
index_file.write(line + "\n")
|
||||||
|
index_file.write(html_footer)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_text_files(name, full_path, subtype):
|
||||||
|
if name.endswith(".html") or subtype == "html":
|
||||||
|
subtype = "html"
|
||||||
|
# what types of text files to expand
|
||||||
|
tag = '<section id="{}">{}</section>'.format(
|
||||||
|
name, open(full_path).read()
|
||||||
|
)
|
||||||
|
elif subtype in CODE_TYPES or name.endswith(".txt"):
|
||||||
|
# if the plain text is code,
|
||||||
|
# which types do we wrap in pre-tags?
|
||||||
|
tag = "<pre>" + open(full_path).read() + "</pre>"
|
||||||
|
else:
|
||||||
|
subtype = subtype + " unkown-file"
|
||||||
|
tag = "<a href='{}'>{}</a>"
|
||||||
|
# a = FILE_TYPES[type_]
|
||||||
|
return subtype, tag
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_image_files(name, full_path):
|
||||||
|
thumbnail_filename = _make_thumbnail(full_path, name)
|
||||||
|
if thumbnail_filename is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
image_alttext = _add_alttext(full_path)
|
||||||
|
image_description = _add_description(full_path)
|
||||||
|
|
||||||
|
if not image_alttext and not image_description:
|
||||||
|
return image_no_description.format(
|
||||||
|
name=name, thumbnail_filename=thumbnail_filename
|
||||||
|
)
|
||||||
|
if not image_alttext:
|
||||||
|
return image_with_description.format(
|
||||||
|
name=name,
|
||||||
|
thumbnail_filename=thumbnail_filename,
|
||||||
|
image_description=image_description,
|
||||||
|
)
|
||||||
|
if not image_description:
|
||||||
|
return image_with_alttext.format(
|
||||||
|
name=name,
|
||||||
|
thumbnail_filename=thumbnail_filename,
|
||||||
|
image_alttext=image_alttext,
|
||||||
|
)
|
||||||
|
return image_full_figure.format(
|
||||||
|
name=name,
|
||||||
|
thumbnail_filename=thumbnail_filename,
|
||||||
|
image_alttext=image_alttext,
|
||||||
|
image_description=image_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_existing_index_html(root, files):
|
||||||
|
index = os.path.join(root, "index.html")
|
||||||
|
if "index.html" in files:
|
||||||
|
try:
|
||||||
|
if check_distribusi_index(index):
|
||||||
|
os.remove(index)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_hidden_files_and_folders(dirs, files):
|
||||||
|
dirs = list(filter(lambda d: not d.startswith("."), dirs))
|
||||||
|
files = list(filter(lambda f: not f.startswith("."), files))
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
def _is_skippable_file(name, cssfile):
|
||||||
|
if "index.html" in name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if name.endswith("_thumbnail.jpg"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if name.endswith("_alttext.txt"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if name.endswith("_dv_description.txt"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if cssfile in name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def distribusify(directory, cssfile):
|
||||||
|
for root, dirs, files in os.walk(directory):
|
||||||
|
html = []
|
||||||
|
dirs, files = _remove_hidden_files_and_folders(dirs, files)
|
||||||
|
_remove_existing_index_html(root, files)
|
||||||
|
|
||||||
|
for name in sorted(files):
|
||||||
|
if _is_skippable_file(name, cssfile):
|
||||||
|
continue
|
||||||
|
|
||||||
|
full_path = os.path.join(root, name)
|
||||||
|
mime = MIME_TYPE.from_file(full_path)
|
||||||
|
type_, subtype = mime.split("/")
|
||||||
|
alttext = name
|
||||||
|
|
||||||
|
if type_ in FILE_TYPES:
|
||||||
|
match type_:
|
||||||
|
case "text":
|
||||||
|
subtype, tag = handle_text_files(
|
||||||
|
name, full_path, subtype
|
||||||
|
)
|
||||||
|
case "image":
|
||||||
|
tag = _handle_image_files(name, full_path)
|
||||||
|
if tag is None:
|
||||||
|
continue
|
||||||
|
case _:
|
||||||
|
tag = FILE_TYPES[type_].format(name, alttext)
|
||||||
|
|
||||||
|
if subtype in SUB_TYPES:
|
||||||
|
tag = SUB_TYPES[subtype]
|
||||||
|
|
||||||
|
if type_ not in FILE_TYPES and subtype not in SUB_TYPES:
|
||||||
|
# catch exceptions not yet defined in FILE_TYPES or SUB_TYPES
|
||||||
|
tag = "<a href='{}'>{}</a>"
|
||||||
|
|
||||||
|
tag = tag.replace("{}", name)
|
||||||
|
html.append(_format_div(type_, subtype, tag, name))
|
||||||
|
|
||||||
|
if root != directory:
|
||||||
|
html.append('<a href="../index.html">../</a>')
|
||||||
|
|
||||||
|
for name in sorted(dirs):
|
||||||
|
tag = "<a href='{}/index.html'>{}</a>".replace("{}", name)
|
||||||
|
html.insert(0, format_div("dir", "dir", tag, "folder"))
|
||||||
|
|
||||||
|
index = os.path.join(root, "index.html")
|
||||||
|
_write_index_html(index, html, cssfile)
|
15
distribusi/mappings.py
Normal file
15
distribusi/mappings.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
CODE_TYPES = ["x-c", "x-shellscript", "x-python"]
|
||||||
|
|
||||||
|
FILE_TYPES = {
|
||||||
|
"image": '<img class="image" src="{}" alt="{}">',
|
||||||
|
"text": '<a href="{}" class="text">{}</a>',
|
||||||
|
"video": ("<video controls>" '<source src="{}"></video>'),
|
||||||
|
"audio": ('<audio controls class="audio">' '<source src="{}"></audio>'),
|
||||||
|
}
|
||||||
|
|
||||||
|
SUB_TYPES = {
|
||||||
|
"pdf": (
|
||||||
|
'<object data="{}" class="pdf" type="application/pdf">'
|
||||||
|
'<embed src="{}" type="application/pdf" /></object>'
|
||||||
|
)
|
||||||
|
}
|
0
distribusi/templates/__init__.py
Normal file
0
distribusi/templates/__init__.py
Normal file
4
distribusi/templates/image_templates.py
Normal file
4
distribusi/templates/image_templates.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
image_no_description = '<a href="{name}"><img class="thumbnail" src="{thumbnail_filename}" alt="{name}"></a>'
|
||||||
|
image_with_description = '<figure><a href="{name}"><img class="thumbnail" src="{thumbnail_filename}"></a><figcaption>{image_description}</figcaption></figure>'
|
||||||
|
image_with_alttext = '<a href="{name}"><img class="thumbnail" src="{thumbnail_filename}" alt="{image_alttext}"></a>'
|
||||||
|
image_full_figure = '<figure><a href="{name}"><img class="thumbnail" src="{thumbnail_filename}" alt="{image_alttext}"></a><figcaption>{image_description}</figcaption></figure>'
|
16
distribusi/templates/page_template.py
Normal file
16
distribusi/templates/page_template.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
html_head = """\
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Generated with distribusi https://git.vvvvvvaria.org/crunk/distribusi -->
|
||||||
|
<meta name="generator" content="distribusi" />
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||||
|
<link rel="stylesheet" href="{cssfile}" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
"""
|
||||||
|
|
||||||
|
html_footer = """\
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
@ -8,7 +8,6 @@ bleach-allowlist==1.0.3
|
|||||||
blinker==1.7.0
|
blinker==1.7.0
|
||||||
cffi
|
cffi
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi@3eefd6e5ca7048555d441df8c6fbf4f2e255acac
|
|
||||||
dnspython==2.1.0
|
dnspython==2.1.0
|
||||||
email-validator==1.1.3
|
email-validator==1.1.3
|
||||||
Flask==3.0.3
|
Flask==3.0.3
|
||||||
|
2
setup.py
2
setup.py
@ -1,3 +1,3 @@
|
|||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
setup(name="library", version="1.0", packages=find_packages())
|
setup(name="dsitribusi-verse", version="1.0", packages=find_packages())
|
||||||
|
@ -2,7 +2,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from flask import render_template, Blueprint
|
from flask import render_template, Blueprint
|
||||||
from flask_login import current_user, login_required
|
from flask_login import login_required
|
||||||
from sqlalchemy.exc import (
|
from sqlalchemy.exc import (
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
DataError,
|
DataError,
|
||||||
|
@ -51,6 +51,8 @@ def create_app():
|
|||||||
def inject_title():
|
def inject_title():
|
||||||
return dict(title=APP.config["title"])
|
return dict(title=APP.config["title"])
|
||||||
|
|
||||||
|
APP.logger.setLevel("INFO")
|
||||||
|
APP.logger.info("Distribusi-verse successfully started")
|
||||||
return APP
|
return APP
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,8 +15,6 @@ from sqlalchemy.exc import (
|
|||||||
InterfaceError,
|
InterfaceError,
|
||||||
InvalidRequestError,
|
InvalidRequestError,
|
||||||
)
|
)
|
||||||
import piexif
|
|
||||||
from PIL import Image
|
|
||||||
from app import db
|
from app import db
|
||||||
from models.distribusi_model import Distribusis
|
from models.distribusi_model import Distribusis
|
||||||
from models.distribusi_file_model import DistribusiFiles
|
from models.distribusi_file_model import DistribusiFiles
|
||||||
@ -114,7 +112,6 @@ def get_distribusi_file_forms(distribusi_id):
|
|||||||
def save_described_file_to_db(describe_form, distribusi_file):
|
def save_described_file_to_db(describe_form, distribusi_file):
|
||||||
try:
|
try:
|
||||||
if describe_form.description.data:
|
if describe_form.description.data:
|
||||||
print(distribusi_file.id)
|
|
||||||
distribusi_file.description = describe_form.description.data
|
distribusi_file.description = describe_form.description.data
|
||||||
if describe_form.alttext.data:
|
if describe_form.alttext.data:
|
||||||
distribusi_file.alttext = describe_form.alttext.data
|
distribusi_file.alttext = describe_form.alttext.data
|
||||||
@ -143,7 +140,8 @@ def save_described_file_to_db(describe_form, distribusi_file):
|
|||||||
def add_alttext_to_file(describe_form, distribusi_file):
|
def add_alttext_to_file(describe_form, distribusi_file):
|
||||||
if not describe_form.alttext.data:
|
if not describe_form.alttext.data:
|
||||||
return
|
return
|
||||||
filename_no_ext = os.path.splitext(distribusi_file.path)[0]
|
filename = os.path.join("stash", distribusi_file.path)
|
||||||
|
filename_no_ext = os.path.splitext(filename)[0]
|
||||||
with open(f"{filename_no_ext}_alttext.txt", "w") as alttext_file:
|
with open(f"{filename_no_ext}_alttext.txt", "w") as alttext_file:
|
||||||
alttext_file.write(describe_form.alttext.data)
|
alttext_file.write(describe_form.alttext.data)
|
||||||
return
|
return
|
||||||
@ -152,7 +150,8 @@ def add_alttext_to_file(describe_form, distribusi_file):
|
|||||||
def add_description_to_file(describe_form, distribusi_file):
|
def add_description_to_file(describe_form, distribusi_file):
|
||||||
if not describe_form.description.data:
|
if not describe_form.description.data:
|
||||||
return
|
return
|
||||||
filename_no_ext = os.path.splitext(distribusi_file.path)[0]
|
filename = os.path.join("stash", distribusi_file.path)
|
||||||
|
filename_no_ext = os.path.splitext(filename)[0]
|
||||||
with open(
|
with open(
|
||||||
f"{filename_no_ext}_dv_description.txt", "w"
|
f"{filename_no_ext}_dv_description.txt", "w"
|
||||||
) as description_file:
|
) as description_file:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Describe File Form to describe files in the distribusi archive"""
|
"""Describe File Form to describe files in the distribusi archive"""
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField, validators
|
from wtforms import StringField, SubmitField
|
||||||
from wtforms.validators import Length
|
from wtforms.validators import Length
|
||||||
from wtforms.widgets import TextArea
|
from wtforms.widgets import TextArea
|
||||||
|
|
||||||
@ -39,10 +39,3 @@ class DescribeFilesForm(FlaskForm):
|
|||||||
self.searchtags.id = f"searchtags-{id}"
|
self.searchtags.id = f"searchtags-{id}"
|
||||||
self.description.id = f"description-{id}"
|
self.description.id = f"description-{id}"
|
||||||
self.save.id = f"save-{id}"
|
self.save.id = f"save-{id}"
|
||||||
|
|
||||||
|
|
||||||
# def StringFieldWithId(form_name, **kwargs):
|
|
||||||
# name = kwargs.get('name', 'Bar')
|
|
||||||
# return wtf.StringField(name, render_kw={
|
|
||||||
# 'id': f'{}{}'.format(form_name, name.lower(),
|
|
||||||
# **kwargs.get('render_kw', {})})
|
|
||||||
|
@ -31,13 +31,13 @@
|
|||||||
{% for id, describe_form in distribusi_file_forms.items() %}
|
{% for id, describe_form in distribusi_file_forms.items() %}
|
||||||
<div class="distribusi_file">
|
<div class="distribusi_file">
|
||||||
{% if describe_form.type == "image" %}
|
{% if describe_form.type == "image" %}
|
||||||
<img src="{{describe_form.file_path}}" alt="" data-src="{{describe_form.file_path}}" loading="lazy"/>
|
<img src="{{ url_for('shortstashurl')}}/{{describe_form.file_path}}" alt="" data-src="{{ url_for('shortstashurl')}}/{{describe_form.file_path}}" loading="lazy"/>
|
||||||
{% elif describe_form.type == "video" %}
|
{% elif describe_form.type == "video" %}
|
||||||
<video src="{{describe_form.file_path}}" preload="auto" alt="" controls loading="lazy"></video>
|
<video src="{{ url_for('shortstashurl')}}/{{describe_form.file_path}}" preload="auto" alt="" controls loading="lazy"></video>
|
||||||
{% elif describe_form.type == "audio" %}
|
{% elif describe_form.type == "audio" %}
|
||||||
<audio src="{{describe_form.file_path}}" preload="auto" alt="" controls loading="lazy"> </audio>
|
<audio src="{{ url_for('shortstashurl')}}/{{describe_form.file_path}}" preload="auto" alt="" controls loading="lazy"> </audio>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{describe_form.file_path}}">file: {{describe_form.file_path}}</a>
|
<a href="{{ url_for('shortstashurl')}}/{{describe_form.file_path}}">file: {{describe_form.file_path}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form id={{id}} method="POST" enctype="multipart/form-data" action="{{ url_for('describer.describe_file', file_id=id) }}">
|
<form id={{id}} method="POST" enctype="multipart/form-data" action="{{ url_for('describer.describe_file', file_id=id) }}">
|
||||||
{{ describe_form.csrf_token }}
|
{{ describe_form.csrf_token }}
|
||||||
|
@ -4,7 +4,7 @@ import magic
|
|||||||
from distribusi.mappings import FILE_TYPES
|
from distribusi.mappings import FILE_TYPES
|
||||||
from models.distribusi_model import Distribusis
|
from models.distribusi_model import Distribusis
|
||||||
from models.distribusi_file_model import DistribusiFiles
|
from models.distribusi_file_model import DistribusiFiles
|
||||||
from app import create_app, get_app, db
|
from app import get_app, db
|
||||||
from sqlalchemy.exc import (
|
from sqlalchemy.exc import (
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
DataError,
|
DataError,
|
||||||
@ -30,14 +30,14 @@ def _get_distribusi_from_path(path):
|
|||||||
|
|
||||||
def _add_distribusi_file_to_db(distribusi, full_path, type):
|
def _add_distribusi_file_to_db(distribusi, full_path, type):
|
||||||
app = get_app()
|
app = get_app()
|
||||||
app.logger.info(f"adding file to database: {full_path} type: {type}")
|
app.logger.info(f"Adding file to database: {full_path} type: {type}")
|
||||||
distribusi_file = DistribusiFiles.query.filter_by(path=full_path).first()
|
distribusi_file = DistribusiFiles.query.filter_by(path=full_path).first()
|
||||||
if distribusi_file is not None:
|
if distribusi_file is not None:
|
||||||
app.logger.error(f"File already in database: {full_path}")
|
app.logger.error(f"File already in database: {full_path}")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
new_distribusi_file = DistribusiFiles(
|
new_distribusi_file = DistribusiFiles(
|
||||||
path=full_path,
|
path=full_path.lstrip("stash/"),
|
||||||
type=type,
|
type=type,
|
||||||
distribusi=distribusi.id,
|
distribusi=distribusi.id,
|
||||||
)
|
)
|
||||||
|
@ -12,7 +12,7 @@ from sqlalchemy.exc import (
|
|||||||
)
|
)
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from app import db
|
from app import db, APP
|
||||||
from distribusikan.distribusis_info import DistribusisInfo
|
from distribusikan.distribusis_info import DistribusisInfo
|
||||||
from distribusikan.forms.distribusiform import DistribusiForm
|
from distribusikan.forms.distribusiform import DistribusiForm
|
||||||
from distribusikan.forms.editorform import EditorForm
|
from distribusikan.forms.editorform import EditorForm
|
||||||
@ -92,8 +92,7 @@ def copy_public_to_user_folder(editorform, publicfolder, newcssfolder):
|
|||||||
copycssfile = os.path.join(
|
copycssfile = os.path.join(
|
||||||
publicfolder, f"{secure_filename(editorform.cssname.data)}.css"
|
publicfolder, f"{secure_filename(editorform.cssname.data)}.css"
|
||||||
)
|
)
|
||||||
print(f"copying file: {copycssfile}")
|
APP.logger.info(f"copying file:{copycssfile} to folder:{newcssfolder}")
|
||||||
print(f"to folder: {newcssfolder}")
|
|
||||||
shutil.copy(copycssfile, newcssfolder)
|
shutil.copy(copycssfile, newcssfolder)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from sqlalchemy.exc import (
|
|||||||
InvalidRequestError,
|
InvalidRequestError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app import db
|
from app import db, APP
|
||||||
from distribusikan.distribusis_info import DistribusisInfo
|
from distribusikan.distribusis_info import DistribusisInfo
|
||||||
from distribusikan.forms.distribusiform import DistribusiForm
|
from distribusikan.forms.distribusiform import DistribusiForm
|
||||||
from distribusikan.forms.publicthemeform import PublicThemeForm
|
from distribusikan.forms.publicthemeform import PublicThemeForm
|
||||||
@ -18,6 +18,7 @@ from distribusikan.forms.selectorform import SelectorForm
|
|||||||
from distribusikan.forms.themeform import ThemeForm
|
from distribusikan.forms.themeform import ThemeForm
|
||||||
from distribusikan.forms.uploadform import UploadForm
|
from distribusikan.forms.uploadform import UploadForm
|
||||||
from models.distribusi_model import Distribusis
|
from models.distribusi_model import Distribusis
|
||||||
|
from models.distribusi_file_model import DistribusiFiles
|
||||||
from models.user_model import User
|
from models.user_model import User
|
||||||
|
|
||||||
# UserPengguna
|
# UserPengguna
|
||||||
@ -65,7 +66,7 @@ def auto_fill_in_upload_form(uploadform, current_distribusi):
|
|||||||
|
|
||||||
|
|
||||||
def select_new_distribusi():
|
def select_new_distribusi():
|
||||||
print("make a new distribusi")
|
APP.logger.info("make a new distribusi")
|
||||||
select_current_distribusi("new")
|
select_current_distribusi("new")
|
||||||
|
|
||||||
|
|
||||||
@ -79,12 +80,11 @@ def select_describe_distribusi(distribusiname):
|
|||||||
|
|
||||||
|
|
||||||
def select_update_distribusi(distribusiname):
|
def select_update_distribusi(distribusiname):
|
||||||
print(f"Update this distribusi {distribusiname}")
|
|
||||||
select_current_distribusi(distribusiname)
|
select_current_distribusi(distribusiname)
|
||||||
|
|
||||||
|
|
||||||
def delete_distribusi(distribusiname):
|
def delete_distribusi(distribusiname):
|
||||||
print(f"delete this distribusi {distribusiname}")
|
APP.logger.info(f"attempt to delete distribusi:{distribusiname}")
|
||||||
selectorform = SelectorForm()
|
selectorform = SelectorForm()
|
||||||
try:
|
try:
|
||||||
user = User.query.filter_by(email=current_user.email).first()
|
user = User.query.filter_by(email=current_user.email).first()
|
||||||
@ -94,29 +94,45 @@ def delete_distribusi(distribusiname):
|
|||||||
if distribusi.userid is user.id:
|
if distribusi.userid is user.id:
|
||||||
db.session.delete(distribusi)
|
db.session.delete(distribusi)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
userfolder = os.path.join("stash", distribusi.distribusiname)
|
|
||||||
if os.path.exists(userfolder):
|
|
||||||
shutil.rmtree(userfolder)
|
|
||||||
cssfolder = os.path.join(
|
|
||||||
"themes/userthemes", distribusi.distribusiname
|
|
||||||
)
|
|
||||||
if os.path.exists(cssfolder):
|
|
||||||
shutil.rmtree(cssfolder)
|
|
||||||
if distribusi.publictheme is not None:
|
|
||||||
publicthemefolder = os.path.join(
|
|
||||||
"themes/publicthemes", distribusi.distribusiname
|
|
||||||
)
|
|
||||||
if os.path.exists(publicthemefolder):
|
|
||||||
shutil.rmtree(publicthemefolder)
|
|
||||||
# SelectField error is list is a tuple?? why??
|
|
||||||
# selectorform.distribusis.errors.append("Distribusi deleted!")
|
|
||||||
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
|
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
# selectorform.distribusis.errors.append("Unknown error occured!")
|
|
||||||
flash("An error occured !", "danger")
|
flash("An error occured !", "danger")
|
||||||
|
_delete_distribusi_files(distribusi.distribusiname)
|
||||||
|
APP.logger.info(f"deleted distribusi:{distribusiname}")
|
||||||
return selectorform
|
return selectorform
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_distribusi_files(distribusiname):
|
||||||
|
userfolder = os.path.join("stash", distribusiname)
|
||||||
|
if os.path.exists(userfolder):
|
||||||
|
shutil.rmtree(userfolder)
|
||||||
|
cssfolder = os.path.join("themes/userthemes", distribusiname)
|
||||||
|
if os.path.exists(cssfolder):
|
||||||
|
shutil.rmtree(cssfolder)
|
||||||
|
|
||||||
|
distribusi = Distribusis.query.filter_by(
|
||||||
|
distribusiname=distribusiname
|
||||||
|
).first()
|
||||||
|
if distribusi.publictheme is not None:
|
||||||
|
publicthemefolder = os.path.join("themes/publicthemes", distribusiname)
|
||||||
|
if os.path.exists(publicthemefolder):
|
||||||
|
shutil.rmtree(publicthemefolder)
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_distribusi_database_files(distribusiname):
|
||||||
|
try:
|
||||||
|
distribusi = Distribusis.query.filter_by(
|
||||||
|
distribusiname=distribusiname
|
||||||
|
).first()
|
||||||
|
distribusi_db_files = DistribusiFiles.query.filter_by(
|
||||||
|
distribusi=distribusi
|
||||||
|
)
|
||||||
|
db.session.delete(distribusi_db_files)
|
||||||
|
db.session.commit()
|
||||||
|
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
|
||||||
|
db.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
def select_current_distribusi(distribusiname):
|
def select_current_distribusi(distribusiname):
|
||||||
if not current_user.is_authenticated:
|
if not current_user.is_authenticated:
|
||||||
return
|
return
|
||||||
|
@ -2,8 +2,6 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
# Tada!
|
|
||||||
from distribusi.cli import build_argparser
|
|
||||||
from distribusi.distribusi import distribusify
|
from distribusi.distribusi import distribusify
|
||||||
from flask import flash, redirect, render_template, url_for
|
from flask import flash, redirect, render_template, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -14,7 +12,7 @@ from sqlalchemy.exc import (
|
|||||||
InvalidRequestError,
|
InvalidRequestError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app import db
|
from app import db, APP
|
||||||
from distribusikan.add_files_to_describer import add_distribusi_files_to_db
|
from distribusikan.add_files_to_describer import add_distribusi_files_to_db
|
||||||
from distribusikan.distribusi_selector import selector_visible
|
from distribusikan.distribusi_selector import selector_visible
|
||||||
from distribusikan.distribusis_info import DistribusisInfo
|
from distribusikan.distribusis_info import DistribusisInfo
|
||||||
@ -96,10 +94,10 @@ def get_css_file(distribusi):
|
|||||||
|
|
||||||
|
|
||||||
def run_distribusi(userfolder, cssfile):
|
def run_distribusi(userfolder, cssfile):
|
||||||
print(f"Run distribusi on this folder: {userfolder} with css:{cssfile}")
|
APP.logger.info(
|
||||||
parser = build_argparser()
|
f"Run distribusi on this folder: {userfolder} with css:{cssfile}"
|
||||||
args = parser.parse_args(["-t", "-a", "--menu-with-index", "-s", cssfile])
|
)
|
||||||
distribusify(args, userfolder)
|
distribusify(userfolder, cssfile)
|
||||||
|
|
||||||
|
|
||||||
def set_distribusi_to_visible(distribusi, user):
|
def set_distribusi_to_visible(distribusi, user):
|
||||||
|
@ -4,7 +4,6 @@ from wtforms import (
|
|||||||
SelectField,
|
SelectField,
|
||||||
StringField,
|
StringField,
|
||||||
SubmitField,
|
SubmitField,
|
||||||
TextAreaField,
|
|
||||||
validators,
|
validators,
|
||||||
)
|
)
|
||||||
from wtforms.validators import (
|
from wtforms.validators import (
|
||||||
|
@ -79,7 +79,9 @@ def upload_updates_files(uploadfolder):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
|
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
uploadform.sitename.errors.append("Something went wrong!")
|
uploadform.sitename.errors.append(
|
||||||
|
"Something went wrong with the database!"
|
||||||
|
)
|
||||||
|
|
||||||
zipfilename = "{}.zip".format(distribusi.distribusiname)
|
zipfilename = "{}.zip".format(distribusi.distribusiname)
|
||||||
zipfile = uploadform.zipfile.data
|
zipfile = uploadform.zipfile.data
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
"""SearchForm to search files and distribusis in the distribusi archive"""
|
"""SearchForm to search files and distribusis in the distribusi archive"""
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField, validators
|
from wtforms import StringField, SubmitField
|
||||||
from wtforms.validators import Length
|
|
||||||
from wtforms.widgets import TextArea
|
|
||||||
|
|
||||||
|
|
||||||
class SearchForm(FlaskForm):
|
class SearchForm(FlaskForm):
|
||||||
|
@ -20,12 +20,16 @@ SEARCH_DATA_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "searchdata"))
|
|||||||
def searchpage():
|
def searchpage():
|
||||||
searchform = SearchForm()
|
searchform = SearchForm()
|
||||||
found_distribusis = []
|
found_distribusis = []
|
||||||
|
found_distribusi_files = []
|
||||||
if searchform.validate_on_submit():
|
if searchform.validate_on_submit():
|
||||||
found_distribusis = search(searchform.searchfield.data)
|
found_distribusis, found_distribusi_files = search(
|
||||||
|
searchform.searchfield.data
|
||||||
|
)
|
||||||
template = render_template(
|
template = render_template(
|
||||||
"search.html",
|
"search.html",
|
||||||
searchform=searchform,
|
searchform=searchform,
|
||||||
found_distribusis=found_distribusis,
|
found_distribusis=found_distribusis,
|
||||||
|
found_distribusi_files=found_distribusi_files,
|
||||||
)
|
)
|
||||||
return template
|
return template
|
||||||
|
|
||||||
@ -36,7 +40,15 @@ def search(searchinput):
|
|||||||
with ix.searcher() as searcher:
|
with ix.searcher() as searcher:
|
||||||
query = QueryParser("content", ix.schema).parse(searchinput)
|
query = QueryParser("content", ix.schema).parse(searchinput)
|
||||||
search_results = searcher.search(query)
|
search_results = searcher.search(query)
|
||||||
found_distribusis = []
|
|
||||||
for result in search_results:
|
for result in search_results:
|
||||||
found_distribusis.append(result["title"])
|
print(result["title"])
|
||||||
return found_distribusis
|
print(result["path"])
|
||||||
|
|
||||||
|
found_distribusis = []
|
||||||
|
found_distribusi_files = []
|
||||||
|
for result in search_results:
|
||||||
|
if result["path"] == "/a":
|
||||||
|
found_distribusis.append(result["title"])
|
||||||
|
if result["path"] == "/b":
|
||||||
|
found_distribusi_files.append(result["title"])
|
||||||
|
return found_distribusis, found_distribusi_files
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
from whoosh.fields import *
|
from whoosh.fields import *
|
||||||
from whoosh.index import create_in
|
from whoosh.index import create_in, open_dir
|
||||||
from whoosh.qparser import QueryParser
|
|
||||||
from models.distribusi_model import Distribusis
|
from models.distribusi_model import Distribusis
|
||||||
|
from models.distribusi_file_model import DistribusiFiles
|
||||||
import flask_apscheduler
|
import flask_apscheduler
|
||||||
|
|
||||||
|
|
||||||
@ -15,21 +15,26 @@ def init_search_index(APP):
|
|||||||
scheduler.api_enabled = False
|
scheduler.api_enabled = False
|
||||||
scheduler.init_app(APP)
|
scheduler.init_app(APP)
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
index_distribusis(APP)
|
|
||||||
index_distribusi_files(APP)
|
|
||||||
|
|
||||||
@scheduler.task("interval", id="update", minutes=60)
|
|
||||||
def update_search_index():
|
|
||||||
index_distribusis(APP)
|
|
||||||
index_distribusi_files(APP)
|
|
||||||
|
|
||||||
|
|
||||||
def index_distribusis(APP):
|
|
||||||
schema = Schema(
|
schema = Schema(
|
||||||
title=TEXT(stored=True), path=ID(stored=True), content=TEXT
|
title=TEXT(stored=True), path=ID(stored=True), content=TEXT
|
||||||
)
|
)
|
||||||
ix = create_in(SEARCH_DATA_DIR, schema)
|
ix = create_in(SEARCH_DATA_DIR, schema)
|
||||||
writer = ix.writer()
|
writer = ix.writer()
|
||||||
|
index_distribusis(APP, writer)
|
||||||
|
index_distribusi_files(APP, writer)
|
||||||
|
writer.commit(optimize=True)
|
||||||
|
|
||||||
|
@scheduler.task("interval", id="update", minutes=60)
|
||||||
|
def update_search_index():
|
||||||
|
ix = open_dir(SEARCH_DATA_DIR)
|
||||||
|
update_writer = ix.writer()
|
||||||
|
index_distribusis(APP, update_writer)
|
||||||
|
index_distribusi_files(APP, update_writer)
|
||||||
|
update_writer.commit(optimize=True)
|
||||||
|
|
||||||
|
|
||||||
|
def index_distribusis(APP, writer):
|
||||||
distribusis = _visible_distribusis(APP)
|
distribusis = _visible_distribusis(APP)
|
||||||
for distribusi in distribusis:
|
for distribusi in distribusis:
|
||||||
writer.add_document(
|
writer.add_document(
|
||||||
@ -37,11 +42,19 @@ def index_distribusis(APP):
|
|||||||
path="/a",
|
path="/a",
|
||||||
content=distribusi.description,
|
content=distribusi.description,
|
||||||
)
|
)
|
||||||
writer.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def index_distribusi_files(APP):
|
def index_distribusi_files(APP, writer):
|
||||||
APP.logger.info("searching distribusi files not implemented yet.")
|
with APP.app_context():
|
||||||
|
for distribusi_file in DistribusiFiles.query.all():
|
||||||
|
APP.logger.info(
|
||||||
|
f"adding distribusi file {distribusi_file.path} to search index"
|
||||||
|
)
|
||||||
|
writer.add_document(
|
||||||
|
title=distribusi_file.path,
|
||||||
|
path="/b",
|
||||||
|
content=distribusi_file.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _visible_distribusis(APP):
|
def _visible_distribusis(APP):
|
||||||
|
@ -19,5 +19,13 @@
|
|||||||
<a href='{{ url_for('shortstashurl')}}/{{found_distribusi}}/index.html'>{{found_distribusi}}</a>
|
<a href='{{ url_for('shortstashurl')}}/{{found_distribusi}}/index.html'>{{found_distribusi}}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="searchresults">
|
||||||
|
{% for found_distribusi_file in found_distribusi_files %}
|
||||||
|
<a href="{{ url_for('shortstashurl')}}/{{found_distribusi_file}}">
|
||||||
|
<img src="{{ url_for('shortstashurl')}}/{{found_distribusi_file}}" alt="">
|
||||||
|
{{found_distribusi_file}}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -10,7 +10,7 @@ from flask import (
|
|||||||
session,
|
session,
|
||||||
url_for,
|
url_for,
|
||||||
)
|
)
|
||||||
from flask_login import current_user, login_required, logout_user
|
from flask_login import login_required, logout_user
|
||||||
from flask_wtf.csrf import CSRFError
|
from flask_wtf.csrf import CSRFError
|
||||||
|
|
||||||
from admin import is_adminuser
|
from admin import is_adminuser
|
||||||
|
@ -8,7 +8,10 @@ body
|
|||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#mainworkflow
|
#mainworkflow
|
||||||
{
|
{
|
||||||
@ -121,13 +124,15 @@ input[type="submit"]:disabled:focus {
|
|||||||
to { width: 55%; }
|
to { width: 55%; }
|
||||||
}
|
}
|
||||||
#fancyboi {
|
#fancyboi {
|
||||||
|
font-size: 24px;
|
||||||
width: 55%;
|
width: 55%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
animation: reveal 4s linear;
|
animation: reveal 2s linear;
|
||||||
text-overflow: "█";
|
text-overflow: "█";
|
||||||
background-color: #9de457;
|
background-color: #9de457;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
#fancyboi::after {
|
#fancyboi::after {
|
||||||
content: "█";
|
content: "█";
|
||||||
@ -139,6 +144,7 @@ div.maincontent{
|
|||||||
width: 55%;
|
width: 55%;
|
||||||
border: 3px #9de457;
|
border: 3px #9de457;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
margin: auto;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border-style: outset;
|
border-style: outset;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,6 @@ def LoginUser():
|
|||||||
flash("Logged in successfully.", "success")
|
flash("Logged in successfully.", "success")
|
||||||
next = request.args.get("next")
|
next = request.args.get("next")
|
||||||
if next is not None and not is_safe_url(next): # noqa: F821
|
if next is not None and not is_safe_url(next): # noqa: F821
|
||||||
print(next)
|
|
||||||
return abort(400)
|
return abort(400)
|
||||||
print("index")
|
print("index")
|
||||||
return redirect(next or url_for("index"))
|
return redirect(next or url_for("index"))
|
||||||
|
@ -1,20 +1,32 @@
|
|||||||
{% block menu %}
|
{% block menu %}
|
||||||
<button onclick="filterSelection('all')" id="removefilter">Remove filter</button>
|
<button onclick="filterSelection('all')" id="removefilter">Remove filter</button>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button id="Year" class="dropbtn">Year</button>
|
<nav class="button-navigation">
|
||||||
|
<button id="Year" class="dropbtn" aria-haspopup="true">Year</button>
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
|
<ul>
|
||||||
{% for year in years %}
|
{% for year in years %}
|
||||||
<button type="button" name="button" onclick="filterSelection('{{ year[0] }}', '{{ year[1] }}', 'Year')" >{{ year[1] }}</button>
|
<li>
|
||||||
|
<button type="button" name="button" onclick="filterSelection('{{ year[0] }}', '{{ year[1] }}', 'Year')" >{{ year[1] }}</button>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button id="Category" class="dropbtn">Category</button>
|
<nav class="button-navigation">
|
||||||
|
<button id="Category" class="dropbtn" aria-haspopup="true">Category</button>
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
|
<ul>
|
||||||
{% for category in categories %}
|
{% for category in categories %}
|
||||||
<button type="button" name="button" onclick="filterSelection('{{ category[0] }}', '{{ category[1] }}', 'Category')" >{{ category[1] }}</button>
|
<li>
|
||||||
|
<button type="button" name="button" onclick="filterSelection('{{ category[0] }}', '{{ category[1] }}', 'Category')" >{{ category[1] }}</button>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<input id="tagsearch" type="text" placeholder="Search..">
|
<input id="tagsearch" type="text" placeholder="Search..">
|
||||||
{% endblock menu %}
|
{% endblock menu %}
|
||||||
|
@ -39,9 +39,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
<h2 id="fancyboi"> Hi {{ current_user.username }}!</h2>
|
<h1 id="fancyboi"> Hi {{ current_user.username }}!</h1>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h2 id="fancyboi"> Welcome to distribusi-verse</h2>
|
<h1 id="fancyboi"> Welcome to the Varia Archive </h1>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="maincontent">
|
<div class="maincontent">
|
||||||
<p>Distribusi is a content management system for the web that produces static index pages based on folders in the files system. It is inspired by the automatic index functions featured in several popular web servers. Distribusi works by traversing the file system and directory hierarchy to automatically list all the files in the directory, detect the file types and providing them with relevant html classes and tags for easy styling.
|
<p>Distribusi is a content management system for the web that produces static index pages based on folders in the files system. It is inspired by the automatic index functions featured in several popular web servers. Distribusi works by traversing the file system and directory hierarchy to automatically list all the files in the directory, detect the file types and providing them with relevant html classes and tags for easy styling.
|
||||||
|
Loading…
Reference in New Issue
Block a user