@ -1,25 +1,49 @@ |
|||
[tool.black] |
|||
[tool.ruff] |
|||
line-length = 79 |
|||
target-version = ['py311'] |
|||
include = '\.pyi?$' |
|||
exclude = ''' |
|||
/( |
|||
\.eggs |
|||
| \.git |
|||
| \.hg |
|||
| \.mypy_cache |
|||
| \.tox |
|||
| \.venv |
|||
| _build |
|||
| buck-out |
|||
| build |
|||
| dist |
|||
|
|||
# The following are specific to Black, you probably don't want those. |
|||
| blib2to3 |
|||
| tests/data |
|||
| profiling |
|||
)/ |
|||
''' |
|||
target-version = "py311" |
|||
#include = '\.pyi?$' |
|||
exclude = [ |
|||
".bzr", |
|||
".direnv", |
|||
".eggs", |
|||
".git", |
|||
".git-rewrite", |
|||
".hg", |
|||
".ipynb_checkpoints", |
|||
".mypy_cache", |
|||
".nox", |
|||
".pants.d", |
|||
".pyenv", |
|||
".pytest_cache", |
|||
".pytype", |
|||
".ruff_cache", |
|||
".svn", |
|||
".tox", |
|||
".venv", |
|||
".vscode", |
|||
"__pypackages__", |
|||
"_build", |
|||
"buck-out", |
|||
"build", |
|||
"dist", |
|||
"node_modules", |
|||
"site-packages", |
|||
"venv", |
|||
] |
|||
|
|||
[tool.ruff.lint] |
|||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. |
|||
select = ["E4", "E7", "E9", "F"] |
|||
ignore = [] |
|||
# Allow fix for all enabled rules (when `--fix`) is provided. |
|||
fixable = ["ALL"] |
|||
unfixable = [] |
|||
# Allow unused variables when underscore-prefixed. |
|||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" |
|||
|
|||
[tool.ruff.format] |
|||
quote-style = "double" |
|||
indent-style = "space" |
|||
docstring-code-format = true |
|||
line-ending = "auto" |
|||
skip-magic-trailing-comma = false |
|||
|
@ -1,3 +0,0 @@ |
|||
* |
|||
*/ |
|||
!.gitignore |
@ -0,0 +1,9 @@ |
|||
from flask_login import current_user |
|||
from models.user_model import User |
|||
|
|||
|
|||
def is_adminuser(): |
|||
if not current_user.is_authenticated: |
|||
return False |
|||
user = User.query.filter_by(email=current_user.email).first() |
|||
return user.admin |
@ -0,0 +1,160 @@ |
|||
import os |
|||
from flask import ( |
|||
Blueprint, |
|||
render_template, |
|||
redirect, |
|||
url_for, |
|||
send_from_directory, |
|||
flash, |
|||
) |
|||
from flask_login import current_user, login_required |
|||
from sqlalchemy.exc import ( |
|||
DatabaseError, |
|||
DataError, |
|||
IntegrityError, |
|||
InterfaceError, |
|||
InvalidRequestError, |
|||
) |
|||
import piexif |
|||
from PIL import Image |
|||
from app import db |
|||
from models.distribusi_model import Distribusis |
|||
from models.distribusi_file_model import DistribusiFiles |
|||
from describer.forms.describe_files_form import DescribeFilesForm |
|||
from describer.forms.redistribusi_form import ReDistribusiForm |
|||
from distribusikan.distribusi_workflow import run_distribusi, get_css_file |
|||
|
|||
describer = Blueprint( |
|||
"describer", |
|||
__name__, |
|||
template_folder="templates/describe_files", |
|||
static_folder="static", |
|||
) |
|||
|
|||
|
|||
@describer.route("/<string:distribusiname>") |
|||
@login_required |
|||
def show_distribusi_files(distribusiname): |
|||
if not current_user.is_authenticated: |
|||
return redirect(url_for("index")) |
|||
distribusi = Distribusis.query.filter_by( |
|||
distribusiname=distribusiname |
|||
).first() |
|||
redistribusi_form = ReDistribusiForm() |
|||
distribusi_file_forms = get_distribusi_file_forms(distribusi.id) |
|||
return render_template( |
|||
"describe.html", |
|||
distribusiname=distribusiname, |
|||
redistribusi_form=redistribusi_form, |
|||
distribusi_file_forms=distribusi_file_forms, |
|||
) |
|||
|
|||
|
|||
@describer.route("/redistribusi/<string:distribusiname>", methods=["POST"]) |
|||
@login_required |
|||
def re_distribusi_files(distribusiname): |
|||
distribusi = Distribusis.query.filter_by( |
|||
distribusiname=distribusiname |
|||
).first() |
|||
redistribusi_form = ReDistribusiForm() |
|||
if redistribusi_form.validate_on_submit(): |
|||
userfolder = os.path.join("stash", distribusi.distribusiname) |
|||
cssfile = get_css_file(distribusi) |
|||
run_distribusi(userfolder, cssfile) |
|||
return redirect( |
|||
url_for( |
|||
"describer.show_distribusi_files", |
|||
distribusiname=distribusi.distribusiname, |
|||
) |
|||
) |
|||
|
|||
|
|||
@describer.route("/describe_file/<int:file_id>", methods=["POST"]) |
|||
@login_required |
|||
def describe_file(file_id): |
|||
distribusi_file = DistribusiFiles.query.filter_by(id=file_id).first() |
|||
describe_form = DescribeFilesForm( |
|||
distribusi_file.id, distribusi_file.path, distribusi_file.type |
|||
) |
|||
save_described_file_to_db(describe_form, distribusi_file) |
|||
add_alttext_to_file(describe_form, distribusi_file) |
|||
add_description_to_file(describe_form, distribusi_file) |
|||
distribusi = Distribusis.query.filter_by( |
|||
id=distribusi_file.distribusi |
|||
).first() |
|||
return redirect( |
|||
url_for( |
|||
"describer.show_distribusi_files", |
|||
distribusiname=distribusi.distribusiname, |
|||
) |
|||
) |
|||
|
|||
|
|||
@describer.route("/stash/<path:path>") |
|||
def send_stash_file(path): |
|||
return send_from_directory("stash", path) |
|||
|
|||
|
|||
def get_distribusi_file_forms(distribusi_id): |
|||
distribusi_file_forms = {} |
|||
distribusi_files = DistribusiFiles.query.filter_by( |
|||
distribusi=distribusi_id |
|||
).all() |
|||
for distribusi_file in distribusi_files: |
|||
describe_form = DescribeFilesForm( |
|||
distribusi_file.id, distribusi_file.path, distribusi_file.type |
|||
) |
|||
describe_form.description.data = distribusi_file.description |
|||
describe_form.alttext.data = distribusi_file.alttext |
|||
describe_form.searchtags.data = distribusi_file.tags |
|||
distribusi_file_forms[distribusi_file.id] = describe_form |
|||
return distribusi_file_forms |
|||
|
|||
|
|||
def save_described_file_to_db(describe_form, distribusi_file): |
|||
try: |
|||
if describe_form.description.data: |
|||
print(distribusi_file.id) |
|||
distribusi_file.description = describe_form.description.data |
|||
if describe_form.alttext.data: |
|||
distribusi_file.alttext = describe_form.alttext.data |
|||
if describe_form.searchtags.data: |
|||
distribusi_file.tags = describe_form.searchtags.data |
|||
db.session.add(distribusi_file) |
|||
db.session.commit() |
|||
except (InvalidRequestError, IntegrityError): |
|||
db.session.rollback() |
|||
describe_form.save.errors.append("Something went wrong!") |
|||
flash("Something went wrong!", "danger") |
|||
except DataError: |
|||
db.session.rollback() |
|||
describe_form.save.errors.append("Invalid Entry") |
|||
flash("Invalid Entry", "warning") |
|||
except InterfaceError: |
|||
db.session.rollback() |
|||
describe_form.save.errors.append("Error connecting to the database") |
|||
flash("Error connecting to the database", "danger") |
|||
except DatabaseError: |
|||
db.session.rollback() |
|||
describe_form.save.errors.append("Error connecting to the database") |
|||
flash("Error connecting to the database", "danger") |
|||
|
|||
|
|||
def add_alttext_to_file(describe_form, distribusi_file): |
|||
if not describe_form.alttext.data: |
|||
return |
|||
filename_no_ext = os.path.splitext(distribusi_file.path)[0] |
|||
with open(f"{filename_no_ext}_alttext.txt", "w") as alttext_file: |
|||
alttext_file.write(describe_form.alttext.data) |
|||
return |
|||
|
|||
|
|||
def add_description_to_file(describe_form, distribusi_file): |
|||
if not describe_form.description.data: |
|||
return |
|||
filename_no_ext = os.path.splitext(distribusi_file.path)[0] |
|||
with open( |
|||
f"{filename_no_ext}_dv_description.txt", "w" |
|||
) as description_file: |
|||
description_file.write(describe_form.description.data) |
|||
return |
@ -0,0 +1,48 @@ |
|||
"""Describe File Form to describe files in the distribusi archive""" |
|||
|
|||
from flask_wtf import FlaskForm |
|||
from wtforms import StringField, SubmitField, validators |
|||
from wtforms.validators import Length |
|||
from wtforms.widgets import TextArea |
|||
|
|||
|
|||
class DescribeFilesForm(FlaskForm): |
|||
"""DescribeFileForm selection form.""" |
|||
|
|||
alttext = StringField( |
|||
"Alt-text for this file:", |
|||
validators=[ |
|||
Length(3, 255), |
|||
], |
|||
) |
|||
searchtags = StringField( |
|||
"Add search tags, seperated by commas. No need for the '#' sign:", |
|||
validators=[ |
|||
Length(3, 500), |
|||
], |
|||
) |
|||
description = StringField( |
|||
"Description of this file:", |
|||
validators=[ |
|||
Length(3, 4096), |
|||
], |
|||
widget=TextArea(), |
|||
) |
|||
save = SubmitField("Save") |
|||
|
|||
def __init__(self, id, file_path=None, type=None): |
|||
super(DescribeFilesForm, self).__init__() |
|||
self.id = id |
|||
self.file_path = file_path |
|||
self.type = type |
|||
self.alttext.id = f"alttext-{id}" |
|||
self.searchtags.id = f"searchtags-{id}" |
|||
self.description.id = f"description-{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', {})}) |
@ -0,0 +1,8 @@ |
|||
from flask_wtf import FlaskForm |
|||
from wtforms import SubmitField |
|||
|
|||
|
|||
class ReDistribusiForm(FlaskForm): |
|||
"""Re-Distribusi form class to re-distrusi with desribed files""" |
|||
|
|||
submit = SubmitField("Re-Distribusi!") |
@ -0,0 +1,32 @@ |
|||
#describe_workflow { |
|||
display: flex; |
|||
width: 60em; |
|||
margin:0 auto; |
|||
} |
|||
|
|||
.redistribusi { |
|||
position: sticky; |
|||
top: 2em; |
|||
margin-top: 2em; |
|||
margin-left: 1em; |
|||
width: 15em; |
|||
height: 10em; |
|||
border-style: outset; |
|||
float: right; |
|||
padding: 0.5em; |
|||
} |
|||
|
|||
.distribusi_file { |
|||
margin-top: 2em; |
|||
padding: 0.5em; |
|||
width: 42em; |
|||
background-color:#fdfdfd; |
|||
text-decoration: none; |
|||
scroll-behavior: smooth; |
|||
border-style: outset; |
|||
} |
|||
|
|||
.distribusi_file > img, video { |
|||
max-width: 100%; |
|||
max-height: 100%; |
|||
} |
@ -0,0 +1,78 @@ |
|||
{% extends "base/base.html" %} |
|||
{% block main %} |
|||
<div id="buttons"> |
|||
{% if current_user.is_authenticated %} |
|||
<div class="distribusi"> |
|||
<a href="{{ url_for('distribusikan.distribusi') }}"> |
|||
<input type="button" name="button" value="Distribusi"></input> |
|||
</a> |
|||
</div> |
|||
{% if adminuser %} |
|||
<div class="admin"> |
|||
<a href="/admin"> |
|||
<input type="button" name="button" value="Admin"></input> |
|||
</a> |
|||
</div> |
|||
{% endif %} |
|||
<div class="logout"> |
|||
<a href="/logout"> |
|||
<input type="button" name="button" value="Logout"></input> |
|||
</a> |
|||
</div> |
|||
{% endif %} |
|||
<div class="help"> |
|||
<a href="/help"> |
|||
<input type="button" name="button" value="Help"></input> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
<div id="describe_workflow"> |
|||
<div class="distribusi_files"> |
|||
{% for id, describe_form in distribusi_file_forms.items() %} |
|||
<div class="distribusi_file"> |
|||
{% if describe_form.type == "image" %} |
|||
<img src="{{describe_form.file_path}}" alt="" data-src="{{describe_form.file_path}}" loading="lazy"/> |
|||
{% elif describe_form.type == "video" %} |
|||
<video src="{{describe_form.file_path}}" preload="auto" alt="" controls loading="lazy"></video> |
|||
{% elif describe_form.type == "audio" %} |
|||
<audio src="{{describe_form.file_path}}" preload="auto" alt="" controls loading="lazy"> </audio> |
|||
{% else %} |
|||
<a href="{{describe_form.file_path}}">file: {{describe_form.file_path}}</a> |
|||
{% endif %} |
|||
<form id={{id}} method="POST" enctype="multipart/form-data" action="{{ url_for('describer.describe_file', file_id=id) }}"> |
|||
{{ describe_form.csrf_token }} |
|||
<p>File path: {{describe_form.file_path}}</p> |
|||
<fieldset class="description"> |
|||
{{ describe_form.description.label }} |
|||
{{ describe_form.description }} |
|||
{% for message in describe_form.description.errors %} |
|||
<div class="error">{{ message }}</div> |
|||
{% endfor %} |
|||
</fieldset> |
|||
<fieldset class=""> |
|||
{{ describe_form.searchtags.label }} |
|||
{{ describe_form.searchtags }} |
|||
{% for message in describe_form.searchtags.errors %} |
|||
<div class="error">{{ message }}</div> |
|||
{% endfor %} |
|||
</fieldset> |
|||
<fieldset class=""> |
|||
{{ describe_form.alttext.label }} |
|||
{{ describe_form.alttext }} |
|||
{% for message in describe_form.alttext.errors %} |
|||
<div class="error">{{ message }}</div> |
|||
{% endfor %} |
|||
</fieldset> |
|||
<fieldset class="button"> |
|||
{{ describe_form.save }} |
|||
</fieldset> |
|||
</form> |
|||
</div> |
|||
{% endfor%} |
|||
</div> |
|||
{% block redistribusi %} |
|||
{% include "redistribusi.html" %} |
|||
{% endblock redistribusi%} |
|||
</div> |
|||
<link rel="stylesheet" type="text/css" href="{{ url_for('describer.static', filename='css/describer.css')}}"> |
|||
{% endblock %} |
@ -0,0 +1,13 @@ |
|||
<div class="redistribusi"> |
|||
<p class="tooltip">Run distribusi again after describing your files.<span class="tooltiptext">Distribusi will run again and add your alttext and descriptions. |
|||
</span></p> |
|||
<form id={{distribusiname}} method="POST" enctype="multipart/form-data" action="{{ url_for('describer.re_distribusi_files', distribusiname=distribusiname) }}"> |
|||
{{ redistribusi_form.csrf_token }} |
|||
<fieldset class=""> |
|||
{{ redistribusi_form.submit }} |
|||
{% for message in redistribusi_form.submit.errors %} |
|||
<div class="error">{{ message }}</div> |
|||
{% endfor %} |
|||
</fieldset> |
|||
</form> |
|||
</div> |
@ -0,0 +1,82 @@ |
|||
import os |
|||
|
|||
import magic |
|||
from distribusi.mappings import FILE_TYPES |
|||
from models.distribusi_model import Distribusis |
|||
from models.distribusi_file_model import DistribusiFiles |
|||
from app import create_app, get_app, db |
|||
from sqlalchemy.exc import ( |
|||
DatabaseError, |
|||
DataError, |
|||
IntegrityError, |
|||
InterfaceError, |
|||
InvalidRequestError, |
|||
) |
|||
|
|||
MIME_TYPE = magic.Magic(mime=True) |
|||
|
|||
|
|||
def _distribusi_file_with_type(distribusi, full_path): |
|||
mime = MIME_TYPE.from_file(full_path) |
|||
type_, subtype = mime.split("/") |
|||
if type_ in FILE_TYPES: |
|||
_add_distribusi_file_to_db(distribusi, full_path, type_) |
|||
|
|||
|
|||
def _get_distribusi_from_path(path): |
|||
distribusi = Distribusis.query.filter_by(distribusiname=path).first() |
|||
return distribusi |
|||
|
|||
|
|||
def _add_distribusi_file_to_db(distribusi, full_path, type): |
|||
app = get_app() |
|||
app.logger.info(f"adding file to database: {full_path} type: {type}") |
|||
distribusi_file = DistribusiFiles.query.filter_by(path=full_path).first() |
|||
if distribusi_file is not None: |
|||
app.logger.error(f"File already in database: {full_path}") |
|||
return |
|||
try: |
|||
new_distribusi_file = DistribusiFiles( |
|||
path=full_path, |
|||
type=type, |
|||
distribusi=distribusi.id, |
|||
) |
|||
db.session.add(new_distribusi_file) |
|||
db.session.commit() |
|||
return |
|||
except InvalidRequestError: |
|||
db.session.rollback() |
|||
app.logger.error("Something went wrong!") |
|||
except IntegrityError: |
|||
db.session.rollback() |
|||
app.logger.error("File %s already exists!", full_path) |
|||
except DataError: |
|||
db.session.rollback() |
|||
app.logger.error("%s Invalid Entry", full_path) |
|||
except InterfaceError: |
|||
db.session.rollback() |
|||
app.logger.error("Error connecting to the database") |
|||
except DatabaseError: |
|||
db.session.rollback() |
|||
app.logger.error("Error connecting to the database") |
|||
|
|||
|
|||
def add_distribusi_files_to_db(path): |
|||
distribusi = _get_distribusi_from_path(path) |
|||
path = os.path.join("stash", path) |
|||
for root, dirs, files in os.walk(path, topdown=True): |
|||
files = list(filter(lambda f: not f.startswith("."), files)) |
|||
files = list(filter(lambda f: not f.endswith(".html"), files)) |
|||
files = list(filter(lambda f: not f.endswith("_thumbnail.jpg"), files)) |
|||
files = list(filter(lambda f: not f.endswith("_alttext.txt"), files)) |
|||
files = list( |
|||
filter(lambda f: not f.endswith("_dv_description.txt"), files) |
|||
) |
|||
|
|||
for file in files: |
|||
full_path = os.path.join(root, file) |
|||
distribusi_file = DistribusiFiles.query.filter_by( |
|||
path=full_path |
|||
).first() |
|||
if distribusi_file is None: |
|||
_distribusi_file_with_type(distribusi, full_path) |
@ -0,0 +1,32 @@ |
|||
#publicthemes > ul { |
|||
max-height: 20em; |
|||
overflow: auto; |
|||
} |
|||
|
|||
#publicthemes > ul > li{ |
|||
word-break: break-all; |
|||
} |
|||
|
|||
.workflow{ |
|||
margin-top: 1em; |
|||
padding: 0.5em; |
|||
background-color:#fdfdfd; |
|||
text-decoration: none; |
|||
scroll-behavior: smooth; |
|||
border-style: outset; |
|||
} |
|||
|
|||
.workflow > p { |
|||
padding-left: 1em; |
|||
} |
|||
|
|||
.workflow > h2 { |
|||
padding-left: 0.4em;; |
|||
} |
|||
|
|||
.workflow input{ |
|||
max-width: 20em; |
|||
} |
|||
.new_divider { |
|||
height: 5em; |
|||
} |
@ -1,7 +1,7 @@ |
|||
<div id="edit" class="workflow"> |
|||
<h2>Step 3: Edit Custom CSS (Optional)</h2> |
|||
{% if files_uploaded or distribusi_live %} |
|||
<p><a href="/editor">Go to CSS editor</a></p> |
|||
<p><a href="/distribusikan/editor">Go to CSS editor</a></p> |
|||
{% else %} |
|||
<p> |
|||
You need to upload your files first before you can select a css theme |
@ -1,34 +1,34 @@ |
|||
from flask import render_template |
|||
|
|||
from app import APP |
|||
from distribusikan.distribusiselector import SelectorVisible |
|||
from distribusikan.distribusisinfo import DistribusisInfo |
|||
from distribusikan.upload import UploadNewDistribusi, UploadUpdatedFiles |
|||
from forms.distribusiform import DistribusiForm |
|||
from forms.publicthemeform import PublicThemeForm |
|||
from forms.selectorform import SelectorForm |
|||
from forms.themeform import ThemeForm |
|||
from distribusikan.distribusi_selector import selector_visible |
|||
from distribusikan.distribusis_info import DistribusisInfo |
|||
from distribusikan.upload import upload_new_distribusi, upload_updates_files |
|||
from distribusikan.forms.distribusiform import DistribusiForm |
|||
from distribusikan.forms.publicthemeform import PublicThemeForm |
|||
from distribusikan.forms.selectorform import SelectorForm |
|||
from distribusikan.forms.themeform import ThemeForm |
|||
|
|||
# UserPengguna |
|||
from statuspengguna.helper import UserHelper |
|||
|
|||
|
|||
def UploadPage(): |
|||
def upload_page(): |
|||
"render upload page section of distribusi workflow" |
|||
uploadfolder = APP.config["UPLOAD_FOLDER"] |
|||
distribusiform = DistribusiForm() |
|||
themeform = ThemeForm() |
|||
publicthemeform = PublicThemeForm() |
|||
publicthemeform.publicthemes.choices = DistribusisInfo.publicthemes() |
|||
publicthemeform.publicthemes.choices = DistribusisInfo.public_themes() |
|||
selectorform = SelectorForm() |
|||
selectorform.distribusis.choices = DistribusisInfo.userdistribusinames() |
|||
selectorvisible = SelectorVisible() |
|||
selectorform.distribusis.choices = DistribusisInfo.user_distribusinames() |
|||
selectorvisible = selector_visible() |
|||
|
|||
current_distribusi = UserHelper.current_distribusi() |
|||
if current_distribusi == "new" or UserHelper.has_distribusi() is False: |
|||
uploadform = UploadNewDistribusi(uploadfolder) |
|||
uploadform = upload_new_distribusi(uploadfolder) |
|||
else: |
|||
uploadform = UploadUpdatedFiles(uploadfolder) |
|||
uploadform = upload_updates_files(uploadfolder) |
|||
|
|||
files_uploaded = UserHelper.is_zip_uploaded(uploadform.sitename.data) |
|||
distribusi_live = UserHelper.is_distribusi_live(current_distribusi) |
@ -0,0 +1,11 @@ |
|||
"""SearchForm to search files and distribusis in the distribusi archive""" |
|||
|
|||
from flask_wtf import FlaskForm |
|||
from wtforms import StringField, SubmitField, validators |
|||
from wtforms.validators import Length |
|||
from wtforms.widgets import TextArea |
|||
|
|||
|
|||
class SearchForm(FlaskForm): |
|||
searchfield = StringField("Search distribusi-verse archive") |
|||
submit = SubmitField("Search") |
@ -0,0 +1,42 @@ |
|||
import os |
|||
from flask import Blueprint, render_template |
|||
from whoosh.fields import * |
|||
from whoosh.index import open_dir |
|||
from whoosh.qparser import QueryParser |
|||
from search.forms.searchform import SearchForm |
|||
|
|||
searchpages = Blueprint( |
|||
"search", |
|||
__name__, |
|||
template_folder="templates/search", |
|||
static_folder="static", |
|||
) |
|||
|
|||
SCRIPT_DIR = os.path.dirname(__file__) |
|||
SEARCH_DATA_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "searchdata")) |
|||
|
|||
|
|||
@searchpages.route("/", methods=["GET", "POST"]) |
|||
def searchpage(): |
|||
searchform = SearchForm() |
|||
found_distribusis = [] |
|||
if searchform.validate_on_submit(): |
|||
found_distribusis = search(searchform.searchfield.data) |
|||
template = render_template( |
|||
"search.html", |
|||
searchform=searchform, |
|||
found_distribusis=found_distribusis, |
|||
) |
|||
return template |
|||
|
|||
|
|||
def search(searchinput): |
|||
"""search and get search result titles and return them as distribusi ids""" |
|||
ix = open_dir(SEARCH_DATA_DIR) |
|||
with ix.searcher() as searcher: |
|||
query = QueryParser("content", ix.schema).parse(searchinput) |
|||
search_results = searcher.search(query) |
|||
found_distribusis = [] |
|||
for result in search_results: |
|||
found_distribusis.append(result["title"]) |
|||
return found_distribusis |
@ -0,0 +1,52 @@ |
|||
import os |
|||
from whoosh.fields import * |
|||
from whoosh.index import create_in |
|||
from whoosh.qparser import QueryParser |
|||
from models.distribusi_model import Distribusis |
|||
import flask_apscheduler |
|||
|
|||
|
|||
SCRIPT_DIR = os.path.dirname(__file__) |
|||
SEARCH_DATA_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "searchdata")) |
|||
|
|||
|
|||
def init_search_index(APP): |
|||
scheduler = flask_apscheduler.APScheduler() |
|||
scheduler.api_enabled = False |
|||
scheduler.init_app(APP) |
|||
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( |
|||
title=TEXT(stored=True), path=ID(stored=True), content=TEXT |
|||
) |
|||
ix = create_in(SEARCH_DATA_DIR, schema) |
|||
writer = ix.writer() |
|||
distribusis = _visible_distribusis(APP) |
|||
for distribusi in distribusis: |
|||
writer.add_document( |
|||
title=distribusi.distribusiname, |
|||
path="/a", |
|||
content=distribusi.description, |
|||
) |
|||
writer.commit() |
|||
|
|||
|
|||
def index_distribusi_files(APP): |
|||
APP.logger.info("searching distribusi files not implemented yet.") |
|||
|
|||
|
|||
def _visible_distribusis(APP): |
|||
with APP.app_context(): |
|||
distribusis = Distribusis.query.filter( |
|||
Distribusis.visible.isnot(False) |
|||
).all() |
|||
return distribusis |
@ -0,0 +1,23 @@ |
|||
{% extends "base/base.html" %} |
|||
{% block main %} |
|||
<div id="distribusiverse" class="maincontent"> |
|||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('search.searchpage') }}"> |
|||
{{ searchform.csrf_token }} |
|||
<fieldset class="required"> |
|||
{{ searchform.searchfield.label }} |
|||
{{ searchform.searchfield }} |
|||
{% for message in searchform.searchfield.errors %} |
|||
<div class="error">{{ message }}</div> |
|||
{% endfor %} |
|||
</fieldset> |
|||
<fieldset class="button required"> |
|||
{{ searchform.submit }} |
|||
</fieldset> |
|||
</form> |
|||
<div class="searchresults"> |
|||
{% for found_distribusi in found_distribusis %} |
|||
<a href='{{ url_for('shortstashurl')}}/{{found_distribusi}}/index.html'>{{found_distribusi}}</a> |
|||
{% endfor %} |
|||
</div> |
|||
</div> |
|||
{% endblock %} |
@ -1,4 +1,7 @@ |
|||
title = "Varia Archive X Distribusi-Verse" |
|||
categories = ["event","gathering","work session","workgroup","performance","music event"] |
|||
categories = [ "article", "booklaunch", "broadcast", "curriculum", "game", |
|||
"gathering", "lecture", "opencall", "party", "performance", "presentation", |
|||
"publication", "report", "screening", "statement", "workgroup", "worksession", |
|||
"workshop"] |
|||
start_time = 2017-11-03 |
|||
end_time = 2025-12-31 |
|||
|
@ -1,69 +0,0 @@ |
|||
/* Dropdown Button */ |
|||
/* for sorting on year and category |
|||
*/ |
|||
button { |
|||
background-color: #E0B0FF; |
|||
text-decoration: none; |
|||
border: none; |
|||
} |
|||
.filter { |
|||
display: none; |
|||
} |
|||
|
|||
.activebtn { |
|||
background-color: #62b264; |
|||
} |
|||
|
|||
.show { |
|||
display: block; |
|||
} |
|||
.dropdown { |
|||
position: relative; |
|||
display: inline-block; |
|||
} |
|||
|
|||
/* Dropdown Content (Hidden by Default) */ |
|||
.dropdown-content { |
|||
display: none; |
|||
position: absolute; |
|||
background-color: #E0B0FF; |
|||
min-width: 120px; |
|||
border: 2px solid; |
|||
z-index: 1; |
|||
border-style: outset; |
|||
} |
|||
|
|||
/* Links inside the dropdown */ |
|||
.dropdown-content button { |
|||
color: black; |
|||
padding: 6px; |
|||
border: none; |
|||
min-width: inherit; |
|||
text-align: left; |
|||
text-decoration: none; |
|||
display: block; |
|||
} |
|||
.dropbtn { |
|||
margin-top: 1em; |
|||
} |
|||
/* Change color of dropdown links on hover */ |
|||
.dropdown-content button:hover {background-color: #62b264;} |
|||
|
|||
|
|||
/* Show the dropdown menu on hover */ |
|||
.dropdown:hover .dropdown-content {display: block;} |
|||
|
|||
/* Change the background color of the dropdown button when the dropdown content is shown */ |
|||
.dropdown:hover .dropbtn {background-color: #62b264;} |
|||
|
|||
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) { |
|||
.dropdown-content button { |
|||
font-size: 0.7em; |
|||
} |
|||
.container > button { |
|||
font-size: 0.7em; |
|||
} |
|||
.dropdown > button { |
|||
font-size: 0.7em; |
|||
} |
|||
} |
@ -1,50 +0,0 @@ |
|||
.editareas { |
|||
margin: auto; |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
} |
|||
.editarea { |
|||
width: 30%; |
|||
border: 3px solid #E0B0FF; |
|||
border-style: outset; |
|||
margin-right: 1em; |
|||
margin-left: 0; |
|||
} |
|||
|
|||
.editor { |
|||
min-width: 35%; |
|||
} |
|||
.editform { |
|||
width: 100%%; |
|||
margin: 0 auto; |
|||
} |
|||
#editorsubmitform { |
|||
padding-top: 1em; |
|||
} |
|||
.required label { |
|||
display: block; |
|||
padding-bottom: 2px; |
|||
width: 100% |
|||
} |
|||
textarea { |
|||
width: 100%; |
|||
height: 100%; |
|||
box-sizing: border-box; |
|||
min-height: 250px; |
|||
background: #E0B0FF; |
|||
outline: none; |
|||
font-family: Courier, sans-serif; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
iframe { |
|||
bottom: 0; |
|||
position: relative; |
|||
margin-top: 1em; |
|||
width: 100%; |
|||
height: 30em; |
|||
} |
|||
#html { |
|||
background-color: #60337F; |
|||
color: lightgrey; |
|||
} |
@ -0,0 +1,22 @@ |
|||
div#login{ |
|||
width: 30%; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
background-color:#fdfdfd; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
div#login form { |
|||
width: 24em; |
|||
margin: 0 auto; |
|||
padding-left: 15%; |
|||
padding-right: 15%; |
|||
} |
|||
|
|||
input[type=text], input[type=password], input[type=file] { |
|||
color: #091411; |
|||
width: 18em; |
|||
max-width: 18em; |
|||
background-color: #fdfdfd; |
|||
border: 1px solid #9de457; |
|||
} |
@ -1,53 +0,0 @@ |
|||
.selector-style { |
|||
padding: 0; |
|||
width: 20em; |
|||
max-width: 20em; |
|||
position: relative; |
|||
border: none; |
|||
background: #E0B0FF; |
|||
text-decoration: none; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
margin: 1px; |
|||
} |
|||
|
|||
.selector-style select { |
|||
padding: 0.2em 0.2em; |
|||
width: 20em; |
|||
max-width: 20em; |
|||
border: none; |
|||
box-shadow: none; |
|||
background-color: #E0B0FF; |
|||
background-image: none; |
|||
-webkit-appearance: none; |
|||
-moz-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
.selector-style:after { |
|||
top: 50%; |
|||
left: 95%; |
|||
border: solid; |
|||
content: " "; |
|||
height: 0; |
|||
width: 0; |
|||
position: absolute; |
|||
pointer-events: none; |
|||
border-color: rgba(0, 0, 0, 0); |
|||
border-top-color: #000000; |
|||
margin-top: -2px; |
|||
z-index: 100; |
|||
} |
|||
select.selector option{ |
|||
color: white; |
|||
background-color: #60337F; |
|||
padding: 0 10px; |
|||
} |
|||
|
|||
.selector-style select:focus { |
|||
outline: none; |
|||
} |
|||
.selector-style select option:hover { |
|||
background: #60337F; |
|||
} |
@ -1,281 +0,0 @@ |
|||
body |
|||
{ |
|||
font-family: monospace, monospace; |
|||
font-size: 15px; |
|||
background-color: #fdfdfd; |
|||
color:#29d148; |
|||
word-wrap: break-word; |
|||
line-height: 1.1; |
|||
} |
|||
|
|||
div#login{ |
|||
width: 30%; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
background-color:#fdfdfd; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
div#login form { |
|||
width: 24em; |
|||
margin: 0 auto; |
|||
padding-left: 15%; |
|||
padding-right: 15%; |
|||
} |
|||
|
|||
input[type=text], input[type=password], input[type=file] { |
|||
color: #C397DF; |
|||
width: 18em; |
|||
max-width: 18em; |
|||
background-color: #fdfdfd; |
|||
border: 1px solid #E0B0FF; |
|||
} |
|||
|
|||
div#upload form { |
|||
padding-right: 15%; |
|||
} |
|||
|
|||
.workflow{ |
|||
margin-top: 1em; |
|||
padding: 0.5em; |
|||
padding-left: auto; |
|||
padding-right: auto; |
|||
width: 31em; |
|||
background-color:#fdfdfd; |
|||
text-decoration: none; |
|||
scroll-behavior: smooth; |
|||
border-style: outset; |
|||
} |
|||
.workflow > p { |
|||
padding-left: 1em; |
|||
} |
|||
.workflow > h2 { |
|||
padding-left: 0.4em;; |
|||
} |
|||
|
|||
.workflow input{ |
|||
max-width: 20em; |
|||
} |
|||
|
|||
#mainworkflow |
|||
{ |
|||
width: 30em; |
|||
margin:0 auto; |
|||
} |
|||
|
|||
#distribusiverse { |
|||
margin-bottom: 11em; |
|||
} |
|||
#distribusi-index { |
|||
padding-left: 1em; |
|||
} |
|||
|
|||
div#buttons{ |
|||
position: fixed; |
|||
top: 0.5em; |
|||
right: 0.5em; |
|||
display:flex; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
div#buttons .distribusi input{ |
|||
border: none; |
|||
background: #fff600; |
|||
text-decoration: none; |
|||
margin: 0.2em; |
|||
} |
|||
div#buttons .distribusi input:hover{ |
|||
background: #ffbf00; |
|||
|
|||
} |
|||
fieldset.required { |
|||
border: none; |
|||
} |
|||
|
|||
fieldset.required > ul { |
|||
padding-left: 0px; |
|||
} |
|||
|
|||
fieldset.required > ul > li{ |
|||
list-style-type: none; |
|||
} |
|||
fieldset.tagfield > input { |
|||
width: 100%; |
|||
max-width: 100%; |
|||
} |
|||
#publicthemes > ul { |
|||
max-height: 20em; |
|||
overflow: auto; |
|||
} |
|||
#publicthemes > ul > li{ |
|||
word-break: break-all; |
|||
} |
|||
|
|||
input { |
|||
border: none; |
|||
background: #E0B0FF; |
|||
text-decoration: none; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
margin: 0.2em; |
|||
} |
|||
|
|||
input:hover { |
|||
background: #60337F; |
|||
} |
|||
|
|||
input[type="submit"]:disabled:hover, |
|||
input[type="submit"]:disabled, |
|||
input[type="submit"]:disabled:focus { |
|||
background-color: #2D3039; |
|||
color: #d28cff; |
|||
} |
|||
|
|||
.error { |
|||
font-size: 110%; |
|||
color: #F92020; |
|||
} |
|||
|
|||
#delete { |
|||
color: black; |
|||
background-color: #F92020; |
|||
} |
|||
|
|||
#update { |
|||
color: black; |
|||
background-color: #62b264; |
|||
} |
|||
|
|||
|
|||
/* unvisited link */ |
|||
a:link { |
|||
color: #fff600; |
|||
} |
|||
|
|||
/* visited link */ |
|||
a:visited { |
|||
color: #d28cff; |
|||
} |
|||
|
|||
/* mouse over link */ |
|||
a:hover { |
|||
color: #60337F; |
|||
} |
|||
|
|||
/* selected link */ |
|||
a:active { |
|||
color: white; |
|||
} |
|||
|
|||
/* STOLEN GOODS */ |
|||
#fancyboi::before { |
|||
content: "$ "; |
|||
} |
|||
|
|||
@media (prefers-reduced-motion: no-preference) { |
|||
@keyframes flash { |
|||
50% { opacity: 0; } |
|||
} |
|||
@keyframes reveal { |
|||
from { width: 2em; } /* Width of ::before */ |
|||
to { width: 55%; } |
|||
} |
|||
#fancyboi { |
|||
width: 55%; |
|||
padding: 0.5em; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
animation: reveal 4s linear; |
|||
text-overflow: "█"; |
|||
background-color: #2D3039; |
|||
} |
|||
#fancyboi::after { |
|||
content: "█"; |
|||
animation: flash 0.5s step-end infinite; |
|||
} |
|||
} |
|||
|
|||
div.maincontent{ |
|||
width: 55%; |
|||
border: 3px #E0B0FF; |
|||
margin-top: 0.5em; |
|||
padding: 0.5em; |
|||
border-style: outset; |
|||
} |
|||
|
|||
.tags{ |
|||
background-color: #000; |
|||
color: #fff; |
|||
display: inline-block; |
|||
padding-left: 4px; |
|||
padding-right: 4px; |
|||
text-align: center; |
|||
margin: 1px; |
|||
} |
|||
|
|||
.searched { |
|||
background: #fff600 !important; |
|||
color: black !important; |
|||
} |
|||
|
|||
.tooltip { |
|||
position: relative; |
|||
display: inline-block; |
|||
border-bottom: 1px dotted black; |
|||
} |
|||
|
|||
.tooltip .tooltiptext { |
|||
visibility: hidden; |
|||
width: 120px; |
|||
background-color: black; |
|||
color: #fff; |
|||
text-align: center; |
|||
padding: 5px 0; |
|||
position: absolute; |
|||
z-index: 1; |
|||
bottom: 100%; |
|||
left: 50%; |
|||
margin-left: -60px; |
|||
|
|||
/* Fade in tooltip - takes 1 second to go from 0% to 100% opac: */ |
|||
opacity: 0; |
|||
transition: opacity 2s; |
|||
} |
|||
|
|||
.tooltip:hover .tooltiptext { |
|||
visibility: visible; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.code-example { |
|||
width: 100%; |
|||
color: black; |
|||
padding: 1em; |
|||
box-sizing: border-box; |
|||
background: #E0B0FF; |
|||
outline: none; |
|||
font-family: Courier, sans-serif; |
|||
font-size: 16px; |
|||
} |
|||
/* |
|||
Project colors so far. |
|||
light |
|||
#E0B0FF |
|||
medium |
|||
#d28cff |
|||
dark |
|||
#60337F |
|||
|
|||
background dark |
|||
#2D3039 |
|||
|
|||
yellow important |
|||
#fff600 |
|||
|
|||
red: danger |
|||
ff5a5a |
|||
backgrounds |
|||
*/ |
@ -1,6 +0,0 @@ |
|||
This favicon was generated using the following font: |
|||
|
|||
- Font Title: Klee One |
|||
- Font Author: Copyright 2020 The Klee Project Authors (https://github.com/fontworks-fonts/Klee) |
|||
- Font Source: http://fonts.gstatic.com/s/kleeone/v5/LDIxapCLNRc6A8oT4q4AOeekWPrP.ttf |
|||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL)) |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 322 B |
Before Width: | Height: | Size: 666 B |
Before Width: | Height: | Size: 15 KiB |
@ -1 +0,0 @@ |
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} |
@ -1,92 +0,0 @@ |
|||
filterSelection("all", "None"); |
|||
function filterSelection(c, name, id) { |
|||
resetDropDownButtons(); |
|||
var i; |
|||
var button = document.getElementById(id); |
|||
if(button){ |
|||
button.innerText = name; |
|||
addClass(button, "activebtn"); |
|||
} |
|||
var alldistribusis = document.getElementsByClassName("filter"); |
|||
if (c == "all") { |
|||
for (i = 0; i < alldistribusis.length; i++) { |
|||
addClass(alldistribusis[i], "show"); |
|||
} |
|||
} |
|||
else { |
|||
for (i = 0; i < alldistribusis.length; i++) { |
|||
removeClass(alldistribusis[i], "show"); |
|||
if (alldistribusis[i].className.indexOf(c) > -1) { |
|||
addClass(alldistribusis[i], "show"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function resetDropDownButtons(){ |
|||
document.getElementById("Year").innerText = "Year"; |
|||
document.getElementById("Category").innerText = "Category"; |
|||
allactivebuttons = document.getElementsByClassName("activebtn"); |
|||
for(var i = 0;allactivebuttons.length; i++) { |
|||
removeClass(allactivebuttons[i], "activebtn"); |
|||
} |
|||
} |
|||
function addClass(element, name) { |
|||
var i, arr1, arr2; |
|||
arr1 = element.className.split(" "); |
|||
arr2 = name.split(" "); |
|||
for (i = 0; i < arr2.length; i++) { |
|||
if (arr1.indexOf(arr2[i]) == -1) {element.className += " " + arr2[i];} |
|||
} |
|||
} |
|||
|
|||
function removeClass(element, name) { |
|||
var i, arr1, arr2; |
|||
arr1 = element.className.split(" "); |
|||
arr2 = name.split(" "); |
|||
for (i = 0; i < arr2.length; i++) { |
|||
while (arr1.indexOf(arr2[i]) > -1) { |
|||
arr1.splice(arr1.indexOf(arr2[i]), 1); |
|||
} |
|||
} |
|||
element.className = arr1.join(" "); |
|||
} |
|||
|
|||
|
|||
let searchInput = document.getElementById('tagsearch'); |
|||
let timeout = null; |
|||
// Listen for keystroke events
|
|||
searchInput.addEventListener('keyup', function (e) { |
|||
// Clear the timeout if it has already been set.
|
|||
clearTimeout(timeout); |
|||
// Make a new timeout set to go off in 1000ms (1 second)
|
|||
timeout = setTimeout(function () { |
|||
console.log('Input Value:', searchInput.value); |
|||
if (searchInput.value.length > 2) { |
|||
searchTags(searchInput.value); |
|||
} else { |
|||
clearSearchTags(); |
|||
} |
|||
}, 1000); |
|||
}); |
|||
|
|||
function searchTags(searchInput) { |
|||
let tag_ele = document.getElementsByClassName('tags'); |
|||
for (var i = 0; i < tag_ele.length; ++i) { |
|||
let searchText = searchInput.toLowerCase().trim(); |
|||
let tagtext = tag_ele[i].innerText.toLowerCase(); |
|||
if(searchText.includes(tagtext) || tagtext.includes(searchText)) { |
|||
addClass(tag_ele[i], "searched"); |
|||
} |
|||
else { |
|||
removeClass(tag_ele[i], "searched"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function clearSearchTags() { |
|||
let tag_ele = document.getElementsByClassName('tags'); |
|||
for (var i = 0; i < tag_ele.length; ++i) { |
|||
removeClass(tag_ele[i], "searched"); |
|||
} |
|||
} |
@ -1,19 +0,0 @@ |
|||
function update() { |
|||
|
|||
var html = document.getElementById("html"); |
|||
var css = document.getElementById("css"); |
|||
var code = document.getElementById("code").contentWindow.document; |
|||
|
|||
document.body.onkeyup = function(){ |
|||
code.open(); |
|||
code.writeln(html.value+"<style>"+css.value+"</style>"); |
|||
code.close(); |
|||
}; |
|||
document.addEventListener("DOMContentLoaded", function(){ |
|||
code.open(); |
|||
code.writeln(html.value+"<style>"+css.value+"</style>"); |
|||
code.close(); |
|||
}); |
|||
}; |
|||
|
|||
update(); |
@ -1,25 +0,0 @@ |
|||
console.log("everything is still smooth") |
|||
|
|||
function scrollToTheme() { |
|||
var uploadsuccessful = document.getElementById("uploadsuccessful"); |
|||
if(uploadsuccessful){ |
|||
const theme = document.getElementById('theme') |
|||
theme.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|||
} |
|||
} |
|||
|
|||
function scrollToLaunch() { |
|||
var cssSelected = document.getElementById("cssSelected"); |
|||
if(cssSelected){ |
|||
const launch = document.getElementById('launch') |
|||
launch.scrollIntoView({ behavior: 'smooth', block: 'end' }); |
|||
} |
|||
} |
|||
|
|||
|
|||
document.addEventListener("DOMContentLoaded", scrollToTheme); |
|||
document.addEventListener("DOMContentLoaded", scrollToLaunch); |
|||
|
|||
// function(e) {
|
|||
// (e.keyCode === 13 || e.keyCode === 32) && $(this).trigger("click")
|
|||
// }
|
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.5 KiB |