diff --git a/.gitignore b/.gitignore index 705c21e..f273bbd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,12 @@ *.pyc *.egg-info/ .eggs/ +.ruff_cache build/ dist/ pip-wheel-metadata/ verse/tmpupload/* verse/stash/* +verse/search/searchdata/* *.db diff --git a/pyproject.toml b/pyproject.toml index 9b286c9..665a9ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 diff --git a/requirements.txt b/requirements.txt index 952979d..374fc78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,9 +6,9 @@ black==24.4.0 bleach==4.1.0 bleach-allowlist==1.0.3 blinker==1.7.0 -cffi==1.15.0 +cffi click==8.1.7 -distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi@e291e7497e40211c2ebd54ca32a1f4bdaed71230 +distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi@3eefd6e5ca7048555d441df8c6fbf4f2e255acac dnspython==2.1.0 email-validator==1.1.3 Flask==3.0.3 @@ -36,7 +36,7 @@ neovim==0.3.1 packaging==24.0 passlib==1.7.4 pathspec==0.9.0 -Pillow==8.3.2 +Pillow platformdirs==2.4.0 pycparser==2.21 pynvim==0.5.0 @@ -44,7 +44,7 @@ pyparsing==3.0.7 python-dateutil==2.9.0.post0 python-magic==0.4.24 pytz==2021.3 -regex==2021.11.10 +regex six==1.16.0 speaklater==1.3 SQLAlchemy==2.0.29 diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 94548af..0000000 --- a/src/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -*/ -!.gitignore diff --git a/verse/admin.py b/verse/admin.py new file mode 100644 index 0000000..f95623b --- /dev/null +++ b/verse/admin.py @@ -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 diff --git a/verse/adminpage.py b/verse/admin_page/admin_page.py similarity index 57% rename from verse/adminpage.py rename to verse/admin_page/admin_page.py index 4d78d26..faf46ac 100644 --- a/verse/adminpage.py +++ b/verse/admin_page/admin_page.py @@ -1,7 +1,8 @@ import os import shutil -from flask import render_template +from flask import render_template, Blueprint +from flask_login import current_user, login_required from sqlalchemy.exc import ( DatabaseError, DataError, @@ -10,22 +11,34 @@ from sqlalchemy.exc import ( ) from app import db -from distribusikan.distribusisinfo import DistribusisInfo -from forms.admindistribusiform import AdminDistribusiForm -from forms.adminuserform import AdminUserForm +from admin import is_adminuser +from distribusikan.distribusis_info import DistribusisInfo +from admin_page.forms.admindistribusiform import AdminDistribusiForm +from admin_page.forms.adminuserform import AdminUserForm from models.distribusi_model import Distribusis from models.user_model import User +admin_page = Blueprint( + "admin", + __name__, + template_folder="templates/describe_files", + static_folder="static", +) + -def AdminPage(): - adminuserform = AddUsersToForm(AdminUserForm()) - admindistribusiform = AddDistribusisToForm(AdminDistribusiForm()) +@admin_page.route("/admin", methods=["GET", "POST"]) +@login_required +def admin(): + if not is_adminuser(): + return redirect(url_for("index")) + adminuserform = add_users_to_form(AdminUserForm()) + admindistribusiform = add_distribusis_to_form(AdminDistribusiForm()) if admindistribusiform.validate_on_submit(): - DeleteDistribusis(admindistribusiform) + delete_distribusis(admindistribusiform) if adminuserform.validate_on_submit(): if adminuserform.delete.data: - DeleteUsers(adminuserform) + delete_users(adminuserform) template = render_template( "admin.html", @@ -35,19 +48,18 @@ def AdminPage(): return template -def DeleteUsers(adminuserform): +def delete_users(adminuserform): for userform in adminuserform: if "user" in userform.id: if userform.data: useremail = userform.label.text user = User.query.filter_by(email=useremail).first() - DeleteUserDistribusis(user) - DeleteUserFromDb(user) + delete_User_distribusis(user) + delete_user_from_db(user) userform.errors.append(f"User {useremail} deleted!") - -def DeleteUserFromDb(user): +def delete_user_from_db(user): try: db.session.delete(user) db.session.commit() @@ -55,14 +67,14 @@ def DeleteUserFromDb(user): db.session.rollback() -def DeleteUserDistribusis(user): - distribusis = DistribusisInfo.getuserdistribusis(user.email) +def delete_User_distribusis(user): + distribusis = DistribusisInfo.get_user_distribusis(user.email) for distribusi in distribusis: - DeleteDistribusiFiles(distribusi.distribusiname) - DeleteDistribusiFromDb(distribusi) + delete_distribusi_files(distribusi.distribusiname) + delete_distribusi_from_db(distribusi) -def DeleteDistribusis(admindistribusiform): +def delete_distribusis(admindistribusiform): for distribusiform in admindistribusiform: if "distribusi" in distribusiform.id: if distribusiform.data: @@ -70,12 +82,12 @@ def DeleteDistribusis(admindistribusiform): distribusi = Distribusis.query.filter_by( distribusiname=distribusiname ).first() - DeleteDistribusiFromDb(distribusi) - DeleteDistribusiFiles(distribusiname) + delete_distribusi_from_db(distribusi) + delete_distribusi_files(distribusiname) distribusiform.errors.append("Deleted distribusi") -def DeleteDistribusiFromDb(distribusi): +def delete_distribusi_from_db(distribusi): try: db.session.delete(distribusi) db.session.commit() @@ -83,7 +95,7 @@ def DeleteDistribusiFromDb(distribusi): db.session.rollback() -def DeleteDistribusiFiles(distribusiname): +def delete_distribusi_files(distribusiname): userfolder = os.path.join("stash", distribusiname) if os.path.exists(userfolder): shutil.rmtree(userfolder) @@ -92,15 +104,15 @@ def DeleteDistribusiFiles(distribusiname): shutil.rmtree(cssfolder) -def AddDistribusisToForm(admindistribusiform): - distribusis = DistribusisInfo.visibledistribusis() +def add_distribusis_to_form(admindistribusiform): + distribusis = DistribusisInfo.visible_distribusis() admindistribusiform = AdminDistribusiForm.distribusi_list_form_builder( distribusis ) return admindistribusiform -def AddUsersToForm(adminuserform): +def add_users_to_form(adminuserform): users = User.query.all() adminuserform = AdminUserForm.user_list_form_builder(users) return adminuserform diff --git a/verse/forms/admindistribusiform.py b/verse/admin_page/forms/admindistribusiform.py similarity index 100% rename from verse/forms/admindistribusiform.py rename to verse/admin_page/forms/admindistribusiform.py diff --git a/verse/forms/adminuserform.py b/verse/admin_page/forms/adminuserform.py similarity index 100% rename from verse/forms/adminuserform.py rename to verse/admin_page/forms/adminuserform.py diff --git a/verse/templates/base/admin.html b/verse/admin_page/templates/admin.html similarity index 100% rename from verse/templates/base/admin.html rename to verse/admin_page/templates/admin.html diff --git a/verse/app.py b/verse/app.py index 56dd762..ff47f2b 100644 --- a/verse/app.py +++ b/verse/app.py @@ -8,6 +8,7 @@ from flask_migrate import Migrate from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect + APP = Flask(__name__, static_folder="static") db = SQLAlchemy() migrate = Migrate() @@ -19,8 +20,8 @@ def create_app(): APP.secret_key = os.urandom(24) APP.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///distribusiverse.db" APP.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True - APP.config["MAX_CONTENT_LENGTH"] = 1024 * 1024 * 1024 + APP.config["MAX_CONTENT_LENGTH"] = 1024 * 1024 * 1024 APP.config["MAIL_SERVER"] = "mail.autonomic.zone" APP.config["MAIL_PORT"] = 587 APP.config["MAIL_USE_SSL"] = False @@ -64,7 +65,6 @@ def get_app(): def settings_from_file(): - settings = {} if os.path.isfile("settings_development.toml"): with open("settings_development.toml", "rb") as settings_file: return tomllib.load(settings_file) diff --git a/verse/deploydb.py b/verse/deploydb.py index 9a33cdc..30a8364 100644 --- a/verse/deploydb.py +++ b/verse/deploydb.py @@ -4,6 +4,7 @@ def deploy(): from app import create_app, db from models.distribusi_model import Distribusis # noqa: F401 + from models.distribusi_file_model import DistribusiFiles # noqa: F401 # This model is required for flask_migrate to make the table from models.user_model import User # noqa: F401 diff --git a/verse/describer/describe_files.py b/verse/describer/describe_files.py new file mode 100644 index 0000000..f53c329 --- /dev/null +++ b/verse/describer/describe_files.py @@ -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("/") +@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/", 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/", 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/") +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 diff --git a/verse/describer/forms/describe_files_form.py b/verse/describer/forms/describe_files_form.py new file mode 100644 index 0000000..6155be4 --- /dev/null +++ b/verse/describer/forms/describe_files_form.py @@ -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', {})}) diff --git a/verse/describer/forms/redistribusi_form.py b/verse/describer/forms/redistribusi_form.py new file mode 100644 index 0000000..1d7676a --- /dev/null +++ b/verse/describer/forms/redistribusi_form.py @@ -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!") diff --git a/verse/describer/static/css/describer.css b/verse/describer/static/css/describer.css new file mode 100644 index 0000000..c80d69c --- /dev/null +++ b/verse/describer/static/css/describer.css @@ -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%; +} diff --git a/verse/describer/templates/describe_files/describe.html b/verse/describer/templates/describe_files/describe.html new file mode 100644 index 0000000..65d7120 --- /dev/null +++ b/verse/describer/templates/describe_files/describe.html @@ -0,0 +1,78 @@ +{% extends "base/base.html" %} +{% block main %} +
+ {% if current_user.is_authenticated %} +
+ + + +
+ {% if adminuser %} +
+ + + +
+ {% endif %} +
+ + + +
+ {% endif %} +
+ + + +
+
+
+
+ {% for id, describe_form in distribusi_file_forms.items() %} +
+ {% if describe_form.type == "image" %} + + {% elif describe_form.type == "video" %} + + {% elif describe_form.type == "audio" %} + + {% else %} + file: {{describe_form.file_path}} + {% endif %} +
+ {{ describe_form.csrf_token }} +

File path: {{describe_form.file_path}}

+
+ {{ describe_form.description.label }} + {{ describe_form.description }} + {% for message in describe_form.description.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ describe_form.searchtags.label }} + {{ describe_form.searchtags }} + {% for message in describe_form.searchtags.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ describe_form.alttext.label }} + {{ describe_form.alttext }} + {% for message in describe_form.alttext.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ describe_form.save }} +
+
+
+ {% endfor%} +
+ {% block redistribusi %} + {% include "redistribusi.html" %} + {% endblock redistribusi%} +
+ +{% endblock %} diff --git a/verse/describer/templates/describe_files/redistribusi.html b/verse/describer/templates/describe_files/redistribusi.html new file mode 100644 index 0000000..d373311 --- /dev/null +++ b/verse/describer/templates/describe_files/redistribusi.html @@ -0,0 +1,13 @@ +
+

Run distribusi again after describing your files.Distribusi will run again and add your alttext and descriptions. +

+
+ {{ redistribusi_form.csrf_token }} +
+ {{ redistribusi_form.submit }} + {% for message in redistribusi_form.submit.errors %} +
{{ message }}
+ {% endfor %} +
+
+
diff --git a/verse/distribusikan/add_files_to_describer.py b/verse/distribusikan/add_files_to_describer.py new file mode 100644 index 0000000..466f912 --- /dev/null +++ b/verse/distribusikan/add_files_to_describer.py @@ -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) diff --git a/verse/distribusikan/editor.py b/verse/distribusikan/css_editor_page.py similarity index 69% rename from verse/distribusikan/editor.py rename to verse/distribusikan/css_editor_page.py index 88e63c2..8e9997e 100644 --- a/verse/distribusikan/editor.py +++ b/verse/distribusikan/css_editor_page.py @@ -13,28 +13,28 @@ from sqlalchemy.exc import ( from werkzeug.utils import secure_filename from app import db -from distribusikan.distribusisinfo import DistribusisInfo -from forms.distribusiform import DistribusiForm -from forms.editorform import EditorForm -from forms.publicthemeform import PublicThemeForm -from forms.selectorform import SelectorForm -from forms.themeform import ThemeForm -from forms.uploadform import UploadForm +from distribusikan.distribusis_info import DistribusisInfo +from distribusikan.forms.distribusiform import DistribusiForm +from distribusikan.forms.editorform import EditorForm +from distribusikan.forms.publicthemeform import PublicThemeForm +from distribusikan.forms.selectorform import SelectorForm +from distribusikan.forms.themeform import ThemeForm +from distribusikan.forms.uploadform import UploadForm from models.distribusi_model import Distribusis from statuspengguna.helper import UserHelper -def Editor(): +def css_editor_page(): editorform = EditorForm() current_distribusi = UserHelper.current_distribusi() if editorform.validate_on_submit(): - ValidateEditCssForm(editorform, current_distribusi) - return RenderDistribusiTemplate(current_distribusi) + validate_edit_css_form(editorform, current_distribusi) + return render_distribusi_template(current_distribusi) - return RenderEditorTemplate(editorform, current_distribusi) + return render_editor_template(editorform, current_distribusi) -def ValidateEditCssForm(editorform, current_distribusi): +def validate_edit_css_form(editorform, current_distribusi): newcssfolder = os.path.join("themes/userthemes", current_distribusi) if os.path.exists(newcssfolder): shutil.rmtree(newcssfolder) @@ -44,22 +44,22 @@ def ValidateEditCssForm(editorform, current_distribusi): shutil.rmtree(publicfolder) if editorform.public.data: - MakePublicTheme(editorform, current_distribusi) + make_public_theme(editorform, current_distribusi) if editorform.cssfile.data: - SaveUploadCssFile(editorform, publicfolder) - CopyPublicToUserFolder(editorform, publicfolder, newcssfolder) + save_upload_css_file(editorform, publicfolder) + copy_public_to_user_folder(editorform, publicfolder, newcssfolder) return else: - WriteCssToFile(editorform, publicfolder) + write_css_to_file(editorform, publicfolder) if editorform.cssfile.data: - SaveUploadCssFile(editorform, newcssfolder) + save_upload_css_file(editorform, newcssfolder) return if editorform.cssname.data: - WriteCssToFile(editorform, newcssfolder) + write_css_to_file(editorform, newcssfolder) -def SaveUploadCssFile(editorform, newcssfolder): +def save_upload_css_file(editorform, newcssfolder): if not os.path.exists(newcssfolder): os.mkdir(newcssfolder) cssfile = editorform.cssfile.data @@ -74,7 +74,7 @@ def SaveUploadCssFile(editorform, newcssfolder): cleanfile.close() -def WriteCssToFile(editorform, newcssfolder): +def write_css_to_file(editorform, newcssfolder): if not os.path.exists(newcssfolder): os.mkdir(newcssfolder) @@ -86,7 +86,7 @@ def WriteCssToFile(editorform, newcssfolder): cssfile.close -def CopyPublicToUserFolder(editorform, publicfolder, newcssfolder): +def copy_public_to_user_folder(editorform, publicfolder, newcssfolder): if not os.path.exists(newcssfolder): os.mkdir(newcssfolder) copycssfile = os.path.join( @@ -97,7 +97,7 @@ def CopyPublicToUserFolder(editorform, publicfolder, newcssfolder): shutil.copy(copycssfile, newcssfolder) -def MakePublicTheme(editorform, current_distribusi): +def make_public_theme(editorform, current_distribusi): try: distribusi = Distribusis.query.filter_by( distribusiname=current_distribusi @@ -119,12 +119,12 @@ def MakePublicTheme(editorform, current_distribusi): editorform.public.errors.append("Error connecting to the database") -def RenderDistribusiTemplate(current_distribusi): +def render_distribusi_template(current_distribusi): uploadform = UploadForm() distribusiform = DistribusiForm() themeform = ThemeForm() publicthemeform = PublicThemeForm() - publicthemeform.publicthemes.choices = DistribusisInfo.publicthemes() + publicthemeform.publicthemes.choices = DistribusisInfo.public_themes() selectorform = SelectorForm() files_uploaded = UserHelper.is_zip_uploaded(current_distribusi) @@ -148,11 +148,11 @@ def RenderDistribusiTemplate(current_distribusi): return template -def RenderEditorTemplate(editorform, current_distribusi): - htmlplaceholder = HtmlPlaceholder() +def render_editor_template(editorform, current_distribusi): + html_placeholder = get_html_placeholder() - cssplaceholder = CssPlaceholder(current_distribusi) - editorform.css.data = cssplaceholder + css_placeholder = get_css_placeholder(current_distribusi) + editorform.css.data = css_placeholder files_uploaded = UserHelper.is_zip_uploaded(current_distribusi) distribusi_live = UserHelper.is_distribusi_live(current_distribusi) @@ -161,33 +161,33 @@ def RenderEditorTemplate(editorform, current_distribusi): files_uploaded=files_uploaded, distribusi_live=distribusi_live, editorform=editorform, - htmlplaceholder=htmlplaceholder, + html_placeholder=html_placeholder, ) return template -def CssPlaceholder(current_distribusi): - cssplaceholder = "Try out your CSS here" +def get_css_placeholder(current_distribusi): + css_placeholder = "Try out your CSS here" distribusi = Distribusis.query.filter_by( distribusiname=current_distribusi ).first() if distribusi is not None and distribusi.publictheme is not None: - cssplaceholder = GetPublicCssFile(distribusi) + css_placeholder = get_public_css_file(distribusi) else: with open("themes/editor/placeholder.css") as f: - cssplaceholder = f.read() - return cssplaceholder + css_placeholder = f.read() + return css_placeholder -def HtmlPlaceholder(): - htmlplaceholder = "Write some test HTML here" +def get_html_placeholder(): + html_placeholder = "Write some test HTML here" with open("themes/editor/placeholder.html") as f: - htmlplaceholder = f.read() - return htmlplaceholder + html_placeholder = f.read() + return html_placeholder -def GetPublicCssFile(distribusi): - cssplaceholder = "" +def get_public_css_file(distribusi): + css_placeholder = "" publicthemefolder = os.path.join( "themes/publicthemes", distribusi.distribusiname ) @@ -195,5 +195,5 @@ def GetPublicCssFile(distribusi): if filename.endswith(".css"): cssfile = os.path.join(publicthemefolder, filename) with open(cssfile) as f: - cssplaceholder = f.read() - return cssplaceholder + css_placeholder = f.read() + return css_placeholder diff --git a/verse/distribusikan/distribusiselector.py b/verse/distribusikan/distribusi_selector.py similarity index 69% rename from verse/distribusikan/distribusiselector.py rename to verse/distribusikan/distribusi_selector.py index ccc5770..b47b227 100644 --- a/verse/distribusikan/distribusiselector.py +++ b/verse/distribusikan/distribusi_selector.py @@ -1,7 +1,7 @@ import os import shutil -from flask import flash, render_template +from flask import flash, render_template, redirect, url_for from flask_login import current_user from sqlalchemy.exc import ( DatabaseError, @@ -11,12 +11,12 @@ from sqlalchemy.exc import ( ) from app import db -from distribusikan.distribusisinfo import DistribusisInfo -from forms.distribusiform import DistribusiForm -from forms.publicthemeform import PublicThemeForm -from forms.selectorform import SelectorForm -from forms.themeform import ThemeForm -from forms.uploadform import UploadForm +from distribusikan.distribusis_info import DistribusisInfo +from distribusikan.forms.distribusiform import DistribusiForm +from distribusikan.forms.publicthemeform import PublicThemeForm +from distribusikan.forms.selectorform import SelectorForm +from distribusikan.forms.themeform import ThemeForm +from distribusikan.forms.uploadform import UploadForm from models.distribusi_model import Distribusis from models.user_model import User @@ -24,52 +24,66 @@ from models.user_model import User from statuspengguna.helper import UserHelper -def DistribusiSelector(): +def distribusi_selector(): uploadform = UploadForm() selectorform = SelectorForm() - selectorform.distribusis.choices = DistribusisInfo.userdistribusinames() + selectorform.distribusis.choices = DistribusisInfo.user_distribusinames() current_distribusi = UserHelper.current_distribusi() if selectorform.validate_on_submit(): if selectorform.new.data: - SelectNewDistribusi() + select_new_distribusi() + if selectorform.describe.data: + return select_describe_distribusi(selectorform.distribusis.data) if selectorform.delete.data: - selectorform = DeleteDistribusi(selectorform.distribusis.data) + selectorform = delete_distribusi(selectorform.distribusis.data) selectorform.distribusis.choices = ( - DistribusisInfo.userdistribusinames() + DistribusisInfo.user_distribusinames() ) if selectorform.update.data: - SelectUpdateDistribusi(selectorform.distribusis.data) + select_update_distribusi(selectorform.distribusis.data) current_distribusi = UserHelper.current_distribusi() - uploadform = AutoFillInUploadForm(uploadform, current_distribusi) + uploadform = auto_fill_in_upload_form( + uploadform, current_distribusi + ) - return RenderDistribusiTemplate( + return render_distribusi_template( selectorform, uploadform, current_distribusi ) -def AutoFillInUploadForm(uploadform, current_distribusi): +def auto_fill_in_upload_form(uploadform, current_distribusi): distribusi = Distribusis.query.filter_by( distribusiname=current_distribusi ).first() uploadform.sitename.data = distribusi.distribusiname uploadform.sitename.render_kw = {"readonly": True} + uploadform.description.data = distribusi.description uploadform.category.data = distribusi.category uploadform.year.data = distribusi.year uploadform.tags.data = distribusi.tags return uploadform -def SelectNewDistribusi(): +def select_new_distribusi(): print("make a new distribusi") - SelectCurrentDistribusi("new") + select_current_distribusi("new") + + +def select_describe_distribusi(distribusiname): + return redirect( + url_for( + "describer.show_distribusi_files", + distribusiname=distribusiname, + ) + ) -def SelectUpdateDistribusi(distribusiname): +def select_update_distribusi(distribusiname): print(f"Update this distribusi {distribusiname}") - SelectCurrentDistribusi(distribusiname) + select_current_distribusi(distribusiname) -def DeleteDistribusi(distribusiname): +def delete_distribusi(distribusiname): print(f"delete this distribusi {distribusiname}") selectorform = SelectorForm() try: @@ -103,7 +117,7 @@ def DeleteDistribusi(distribusiname): return selectorform -def SelectCurrentDistribusi(distribusiname): +def select_current_distribusi(distribusiname): if not current_user.is_authenticated: return user = User.query.filter_by(email=current_user.email).first() @@ -115,35 +129,35 @@ def SelectCurrentDistribusi(distribusiname): flash("An error occured !", "danger") -def DistribusiSelected(): +def distribusi_selected(): user = User.query.filter_by(email=current_user.email).first() if user.currentdistribusi is None: return False return True -def SelectorVisible(): +def selector_visible(): has_distribusi = UserHelper.has_distribusi() - distribusi_selected = DistribusiSelected() - if distribusi_selected: + is_distribusi_selected = distribusi_selected() + if is_distribusi_selected: return False if not has_distribusi: return False return True -def RenderDistribusiTemplate(selectorform, uploadform, current_distribusi): +def render_distribusi_template(selectorform, uploadform, current_distribusi): distribusiform = DistribusiForm() themeform = ThemeForm() publicthemeform = PublicThemeForm() - publicthemeform.publicthemes.choices = DistribusisInfo.publicthemes() + publicthemeform.publicthemes.choices = DistribusisInfo.public_themes() files_uploaded = UserHelper.is_zip_uploaded(current_distribusi) distribusi_live = UserHelper.is_distribusi_live(current_distribusi) # because the user has chosen to update his distribusi, we assume # no selected css. css_selected = False - selectorvisible = SelectorVisible() + selectorvisible = selector_visible() limit_reached = UserHelper.distribusi_limit_reached() template = render_template( "distribusi.html", diff --git a/verse/distribusikan/distribusiworkflow.py b/verse/distribusikan/distribusi_workflow.py similarity index 67% rename from verse/distribusikan/distribusiworkflow.py rename to verse/distribusikan/distribusi_workflow.py index 4e3a376..f9977fb 100644 --- a/verse/distribusikan/distribusiworkflow.py +++ b/verse/distribusikan/distribusi_workflow.py @@ -15,15 +15,16 @@ from sqlalchemy.exc import ( ) from app import db -from distribusikan.distribusiselector import SelectorVisible -from distribusikan.distribusisinfo import DistribusisInfo -from forms.distribusiform import DistribusiForm -from forms.publicthemeform import PublicThemeForm -from forms.selectorform import SelectorForm -from forms.themeform import ThemeForm +from distribusikan.add_files_to_describer import add_distribusi_files_to_db +from distribusikan.distribusi_selector import selector_visible +from distribusikan.distribusis_info import DistribusisInfo +from distribusikan.forms.distribusiform import DistribusiForm +from distribusikan.forms.publicthemeform import PublicThemeForm +from distribusikan.forms.selectorform import SelectorForm +from distribusikan.forms.themeform import ThemeForm # Forms! -from forms.uploadform import UploadForm +from distribusikan.forms.uploadform import UploadForm from models.distribusi_model import Distribusis from models.user_model import User @@ -31,7 +32,7 @@ from models.user_model import User from statuspengguna.helper import UserHelper -def DistribusiWorkflow(): +def distribusi_workflow(): distribusiform = DistribusiForm() current_distribusi = UserHelper.current_distribusi() user = User.query.filter_by(email=current_user.email).first() @@ -41,18 +42,19 @@ def DistribusiWorkflow(): if distribusiform.validate_on_submit(): userfolder = os.path.join("stash", distribusi.distribusiname) - cssfile = GetCssFile(distribusi) - UnzipDistribusiFiles(distribusi, userfolder) - CleanUpDistribusiFiles(userfolder) - RunDistribusi(userfolder, cssfile) - SetDistribusiToVisible(distribusi, user) - DeleteCssFile(cssfile) + cssfile = get_css_file(distribusi) + unzip_distribusi_files(distribusi, userfolder) + clean_up_distribusi_files(userfolder) + add_distribusi_files_to_db(distribusi.distribusiname) + run_distribusi(userfolder, cssfile) + set_distribusi_to_visible(distribusi, user) + delete_css_file(cssfile) return redirect(url_for("index")) - return RenderDistribusiTemplate(distribusiform, current_distribusi) + return render_distribusi_template(distribusiform, current_distribusi) -def UnzipDistribusiFiles(distribusi, userfolder): +def unzip_distribusi_files(distribusi, userfolder): zipfilename = "{}.zip".format(distribusi.distribusiname) unzipfile = os.path.join(userfolder, zipfilename) @@ -64,12 +66,12 @@ def UnzipDistribusiFiles(distribusi, userfolder): os.remove(os.path.join(userfolder, zipfilename)) -def CleanUpDistribusiFiles(userfolder): +def clean_up_distribusi_files(userfolder): if os.path.exists(userfolder): - RemoveMacFolders(userfolder) + remove_mac_folders(userfolder) -def RemoveMacFolders(path): +def remove_mac_folders(path): for filename in os.listdir(path): fullpath = os.path.join(path, filename) if filename.startswith("."): @@ -80,10 +82,10 @@ def RemoveMacFolders(path): if filename == "__MACOSX": shutil.rmtree(fullpath) if os.path.isdir(fullpath): - RemoveMacFolders(fullpath) + remove_mac_folders(fullpath) -def GetCssFile(distribusi): +def get_css_file(distribusi): cssfile = "" cssfolder = os.path.join("themes/userthemes", distribusi.distribusiname) if os.path.exists(cssfolder): @@ -93,13 +95,14 @@ def GetCssFile(distribusi): return cssfile -def RunDistribusi(userfolder, cssfile): +def run_distribusi(userfolder, cssfile): + print(f"Run distribusi on this folder: {userfolder} with css:{cssfile}") parser = build_argparser() - args = parser.parse_args(["-t", "--menu-with-index", "-s", cssfile]) + args = parser.parse_args(["-t", "-a", "--menu-with-index", "-s", cssfile]) distribusify(args, userfolder) -def SetDistribusiToVisible(distribusi, user): +def set_distribusi_to_visible(distribusi, user): try: distribusi.visible = True user.currentdistribusi = None @@ -109,19 +112,19 @@ def SetDistribusiToVisible(distribusi, user): flash("Unknown error occured!") -def DeleteCssFile(cssfile): +def delete_css_file(cssfile): if os.path.exists(cssfile): os.remove(cssfile) -def RenderDistribusiTemplate(distribusiform, current_distribusi): +def render_distribusi_template(distribusiform, current_distribusi): uploadform = UploadForm() 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() files_uploaded = UserHelper.is_zip_uploaded(current_distribusi) distribusi_live = UserHelper.is_distribusi_live(current_distribusi) diff --git a/verse/distribusikan/distribusikan.py b/verse/distribusikan/distribusikan.py index 4ba50ae..870ac47 100644 --- a/verse/distribusikan/distribusikan.py +++ b/verse/distribusikan/distribusikan.py @@ -1,14 +1,13 @@ from flask import Blueprint from flask_login import login_required -from distribusikan.distribusiselector import DistribusiSelector +from distribusikan.distribusi_selector import distribusi_selector # Distribusi Information -from distribusikan.distribusisinfo import DistribusisInfo -from distribusikan.distribusiworkflow import DistribusiWorkflow -from distribusikan.editor import Editor -from distribusikan.themeselector import ThemeSelector -from distribusikan.uploadpage import UploadPage +from distribusikan.distribusi_workflow import distribusi_workflow +from distribusikan.css_editor_page import css_editor_page +from distribusikan.theme_selector import theme_selector +from distribusikan.upload_page import upload_page distribusikan = Blueprint( "distribusikan", @@ -21,28 +20,28 @@ distribusikan = Blueprint( @distribusikan.route("/distribusi", methods=["GET", "POST"]) @login_required def distribusi(): - return DistribusiWorkflow() + return distribusi_workflow() @distribusikan.route("/upload", methods=["POST"]) @login_required def upload(): - return UploadPage() + return upload_page() @distribusikan.route("/theme", methods=["GET", "POST"]) @login_required def theme(): - return ThemeSelector() + return theme_selector() @distribusikan.route("/editor", methods=["GET", "POST"]) @login_required def editor(): - return Editor() + return css_editor_page() @distribusikan.route("/selector", methods=["GET", "POST"]) @login_required def selector(): - return DistribusiSelector() + return distribusi_selector() diff --git a/verse/distribusikan/distribusisinfo.py b/verse/distribusikan/distribusis_info.py similarity index 90% rename from verse/distribusikan/distribusisinfo.py rename to verse/distribusikan/distribusis_info.py index 19ef284..aa614bb 100644 --- a/verse/distribusikan/distribusisinfo.py +++ b/verse/distribusikan/distribusis_info.py @@ -5,14 +5,14 @@ from models.user_model import User class DistribusisInfo: - def userdistribusinames(): + def user_distribusinames(): distribusinames = [] user = User.query.filter_by(email=current_user.email).first() for distribusi in Distribusis.query.filter_by(userid=user.id).all(): distribusinames.append(distribusi.distribusiname) return distribusinames - def publicthemes(): + def public_themes(): publicthemes = [] distribusis = Distribusis.query.filter( Distribusis.publictheme.isnot(None) @@ -27,12 +27,12 @@ made by {user.username}""", publicthemes.append(publictheme) return publicthemes - def visibledistribusis(): + def visible_distribusis(): distribusis = Distribusis.query.filter( Distribusis.visible.isnot(False) ).all() return distribusis - def getuserdistribusis(useremail): + def get_user_distribusis(useremail): user = User.query.filter_by(email=useremail).first() return Distribusis.query.filter_by(userid=user.id).all() diff --git a/verse/forms/distribusiform.py b/verse/distribusikan/forms/distribusiform.py similarity index 100% rename from verse/forms/distribusiform.py rename to verse/distribusikan/forms/distribusiform.py diff --git a/verse/forms/editorform.py b/verse/distribusikan/forms/editorform.py similarity index 100% rename from verse/forms/editorform.py rename to verse/distribusikan/forms/editorform.py diff --git a/verse/forms/publicthemeform.py b/verse/distribusikan/forms/publicthemeform.py similarity index 100% rename from verse/forms/publicthemeform.py rename to verse/distribusikan/forms/publicthemeform.py diff --git a/verse/forms/selectorform.py b/verse/distribusikan/forms/selectorform.py similarity index 87% rename from verse/forms/selectorform.py rename to verse/distribusikan/forms/selectorform.py index d726971..f2f9530 100644 --- a/verse/forms/selectorform.py +++ b/verse/distribusikan/forms/selectorform.py @@ -9,4 +9,6 @@ class SelectorForm(FlaskForm): update = SubmitField("update") + describe = SubmitField("describe") + delete = SubmitField("delete") diff --git a/verse/forms/themeform.py b/verse/distribusikan/forms/themeform.py similarity index 100% rename from verse/forms/themeform.py rename to verse/distribusikan/forms/themeform.py diff --git a/verse/forms/uploadform.py b/verse/distribusikan/forms/uploadform.py similarity index 82% rename from verse/forms/uploadform.py rename to verse/distribusikan/forms/uploadform.py index 5d1f21f..f0af89b 100644 --- a/verse/forms/uploadform.py +++ b/verse/distribusikan/forms/uploadform.py @@ -1,18 +1,18 @@ from flask_wtf import FlaskForm from flask_wtf.file import FileAllowed, FileField, FileRequired, FileSize from wtforms import ( - IntegerField, SelectField, StringField, SubmitField, + TextAreaField, validators, ) from wtforms.validators import ( DataRequired, Length, - NumberRange, ValidationError, ) +from wtforms.widgets import TextArea from app import settings @@ -50,6 +50,13 @@ class UploadForm(FlaskForm): distribusiname, ], ) + description = StringField( + "Description of this distribusi:", + validators=[ + Length(10, 32000), + ], + widget=TextArea(), + ) year = SelectField( "Year:", validate_choice=True, @@ -68,8 +75,8 @@ class UploadForm(FlaskForm): ) tags = StringField( - "Add tags, seperated by commas. No need for the '#' sign:", - validators=[validators.InputRequired(), Length(2, 500)], + "Add search tags, seperated by commas. No need for the '#' sign:", + validators=[validators.InputRequired(), Length(3, 500)], ) zipfile = FileField( @@ -79,7 +86,7 @@ class UploadForm(FlaskForm): FileRequired(), FileSize( max_size=1073741824, - message="Zipfile size must be smaller than 100MB", + message="Zipfile size must be smaller than 1024MB", ), ], ) diff --git a/verse/distribusikan/static/css/distribusikan.css b/verse/distribusikan/static/css/distribusikan.css new file mode 100644 index 0000000..2083035 --- /dev/null +++ b/verse/distribusikan/static/css/distribusikan.css @@ -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; +} diff --git a/verse/static/css/editor.css b/verse/distribusikan/static/css/editor.css similarity index 86% rename from verse/static/css/editor.css rename to verse/distribusikan/static/css/editor.css index d087622..d8f2ed6 100644 --- a/verse/static/css/editor.css +++ b/verse/distribusikan/static/css/editor.css @@ -5,7 +5,7 @@ } .editarea { width: 30%; - border: 3px solid #E0B0FF; + border: 3px solid #9de457; border-style: outset; margin-right: 1em; margin-left: 0; @@ -31,7 +31,7 @@ textarea { height: 100%; box-sizing: border-box; min-height: 250px; - background: #E0B0FF; + background: #9de457; outline: none; font-family: Courier, sans-serif; font-size: 16px; @@ -45,6 +45,6 @@ iframe { height: 30em; } #html { - background-color: #60337F; - color: lightgrey; + background-color: #9de457; + color: black; } diff --git a/verse/distribusikan/templates/distribusikan/distribusi.html b/verse/distribusikan/templates/distribusikan/distribusi.html index e85f04c..1e9c563 100644 --- a/verse/distribusikan/templates/distribusikan/distribusi.html +++ b/verse/distribusikan/templates/distribusikan/distribusi.html @@ -20,27 +20,28 @@
{% if selectorvisible %} {% block selector %} - {% include "distribusiworkflow/selector.html" %} + {% include "distribusi_workflow/selector.html" %} {% endblock selector%} {% else %} {% block upload %} - {% include "distribusiworkflow/upload.html" %} + {% include "distribusi_workflow/upload.html" %} {% endblock upload%} {% block theme %} - {% include "distribusiworkflow/theme.html" %} + {% include "distribusi_workflow/theme.html" %} {% endblock theme%} {% block editcss %} - {% include "distribusiworkflow/editcss.html" %} + {% include "distribusi_workflow/editcss.html" %} {% endblock editcss%} {% block launch %} - {% include "distribusiworkflow/launch.html" %} + {% include "distribusi_workflow/launch.html" %} {% endblock launch%} {%endif%}
{% if css_selected %} {% endif %} + {% endblock main %} diff --git a/verse/distribusikan/templates/distribusikan/distribusiworkflow/editcss.html b/verse/distribusikan/templates/distribusikan/distribusi_workflow/editcss.html similarity index 83% rename from verse/distribusikan/templates/distribusikan/distribusiworkflow/editcss.html rename to verse/distribusikan/templates/distribusikan/distribusi_workflow/editcss.html index 0170599..bd003fd 100644 --- a/verse/distribusikan/templates/distribusikan/distribusiworkflow/editcss.html +++ b/verse/distribusikan/templates/distribusikan/distribusi_workflow/editcss.html @@ -1,7 +1,7 @@

Step 3: Edit Custom CSS (Optional)

{% if files_uploaded or distribusi_live %} -

Go to CSS editor

+

Go to CSS editor

{% else %}

You need to upload your files first before you can select a css theme diff --git a/verse/distribusikan/templates/distribusikan/distribusiworkflow/launch.html b/verse/distribusikan/templates/distribusikan/distribusi_workflow/launch.html similarity index 100% rename from verse/distribusikan/templates/distribusikan/distribusiworkflow/launch.html rename to verse/distribusikan/templates/distribusikan/distribusi_workflow/launch.html diff --git a/verse/distribusikan/templates/distribusikan/distribusiworkflow/selector.html b/verse/distribusikan/templates/distribusikan/distribusi_workflow/selector.html similarity index 84% rename from verse/distribusikan/templates/distribusikan/distribusiworkflow/selector.html rename to verse/distribusikan/templates/distribusikan/distribusi_workflow/selector.html index 88dac97..3880148 100644 --- a/verse/distribusikan/templates/distribusikan/distribusiworkflow/selector.html +++ b/verse/distribusikan/templates/distribusikan/distribusi_workflow/selector.html @@ -18,6 +18,13 @@ {{ selectorform.update }}


+

+ Describe your distribusi files. Add description texts, tags for search + or add alt-text for images

+
+ {{ selectorform.describe }} +
+

This will delete your distribusi site. This action cannot be undone! @@ -29,6 +36,7 @@ {% endfor %}


+
{% if limit_reached %}

You have reached your limit of distribusi websites

{% else %} diff --git a/verse/distribusikan/templates/distribusikan/distribusiworkflow/theme.html b/verse/distribusikan/templates/distribusikan/distribusi_workflow/theme.html similarity index 100% rename from verse/distribusikan/templates/distribusikan/distribusiworkflow/theme.html rename to verse/distribusikan/templates/distribusikan/distribusi_workflow/theme.html diff --git a/verse/distribusikan/templates/distribusikan/distribusiworkflow/upload.html b/verse/distribusikan/templates/distribusikan/distribusi_workflow/upload.html similarity index 87% rename from verse/distribusikan/templates/distribusikan/distribusiworkflow/upload.html rename to verse/distribusikan/templates/distribusikan/distribusi_workflow/upload.html index fe3aa78..f8a0502 100644 --- a/verse/distribusikan/templates/distribusikan/distribusiworkflow/upload.html +++ b/verse/distribusikan/templates/distribusikan/distribusi_workflow/upload.html @@ -10,6 +10,13 @@
{{ message }}
{% endfor %} +
+ {{ uploadform.description.label }} + {{ uploadform.description }} + {% for message in uploadform.description.errors %} +
{{ message }}
+ {% endfor %} +
{{ uploadform.year.label }}
diff --git a/verse/distribusikan/templates/distribusikan/editor.html b/verse/distribusikan/templates/distribusikan/editor.html index b6e39f4..7ed5b84 100644 --- a/verse/distribusikan/templates/distribusikan/editor.html +++ b/verse/distribusikan/templates/distribusikan/editor.html @@ -5,7 +5,7 @@
@@ -54,6 +54,6 @@ - + {% endblock main %} diff --git a/verse/distribusikan/themeselector.py b/verse/distribusikan/theme_selector.py similarity index 71% rename from verse/distribusikan/themeselector.py rename to verse/distribusikan/theme_selector.py index 96b2d8a..30388e4 100644 --- a/verse/distribusikan/themeselector.py +++ b/verse/distribusikan/theme_selector.py @@ -3,45 +3,45 @@ import shutil from flask import render_template -from distribusikan.distribusisinfo import DistribusisInfo -from forms.distribusiform import DistribusiForm -from forms.publicthemeform import PublicThemeForm -from forms.selectorform import SelectorForm -from forms.themeform import ThemeForm -from forms.uploadform import UploadForm +from distribusikan.distribusis_info import DistribusisInfo +from distribusikan.forms.distribusiform import DistribusiForm +from distribusikan.forms.publicthemeform import PublicThemeForm +from distribusikan.forms.selectorform import SelectorForm +from distribusikan.forms.themeform import ThemeForm +from distribusikan.forms.uploadform import UploadForm from statuspengguna.helper import UserHelper -def ThemeSelector(): +def theme_selector(): themeform = ThemeForm() publicthemeform = PublicThemeForm() - publicthemeform.publicthemes.choices = DistribusisInfo.publicthemes() + publicthemeform.publicthemes.choices = DistribusisInfo.public_themes() current_distribusi = UserHelper.current_distribusi() if themeform.validate_on_submit(): copycssfile = os.path.join( "themes", f"{themeform.theme.data}.css", ) - MoveCssToUserFolder(current_distribusi, copycssfile) + move_css_to_user_folder(current_distribusi, copycssfile) if publicthemeform.validate_on_submit(): copycssfile = os.path.join( "themes/publicthemes/", f"{publicthemeform.publicthemes.data}.css", ) - MoveCssToUserFolder(current_distribusi, copycssfile) - return RenderDistribusiTemplate( + move_css_to_user_folder(current_distribusi, copycssfile) + return render_distribusi_template( themeform, publicthemeform, current_distribusi ) -def MoveCssToUserFolder(current_distribusi, copycssfile): +def move_css_to_user_folder(current_distribusi, copycssfile): newcssfolder = os.path.join("themes/userthemes", current_distribusi) if not os.path.exists(newcssfolder): os.mkdir(newcssfolder) shutil.copy(copycssfile, newcssfolder) -def RenderDistribusiTemplate(themeform, publicthemeform, current_distribusi): +def render_distribusi_template(themeform, publicthemeform, current_distribusi): uploadform = UploadForm() distribusiform = DistribusiForm() selectorform = SelectorForm() diff --git a/verse/distribusikan/upload.py b/verse/distribusikan/upload.py index 83110b5..b81e3e6 100644 --- a/verse/distribusikan/upload.py +++ b/verse/distribusikan/upload.py @@ -12,14 +12,14 @@ from sqlalchemy.exc import ( ) from app import db -from distribusikan.distribusiselector import SelectCurrentDistribusi -from forms.uploadform import UploadForm +from distribusikan.distribusi_selector import select_current_distribusi +from distribusikan.forms.uploadform import UploadForm from models.distribusi_model import Distribusis from models.user_model import User from statuspengguna.helper import UserHelper -def UploadNewDistribusi(uploadfolder): +def upload_new_distribusi(uploadfolder): uploadform = UploadForm() if uploadform.validate_on_submit(): user = User.query.filter_by(email=current_user.email).first() @@ -28,6 +28,7 @@ def UploadNewDistribusi(uploadfolder): distribusiname=uploadform.sitename.data, userid=user.id, category=uploadform.category.data, + description=uploadform.description.data, year=uploadform.year.data, tags=uploadform.tags.data, ) @@ -46,7 +47,7 @@ def UploadNewDistribusi(uploadfolder): uploadform.sitename.errors.append("Something went wrong!") flash("Something went wrong!", "danger") return uploadform - SelectCurrentDistribusi(newdistribusi.distribusiname) + select_current_distribusi(newdistribusi.distribusiname) zipfilename = "{}.zip".format(newdistribusi.distribusiname) zipfile = uploadform.zipfile.data zipfile.save(os.path.join(uploadfolder, zipfilename)) @@ -62,7 +63,7 @@ def UploadNewDistribusi(uploadfolder): return uploadform -def UploadUpdatedFiles(uploadfolder): +def upload_updates_files(uploadfolder): uploadform = UploadForm() if uploadform.validate_on_submit(): try: @@ -71,6 +72,7 @@ def UploadUpdatedFiles(uploadfolder): distribusiname=current_distribusi ).first() distribusi.category = uploadform.category.data + distribusi.description = (uploadform.description.data,) distribusi.year = uploadform.year.data distribusi.tags = uploadform.tags.data distribusi.visible = False diff --git a/verse/distribusikan/uploadpage.py b/verse/distribusikan/upload_page.py similarity index 64% rename from verse/distribusikan/uploadpage.py rename to verse/distribusikan/upload_page.py index a8f2510..06070f3 100644 --- a/verse/distribusikan/uploadpage.py +++ b/verse/distribusikan/upload_page.py @@ -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) diff --git a/verse/migrations/env.py b/verse/migrations/env.py index 847c719..68a0091 100644 --- a/verse/migrations/env.py +++ b/verse/migrations/env.py @@ -3,9 +3,10 @@ from __future__ import with_statement import logging from logging.config import fileConfig -from alembic import context from flask import current_app +from alembic import context + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config diff --git a/verse/models/distribusi_file_model.py b/verse/models/distribusi_file_model.py index 503f7bb..edddc71 100644 --- a/verse/models/distribusi_file_model.py +++ b/verse/models/distribusi_file_model.py @@ -7,11 +7,12 @@ class DistribusiFiles(db.Model): __tablename__ = "distribusi_files" id = db.Column(db.Integer, primary_key=True) - type = db.Column(db.String(300), nullable=False, unique=True) + type = db.Column(db.String(100), nullable=True, unique=False) distribusi = db.Column(db.Integer, db.ForeignKey("distribusis.id")) - path = db.Column(db.String(4096), nullable=True, unique=False) - description = db.Column(db.String(9), nullable=True, unique=False) + path = db.Column(db.String(4096), nullable=True, unique=True) + alttext = db.Column(db.String(255), nullable=True, unique=False) tags = db.Column(db.String(500), nullable=True, unique=False) + description = db.Column(db.String(4096), nullable=True, unique=False) def __repr__(self): - return "" % self.distribusiname + return "" % self.path diff --git a/verse/models/distribusi_model.py b/verse/models/distribusi_model.py index d8f114e..dd9f040 100644 --- a/verse/models/distribusi_model.py +++ b/verse/models/distribusi_model.py @@ -11,6 +11,7 @@ class Distribusis(db.Model): userid = db.Column(db.Integer, db.ForeignKey("users.id")) category = db.Column(db.String(500), nullable=True, unique=False) year = db.Column(db.String(9), nullable=True, unique=False) + description = db.Column(db.String(32000), nullable=True, unique=False) tags = db.Column(db.String(500), nullable=True, unique=False) publictheme = db.Column(db.String(300), unique=True, nullable=True) visible = db.Column(db.Boolean, default=False) diff --git a/verse/search/forms/searchform.py b/verse/search/forms/searchform.py new file mode 100644 index 0000000..707c9b7 --- /dev/null +++ b/verse/search/forms/searchform.py @@ -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") diff --git a/verse/search/search.py b/verse/search/search.py new file mode 100644 index 0000000..d38a0e6 --- /dev/null +++ b/verse/search/search.py @@ -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 diff --git a/verse/search/search_index.py b/verse/search/search_index.py new file mode 100644 index 0000000..88c9dd0 --- /dev/null +++ b/verse/search/search_index.py @@ -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 diff --git a/verse/search/templates/search/search.html b/verse/search/templates/search/search.html new file mode 100644 index 0000000..709b965 --- /dev/null +++ b/verse/search/templates/search/search.html @@ -0,0 +1,23 @@ +{% extends "base/base.html" %} +{% block main %} +
+
+ {{ searchform.csrf_token }} +
+ {{ searchform.searchfield.label }} + {{ searchform.searchfield }} + {% for message in searchform.searchfield.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ searchform.submit }} +
+
+
+ {% for found_distribusi in found_distribusis %} + {{found_distribusi}} + {% endfor %} +
+
+{% endblock %} diff --git a/verse/settings.toml b/verse/settings.toml index 287c705..86ee959 100644 --- a/verse/settings.toml +++ b/verse/settings.toml @@ -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 diff --git a/verse/start.py b/verse/start.py index 5cbe8c5..368a594 100644 --- a/verse/start.py +++ b/verse/start.py @@ -11,31 +11,35 @@ from flask import ( url_for, ) from flask_login import current_user, login_required, logout_user -from flask_mail import Mail from flask_wtf.csrf import CSRFError -# Interface! these are seperate files in main folder -from adminpage import AdminPage -from app import create_app, login_manager +from admin import is_adminuser +from admin_page.admin_page import admin_page +from app import create_app, login_manager, db +from describer.describe_files import describer from distribusikan.distribusikan import distribusikan -from distribusikan.distribusisinfo import DistribusisInfo - -# Use upload form to populate filters -from forms.uploadform import UploadForm +from distribusikan.distribusis_info import DistribusisInfo +from distribusikan.forms.uploadform import UploadForm from models.distribusi_model import Distribusis from models.user_model import User from statuspengguna.forgotpassword import forgot_password from statuspengguna.helper import UserHelper from statuspengguna.loginuser import login_section from statuspengguna.registeruser import register_user +from search.search import searchpages +from search.search_index import init_search_index APP = create_app() stash_page = Blueprint("stash_page", __name__, static_folder="stash") +APP.register_blueprint(stash_page) +APP.register_blueprint(describer, url_prefix="/describer") APP.register_blueprint(login_section, url_prefix="/login") APP.register_blueprint(register_user, url_prefix="/register") APP.register_blueprint(forgot_password, url_prefix="/login/forgotpassword") -APP.register_blueprint(distribusikan) -APP.register_blueprint(stash_page) +APP.register_blueprint(admin_page, url_prefix="/admin") +APP.register_blueprint(distribusikan, url_prefix="/distribusikan") +APP.register_blueprint(searchpages, url_prefix="/search") +init_search_index(APP) @APP.before_request @@ -48,7 +52,7 @@ def session_handler(): def index(): UserHelper.reset_user_state() uploadform = UploadForm() - distribusis = DistribusisInfo.visibledistribusis() + distribusis = DistribusisInfo.visible_distribusis() distribusisindex = {} for distribusi in distribusis: user = User.query.filter_by(id=distribusi.userid).first() @@ -60,9 +64,10 @@ def index(): "tags": distribusi.tags.split(","), } distribusisindex[distribusi.distribusiname] = singledistribusi + years = uploadform.year.choices categories = uploadform.category.choices - adminuser = isadminuser() + adminuser = is_adminuser() template = render_template( "base/index.html", distribusisindex=distribusisindex, @@ -91,14 +96,6 @@ def shortstashurl(): return redirect(url_for("index")) -@APP.route("/admin", methods=["GET", "POST"]) -@login_required -def admin(): - if not isadminuser(): - return redirect(url_for("index")) - return AdminPage() - - @APP.route("/logout") @login_required def logout(): @@ -113,14 +110,7 @@ def handle_csrf_error(e): @login_manager.user_loader def load_user(user_id): - return User.query.get(int(user_id)) - - -def isadminuser(): - if not current_user.is_authenticated: - return False - user = User.query.filter_by(email=current_user.email).first() - return user.admin + return db.session.get(User, int(user_id)) if __name__ == "__main__": diff --git a/verse/static/css/dropdown.css b/verse/static/css/dropdown.css index 07509d5..6005155 100644 --- a/verse/static/css/dropdown.css +++ b/verse/static/css/dropdown.css @@ -2,7 +2,7 @@ /* for sorting on year and category */ button { - background-color: #E0B0FF; + background-color: #9de457; text-decoration: none; border: none; } @@ -26,7 +26,7 @@ button { .dropdown-content { display: none; position: absolute; - background-color: #E0B0FF; + background-color: #9de457; min-width: 120px; border: 2px solid; z-index: 1; diff --git a/verse/static/css/selector.css b/verse/static/css/selector.css index a500a61..0f9ec0c 100644 --- a/verse/static/css/selector.css +++ b/verse/static/css/selector.css @@ -4,7 +4,7 @@ max-width: 20em; position: relative; border: none; - background: #E0B0FF; + background: #9de457; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; @@ -18,7 +18,7 @@ max-width: 20em; border: none; box-shadow: none; - background-color: #E0B0FF; + background-color: #9de457; background-image: none; -webkit-appearance: none; -moz-appearance: none; @@ -41,7 +41,7 @@ } select.selector option{ color: white; - background-color: #60337F; + background-color: #9de457; padding: 0 10px; } @@ -49,5 +49,5 @@ select.selector option{ outline: none; } .selector-style select option:hover { - background: #60337F; + background: #9de457; } diff --git a/verse/static/css/style.css b/verse/static/css/style.css index 2f772a8..85aabbf 100644 --- a/verse/static/css/style.css +++ b/verse/static/css/style.css @@ -3,63 +3,16 @@ body font-family: monospace, monospace; font-size: 15px; background-color: #fdfdfd; - color:#29d148; + color:#091411; 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; + width: 40em; margin:0 auto; } @@ -69,7 +22,16 @@ div#upload form { #distribusi-index { padding-left: 1em; } - +.description > textarea { + width: 100%; + height: 10em; + resize: none; +} +textarea#description { + width: 100%; + height: 20em; + resize: none; +} div#buttons{ position: fixed; top: 0.5em; @@ -82,13 +44,13 @@ div#buttons{ div#buttons .distribusi input{ border: none; - background: #fff600; + background: #9de457; text-decoration: none; margin: 0.2em; } div#buttons .distribusi input:hover{ - background: #ffbf00; - + background: #091411; + color: #6df2cc; } fieldset.required { border: none; @@ -101,21 +63,15 @@ fieldset.required > ul { 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; + background: #9de457; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; @@ -124,7 +80,9 @@ input { } input:hover { - background: #60337F; + background: #091411; + color: #6df2cc; + cursor: pointer; } input[type="submit"]:disabled:hover, @@ -144,31 +102,11 @@ input[type="submit"]:disabled:focus { background-color: #F92020; } -#update { +#update, #describe { 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: "$ "; @@ -189,7 +127,7 @@ a:active { white-space: nowrap; animation: reveal 4s linear; text-overflow: "█"; - background-color: #2D3039; + background-color: #9de457; } #fancyboi::after { content: "█"; @@ -199,7 +137,7 @@ a:active { div.maincontent{ width: 55%; - border: 3px #E0B0FF; + border: 3px #9de457; margin-top: 0.5em; padding: 0.5em; border-style: outset; @@ -238,8 +176,6 @@ div.maincontent{ 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; } @@ -254,7 +190,7 @@ div.maincontent{ color: black; padding: 1em; box-sizing: border-box; - background: #E0B0FF; + background: #9de457; outline: none; font-family: Courier, sans-serif; font-size: 16px; @@ -262,11 +198,11 @@ div.maincontent{ /* Project colors so far. light -#E0B0FF +#9de457 medium #d28cff dark -#60337F +#9de457 background dark #2D3039 diff --git a/verse/statuspengguna/forgotpassword.py b/verse/statuspengguna/forgotpassword.py index dbb8049..3137e1f 100644 --- a/verse/statuspengguna/forgotpassword.py +++ b/verse/statuspengguna/forgotpassword.py @@ -11,7 +11,7 @@ from sqlalchemy.exc import ( ) from app import db, get_app -from forms.forgotpasswordform import ForgotPasswordForm +from statuspengguna.forms.forgotpasswordform import ForgotPasswordForm from models.user_model import User mail = Mail(get_app()) diff --git a/verse/forms/forgotpasswordform.py b/verse/statuspengguna/forms/forgotpasswordform.py similarity index 100% rename from verse/forms/forgotpasswordform.py rename to verse/statuspengguna/forms/forgotpasswordform.py diff --git a/verse/forms/loginform.py b/verse/statuspengguna/forms/loginform.py similarity index 100% rename from verse/forms/loginform.py rename to verse/statuspengguna/forms/loginform.py diff --git a/verse/forms/registerform.py b/verse/statuspengguna/forms/registerform.py similarity index 93% rename from verse/forms/registerform.py rename to verse/statuspengguna/forms/registerform.py index cca23ab..89c42d6 100644 --- a/verse/forms/registerform.py +++ b/verse/statuspengguna/forms/registerform.py @@ -2,7 +2,7 @@ from flask_wtf import FlaskForm from wtforms import PasswordField, StringField, SubmitField, validators -from wtforms.validators import Email, EqualTo, Length, ValidationError +from wtforms.validators import Email, EqualTo, Length class RegisterForm(FlaskForm): diff --git a/verse/forms/resetpasswordform.py b/verse/statuspengguna/forms/resetpasswordform.py similarity index 100% rename from verse/forms/resetpasswordform.py rename to verse/statuspengguna/forms/resetpasswordform.py diff --git a/verse/statuspengguna/helper.py b/verse/statuspengguna/helper.py index a8b26bd..c227155 100644 --- a/verse/statuspengguna/helper.py +++ b/verse/statuspengguna/helper.py @@ -10,7 +10,7 @@ from sqlalchemy.exc import ( ) from app import db -from distribusikan.distribusisinfo import DistribusisInfo +from distribusikan.distribusis_info import DistribusisInfo from models.distribusi_model import Distribusis from models.user_model import User @@ -76,7 +76,9 @@ class UserHelper: def distribusi_limit_reached(): user = User.query.filter_by(email=current_user.email).first() - distribusiamount = len(DistribusisInfo.getuserdistribusis(user.email)) + distribusiamount = len( + DistribusisInfo.get_user_distribusis(user.email) + ) if distribusiamount > 19: print("user already has 20 distribusis") return True diff --git a/verse/statuspengguna/loginuser.py b/verse/statuspengguna/loginuser.py index db08cac..ba28c72 100644 --- a/verse/statuspengguna/loginuser.py +++ b/verse/statuspengguna/loginuser.py @@ -5,14 +5,12 @@ from flask import ( redirect, render_template, request, - send_from_directory, - session, url_for, ) from flask_bcrypt import check_password_hash from flask_login import login_user -from forms.loginform import LoginForm +from statuspengguna.forms.loginform import LoginForm from models.user_model import User login_section = Blueprint( @@ -37,7 +35,6 @@ def LoginUser(): loginform.password.errors.append("Invalid email or password!") return render_template("login.html", loginform=loginform) if check_password_hash(user.password, loginform.password.data): - print(type(user)) login_user(user) flash("Logged in successfully.", "success") next = request.args.get("next") diff --git a/verse/statuspengguna/registeruser.py b/verse/statuspengguna/registeruser.py index e97f1ee..af1216f 100644 --- a/verse/statuspengguna/registeruser.py +++ b/verse/statuspengguna/registeruser.py @@ -11,7 +11,7 @@ from sqlalchemy.exc import ( from werkzeug.routing import BuildError from app import db -from forms.registerform import RegisterForm +from statuspengguna.forms.registerform import RegisterForm from models.user_model import User register_user = Blueprint( diff --git a/verse/statuspengguna/resetpassword.py b/verse/statuspengguna/resetpassword.py index a75f86e..a9fc1b5 100644 --- a/verse/statuspengguna/resetpassword.py +++ b/verse/statuspengguna/resetpassword.py @@ -1,6 +1,6 @@ from datetime import datetime -from flask import flash, redirect, render_template, url_for +from flask import Blueprint, flash, redirect, render_template, url_for from flask_bcrypt import generate_password_hash from flask_login import login_user from sqlalchemy.exc import ( @@ -13,9 +13,8 @@ from sqlalchemy.exc import ( from werkzeug.routing import BuildError from app import db -from forms.resetpasswordform import ResetPasswordForm +from statuspengguna.forms.resetpasswordform import ResetPasswordForm from models.user_model import User -from statuspengguna import statuspengguna reset_password = Blueprint( "reset_password", diff --git a/verse/statuspengguna/static/css/dropdown.css b/verse/statuspengguna/static/css/dropdown.css deleted file mode 100644 index 07509d5..0000000 --- a/verse/statuspengguna/static/css/dropdown.css +++ /dev/null @@ -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; - } -} diff --git a/verse/statuspengguna/static/css/editor.css b/verse/statuspengguna/static/css/editor.css deleted file mode 100644 index d087622..0000000 --- a/verse/statuspengguna/static/css/editor.css +++ /dev/null @@ -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; -} diff --git a/verse/statuspengguna/static/css/login.css b/verse/statuspengguna/static/css/login.css new file mode 100644 index 0000000..74308d0 --- /dev/null +++ b/verse/statuspengguna/static/css/login.css @@ -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; +} diff --git a/verse/statuspengguna/static/css/selector.css b/verse/statuspengguna/static/css/selector.css deleted file mode 100644 index a500a61..0000000 --- a/verse/statuspengguna/static/css/selector.css +++ /dev/null @@ -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; -} diff --git a/verse/statuspengguna/static/css/style.css b/verse/statuspengguna/static/css/style.css deleted file mode 100644 index fb743fc..0000000 --- a/verse/statuspengguna/static/css/style.css +++ /dev/null @@ -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 -*/ diff --git a/verse/statuspengguna/static/icons/about.txt b/verse/statuspengguna/static/icons/about.txt deleted file mode 100644 index f6a9f45..0000000 --- a/verse/statuspengguna/static/icons/about.txt +++ /dev/null @@ -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)) diff --git a/verse/statuspengguna/static/icons/android-chrome-192x192.png b/verse/statuspengguna/static/icons/android-chrome-192x192.png deleted file mode 100644 index 3b958ff2fe8d23b1731a3b4ae5bfedd63de5351d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5479 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 UF$%_tssI2007*qoM6N<$f)4Gf%>V!Z diff --git a/verse/statuspengguna/static/icons/favicon-32x32.png b/verse/statuspengguna/static/icons/favicon-32x32.png deleted file mode 100644 index 8478c1eef1176757365a114418036d6fd10fcc68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 666 zcmV;L0%iS)P)AUOi6{<1x<3iAVd_Me+ye?4&|KV;#qwkJU_no+q~j-V;S{vR8tw# zRCG;+5J%Tk#xx%;KoM=CJk+I=rC$6#KAH&lASRZhspw_{nE8PI)&S=Zb+W&tfem@3 zl>d!14v15AR;J=g(Pb#1=;Hf z5E15t&1Gj%6>r)%UeVQXJN%3&;34~KJNgbd6PseV{ zByvGCBRxNi2WTp|%DspPy`Gom0J7`*rgxc+=%mF;U@D357ZpUeJ z0By(bBO)|aT{j1?Z}SmEgg)z^mG672tt_|0We}8oGiL%6HBRC zfPiQ|TxJ1Adub528_RD*z@TyHnvZ~{QV%G<0LThWlbn&F^8f$<07*qoM6N<$f(fl4 ATmS$7 diff --git a/verse/statuspengguna/static/icons/favicon.ico b/verse/statuspengguna/static/icons/favicon.ico deleted file mode 100644 index f3757b92dd496a92dafd7e5252e463ab68f6ccc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeHO2T)X36#Xb50tOhdVM7tc7D^O5f`VAE#D*2I#g36^G%5X^Nu_g+lVwXi@ zCW*#LV#OL`kA)>MYD_g6jT*Bj_dZ-_%`D6Q7n430qG)?F|rauA~1Mj^ICKYThj3mTQ$DE$%# zyp26G*S;wG{o11#8#x%gTXx1@+rNGBURSQ}_y5+{=B32Uz@%=&Os@YdCm)S#)yMtK z7Yw#hlzhf9GCUf2@8_CapKW?X`uSShf=Fn$m_Q=f*7Rt2?IDRiciiY+e*rOZ0b9vTfo0fq9z|85n|qI*m}@ zs}360@Ig@BCJ1fPlJ>H$Vol48TMYFFht0AEJ=l5?M^biy<}SK5>wr(@WMR{krAUdH zjuGMg(afhQ9Lm~@{8kNuuzlJJqxVxe_P3MlFgjwOULEgkK93J4q@aOUU4bL4X=~ih zJfXL&9GSXh4awJ*d%E*Fc#kb(R|d5!Radl6jFGR!u{nO3z~JxQ5RZ3URm2W*RzkJ=d&%(+l)zz ztRnx?^3R3*?P4KMX zfePhaaBA^KipV~f_L;C{Q9c&;HLr1lLVAMjRJ~$VoLID55!u`?O~|+9aa_jEPVDhf zbPgF#zB}XT*`gC_d3uR_Cx>$6N5|r~P2VUco8|LaV#_<}nALA028Q&YvsWu|9&1$7 z7kuW43GI!w6Bgo^jb{~=Tilo|&6meZ>9ZI6AC&Iu%d^Bgl6?!Rf&@#*(kp{~VD`TY z`MNu75547R9w_y`(B|axvfoYqH_h$;^@n*9qG+X9N``n6%9e<;aHsJylsJQ&=HVF9{!L8m8iS;PQ?NcR6^D~|;_~tXxSesrsJl3G>#=m?9CQw9 zk2+Oq3g5)h-T}eB0d&vhPaJMNRCTZPf;Z)Do&6^EmD@E6#st!f{ntC>chJ03?A#?s z=chZb;Sb_fXFTVyssCsaCfdCbY*?Y zJ37Ia`06Di5)|?On>b*Kz{FhO59^K_v?br+?wYU)}kpWESa|6uDSF}EG<9fc0A zPA=fP4t;XW_0ClMx_FqA@uKm^W68tS4M&o9S!`Y_fuH$1<~NwrB4-W#=-%m93vnGv z;OD-0LiZpXqriQ@<7w~g42z5-rSP*3ch{d1+#c(|u?PQHU~f8O{%fnggsYRY&>?+vlFi}gIHkU|w_O%IIpc3wv!2c2f3)oioxOVr`}u+=T`EG;9J&&L0 zHc>W*|K{3bbQY>2@Gpv`@GIIA%$by4e|EmRO85mF<8hwu#mtGF+g1`k$BFWtERUTw ze$5n#izrTirSLQMHRF;6cJ`&WPE9wv?Uk}W`!ab8Rq&}=Ti}oIZ;K!4ELWWEl*m`Y z{y&kw&*O#t;R@wl#o5^sXU~$_pW||wvuN2MP}rUChdCxkiFhDm`^HrJvwxK~DiOZb zyajgly%PGzDHhLV41Qj#nfu`Ng87r1>+%eaSsv$iGEaizPU;cvat>O*-5FVvQwe+V zG4jUXGPX=~+wKB8w`^3Bz*M_ZjhDDWtv>pm9E<5kF`^?n_7_~%eTCvr^%0HgyJ+mW8I2JC9=T35uu2` diff --git a/verse/statuspengguna/static/icons/site.webmanifest b/verse/statuspengguna/static/icons/site.webmanifest deleted file mode 100644 index 45dc8a2..0000000 --- a/verse/statuspengguna/static/icons/site.webmanifest +++ /dev/null @@ -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"} \ No newline at end of file diff --git a/verse/statuspengguna/static/js/dropdown.js b/verse/statuspengguna/static/js/dropdown.js deleted file mode 100644 index 505d4a8..0000000 --- a/verse/statuspengguna/static/js/dropdown.js +++ /dev/null @@ -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"); - } -} diff --git a/verse/statuspengguna/static/js/editorupdate.js b/verse/statuspengguna/static/js/editorupdate.js deleted file mode 100644 index f76219e..0000000 --- a/verse/statuspengguna/static/js/editorupdate.js +++ /dev/null @@ -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+""); - code.close(); - }; -document.addEventListener("DOMContentLoaded", function(){ - code.open(); - code.writeln(html.value+""); - code.close(); - }); -}; - -update(); diff --git a/verse/statuspengguna/static/js/script.js b/verse/statuspengguna/static/js/script.js deleted file mode 100644 index 2bd662b..0000000 --- a/verse/statuspengguna/static/js/script.js +++ /dev/null @@ -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") -// } diff --git a/verse/statuspengguna/static/svg/arrow_1.svg b/verse/statuspengguna/static/svg/arrow_1.svg deleted file mode 100644 index 21073fd..0000000 --- a/verse/statuspengguna/static/svg/arrow_1.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/verse/statuspengguna/static/svg/arrow_2.svg b/verse/statuspengguna/static/svg/arrow_2.svg deleted file mode 100644 index ea2a026..0000000 --- a/verse/statuspengguna/static/svg/arrow_2.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/verse/statuspengguna/static/svg/arrow_3.svg b/verse/statuspengguna/static/svg/arrow_3.svg deleted file mode 100644 index c447811..0000000 --- a/verse/statuspengguna/static/svg/arrow_3.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/verse/statuspengguna/templates/statuspengguna/forgotpassword.html b/verse/statuspengguna/templates/statuspengguna/forgotpassword.html index bed8afc..59416fc 100644 --- a/verse/statuspengguna/templates/statuspengguna/forgotpassword.html +++ b/verse/statuspengguna/templates/statuspengguna/forgotpassword.html @@ -1,7 +1,7 @@ {% extends "base/base.html" %} {% block main %}
-
+
- + {% endblock main %} diff --git a/verse/statuspengguna/templates/statuspengguna/login.html b/verse/statuspengguna/templates/statuspengguna/login.html index 70a02b2..6cde1d3 100644 --- a/verse/statuspengguna/templates/statuspengguna/login.html +++ b/verse/statuspengguna/templates/statuspengguna/login.html @@ -23,4 +23,5 @@ + {% endblock main %} diff --git a/verse/statuspengguna/templates/statuspengguna/register.html b/verse/statuspengguna/templates/statuspengguna/register.html index dcdcbad..8a3b152 100644 --- a/verse/statuspengguna/templates/statuspengguna/register.html +++ b/verse/statuspengguna/templates/statuspengguna/register.html @@ -36,4 +36,5 @@ + {% endblock main %} diff --git a/verse/statuspengguna/templates/statuspengguna/resetpassword.html b/verse/statuspengguna/templates/statuspengguna/resetpassword.html index bf2438e..e97dfc0 100644 --- a/verse/statuspengguna/templates/statuspengguna/resetpassword.html +++ b/verse/statuspengguna/templates/statuspengguna/resetpassword.html @@ -26,4 +26,5 @@

Password reset link no longer valid.

{% endif %} + {% endblock main %} diff --git a/verse/templates/base/help.html b/verse/templates/base/help.html index 2abef0c..8858b58 100644 --- a/verse/templates/base/help.html +++ b/verse/templates/base/help.html @@ -19,7 +19,7 @@ {% else %} diff --git a/verse/templates/base/index.html b/verse/templates/base/index.html index e828dcd..65ed3b4 100644 --- a/verse/templates/base/index.html +++ b/verse/templates/base/index.html @@ -15,7 +15,7 @@ {% else %} diff --git a/verse/themes/editor/placeholder.html b/verse/themes/editor/placeholder.html index ffbc168..8846a56 100644 --- a/verse/themes/editor/placeholder.html +++ b/verse/themes/editor/placeholder.html @@ -6,14 +6,14 @@
example_video.mp4