progress #11

Merged
crunk merged 20 commits from Toolsheds/distribusi-verse:main into main 7 months ago
  1. 9
      README.md
  2. 34
      notes.md
  3. 38
      pyproject.toml
  4. 43
      requirements.txt
  5. 4
      setup.py
  6. 36
      verse/adminpage.py
  7. 14
      verse/admintool.py
  8. 45
      verse/app.py
  9. 7
      verse/deploydb.py
  10. 48
      verse/distribusikan/distribusikan.py
  11. 24
      verse/distribusikan/distribusiselector.py
  12. 4
      verse/distribusikan/distribusisinfo.py
  13. 44
      verse/distribusikan/distribusiworkflow.py
  14. 30
      verse/distribusikan/editor.py
  15. 2
      verse/distribusikan/templates/distribusikan/distribusi.html
  16. 2
      verse/distribusikan/templates/distribusikan/distribusiworkflow/editcss.html
  17. 2
      verse/distribusikan/templates/distribusikan/distribusiworkflow/launch.html
  18. 2
      verse/distribusikan/templates/distribusikan/distribusiworkflow/selector.html
  19. 8
      verse/distribusikan/templates/distribusikan/distribusiworkflow/theme.html
  20. 23
      verse/distribusikan/templates/distribusikan/distribusiworkflow/upload.html
  21. 4
      verse/distribusikan/templates/distribusikan/editor.html
  22. 14
      verse/distribusikan/themeselector.py
  23. 28
      verse/distribusikan/upload.py
  24. 13
      verse/distribusikan/uploadpage.py
  25. 8
      verse/forms/admindistribusiform.py
  26. 12
      verse/forms/adminuserform.py
  27. 12
      verse/forms/editorform.py
  28. 8
      verse/forms/forgotpasswordform.py
  29. 9
      verse/forms/loginform.py
  30. 6
      verse/forms/publicthemeform.py
  31. 18
      verse/forms/registerform.py
  32. 8
      verse/forms/resetpasswordform.py
  33. 2
      verse/forms/selectorform.py
  34. 6
      verse/forms/themeform.py
  35. 91
      verse/forms/uploadform.py
  36. 0
      verse/instance/.gitignore
  37. 23
      verse/migrations/env.py
  38. 17
      verse/models/distribusi_file_model.py
  39. 6
      verse/models/distribusi_model.py
  40. 5
      verse/models/user_model.py
  41. 4
      verse/settings.toml
  42. 120
      verse/start.py
  43. 2
      verse/static/css/dropdown.css
  44. 18
      verse/static/css/style.css
  45. 5
      verse/static/js/dropdown.js
  46. 0
      verse/statuspengguna/__init__.py
  47. 26
      verse/statuspengguna/forgotpassword.py
  48. 18
      verse/statuspengguna/helper.py
  49. 31
      verse/statuspengguna/loginuser.py
  50. 32
      verse/statuspengguna/registeruser.py
  51. 34
      verse/statuspengguna/resetpassword.py
  52. 69
      verse/statuspengguna/static/css/dropdown.css
  53. 50
      verse/statuspengguna/static/css/editor.css
  54. 53
      verse/statuspengguna/static/css/selector.css
  55. 281
      verse/statuspengguna/static/css/style.css
  56. 6
      verse/statuspengguna/static/icons/about.txt
  57. BIN
      verse/statuspengguna/static/icons/android-chrome-192x192.png
  58. BIN
      verse/statuspengguna/static/icons/android-chrome-512x512.png
  59. BIN
      verse/statuspengguna/static/icons/apple-touch-icon.png
  60. BIN
      verse/statuspengguna/static/icons/favicon-16x16.png
  61. BIN
      verse/statuspengguna/static/icons/favicon-32x32.png
  62. BIN
      verse/statuspengguna/static/icons/favicon.ico
  63. 1
      verse/statuspengguna/static/icons/site.webmanifest
  64. 92
      verse/statuspengguna/static/js/dropdown.js
  65. 19
      verse/statuspengguna/static/js/editorupdate.js
  66. 25
      verse/statuspengguna/static/js/script.js
  67. 75
      verse/statuspengguna/static/svg/arrow_1.svg
  68. 12
      verse/statuspengguna/static/svg/arrow_2.svg
  69. 12
      verse/statuspengguna/static/svg/arrow_3.svg
  70. 5
      verse/statuspengguna/templates/statuspengguna/forgotpassword.html
  71. 6
      verse/statuspengguna/templates/statuspengguna/login.html
  72. 4
      verse/statuspengguna/templates/statuspengguna/register.html
  73. 2
      verse/statuspengguna/templates/statuspengguna/resetpassword.html
  74. 11
      verse/templates/base/admin.html
  75. 2
      verse/templates/base/base.html
  76. 20
      verse/templates/base/filtermenu.html
  77. 2
      verse/templates/base/help.html
  78. 11
      verse/templates/base/index.html
  79. 28
      verse/templates/filtermenu.html

9
README.md

@ -8,10 +8,15 @@ The contribution consisted of setting up distribusi. ruruhuis.nl (distribusi is
This particular work in progress project is an attempt to make distribusi into a webinterface that can be operated remotely without any knowlegde of CLI. Trying to somehow combine the ideas of distribusi with the ideas of a [tildeverse](https://tildeverse.org/) or [Tilde club ](https://tilde.club/), but also be neither of these ideas. This particular work in progress project is an attempt to make distribusi into a webinterface that can be operated remotely without any knowlegde of CLI. Trying to somehow combine the ideas of distribusi with the ideas of a [tildeverse](https://tildeverse.org/) or [Tilde club ](https://tilde.club/), but also be neither of these ideas.
This project is made for Autonomous Practices at the WDKA in Rotterdam. This project was made for Autonomous Practices at the WDKA in Rotterdam.
The second stage of this project might be the archiving part of the [Toolsheds fellowship.](https://nieuweinstituut.nl/en/articles/call-for-fellows-tool-sheds)
## Work in progress ## Work in progress
Amazingly helpful testers, currently I am writing some database upgrades and new functionalities, and they don't work, so have some patience with testing the in development material. Currently this repo is transforming the academic based search filters into adjustable
small Autonomous space filters.
## Start your engines! ## Start your engines!

34
notes.md

@ -1,34 +0,0 @@
# these are some notes
from distribusi.cli import build_argparser
# from distribusi.distribusi import distribusify
Works!
# Shit! We need entire CRUD functionality.
Done!
## Create:
Done
### Uploading
Done
### CSS editing.
a user can edit CSS of a file in the folder called $distribusiname
Todo: render the placeholder for html editor better.
### Theme selection
a user can select a CSS file from a radio menu
### Distribusi
A flag in de DB is set to true and distribusi is run on the folder of the users
called $distribusiname
## Read:
Based on flags set in the user DB the distribusi folders are set to visible.
## Update:
Done
## Delete:
Done

38
pyproject.toml

@ -1,25 +1,25 @@
[tool.black] [tool.black]
line-length = 79 line-length = 79
target-version = ['py37', 'py38', 'py39'] target-version = ['py311']
include = '\.pyi?$' include = '\.pyi?$'
exclude = ''' exclude = '''
/( /(
\.eggs \.eggs
| \.git | \.git
| \.hg | \.hg
| \.mypy_cache | \.mypy_cache
| \.tox | \.tox
| \.venv | \.venv
| _build | _build
| buck-out | buck-out
| build | build
| dist | dist
# The following are specific to Black, you probably don't want those. # The following are specific to Black, you probably don't want those.
| blib2to3 | blib2to3
| tests/data | tests/data
| profiling | profiling
)/ )/
''' '''

43
requirements.txt

@ -1,48 +1,57 @@
alembic==1.7.5 alembic==1.7.5
APScheduler==3.10.4
Babel==2.9.1 Babel==2.9.1
bcrypt==3.2.0 bcrypt==3.2.0
black==21.11b1 black==24.4.0
bleach==4.1.0 bleach==4.1.0
bleach-allowlist==1.0.3 bleach-allowlist==1.0.3
blinker==1.4 blinker==1.7.0
cffi==1.15.0 cffi==1.15.0
click==8.0.3 click==8.1.7
distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi@e291e7497e40211c2ebd54ca32a1f4bdaed71230
dnspython==2.1.0 dnspython==2.1.0
email-validator==1.1.3 email-validator==1.1.3
Flask==2.0.2 Flask==3.0.3
Flask-APScheduler==1.13.1
Flask-BabelEx==0.9.4 Flask-BabelEx==0.9.4
Flask-Bcrypt==0.7.1 Flask-Bcrypt==1.0.1
Flask-Login==0.5.0 Flask-Login==0.6.3
Flask-Mail==0.9.1 Flask-Mail==0.9.1
Flask-Migrate==3.1.0 Flask-Migrate==3.1.0
Flask-Principal==0.4.0 Flask-Principal==0.4.0
Flask-Security==3.0.0 Flask-Security==3.0.0
Flask-Security-Too==4.1.3 Flask-Security-Too==4.1.3
Flask-SQLAlchemy==2.5.1 Flask-SQLAlchemy==3.1.1
Flask-WTF==1.0.0 Flask-WTF==1.2.1
greenlet==1.1.2 greenlet==3.0.3
idna==3.3 idna==3.3
itsdangerous==2.0.1 isort==5.13.2
Jinja2==3.0.3 itsdangerous==2.2.0
Jinja2==3.1.3
Mako==1.1.6 Mako==1.1.6
MarkupSafe==2.0.1 MarkupSafe==2.1.5
msgpack==1.0.8
mypy-extensions==0.4.3 mypy-extensions==0.4.3
packaging==21.3 neovim==0.3.1
packaging==24.0
passlib==1.7.4 passlib==1.7.4
pathspec==0.9.0 pathspec==0.9.0
Pillow==8.3.2 Pillow==8.3.2
platformdirs==2.4.0 platformdirs==2.4.0
pycparser==2.21 pycparser==2.21
pynvim==0.5.0
pyparsing==3.0.7 pyparsing==3.0.7
python-dateutil==2.9.0.post0
python-magic==0.4.24 python-magic==0.4.24
pytz==2021.3 pytz==2021.3
regex==2021.11.10 regex==2021.11.10
six==1.16.0 six==1.16.0
speaklater==1.3 speaklater==1.3
SQLAlchemy==1.4.27 SQLAlchemy==2.0.29
tomli==1.2.2 tomli==1.2.2
typing_extensions==4.0.1 typing_extensions==4.11.0
tzlocal==5.2
webencodings==0.5.1 webencodings==0.5.1
Werkzeug==2.0.2 Werkzeug==3.0.2
Whoosh==2.7.4
WTForms==3.0.0 WTForms==3.0.0
distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi

4
setup.py

@ -1,3 +1,3 @@
from setuptools import setup, find_packages from setuptools import find_packages, setup
setup(name='library', version='1.0', packages=find_packages()) setup(name="library", version="1.0", packages=find_packages())

36
verse/adminpage.py

@ -1,21 +1,20 @@
import os import os
import shutil import shutil
from flask import render_template
from flask import render_template
from sqlalchemy.exc import ( from sqlalchemy.exc import (
DataError,
DatabaseError, DatabaseError,
DataError,
InterfaceError, InterfaceError,
InvalidRequestError, InvalidRequestError,
) )
from app import db
from usermodel import User
from distribusimodel import Distribusis
from distribusisinfo import DistribusisInfo
from forms.adminuserform import AdminUserForm from app import db
from distribusikan.distribusisinfo import DistribusisInfo
from forms.admindistribusiform import AdminDistribusiForm from forms.admindistribusiform import AdminDistribusiForm
from forms.adminuserform import AdminUserForm
from models.distribusi_model import Distribusis
from models.user_model import User
def AdminPage(): def AdminPage():
@ -27,10 +26,6 @@ def AdminPage():
if adminuserform.validate_on_submit(): if adminuserform.validate_on_submit():
if adminuserform.delete.data: if adminuserform.delete.data:
DeleteUsers(adminuserform) DeleteUsers(adminuserform)
if adminuserform.tutors.data:
ToggleUsersAsTutors(adminuserform, True)
if adminuserform.nottutors.data:
ToggleUsersAsTutors(adminuserform, False)
template = render_template( template = render_template(
"admin.html", "admin.html",
@ -51,23 +46,6 @@ def DeleteUsers(adminuserform):
userform.errors.append(f"User {useremail} deleted!") userform.errors.append(f"User {useremail} deleted!")
def ToggleUsersAsTutors(adminuserform, is_tutor):
for userform in adminuserform:
if "user" in userform.id:
if userform.data:
useremail = userform.label.text
user = User.query.filter_by(email=useremail).first()
ToggleUserTutorinDb(user, is_tutor)
userform.errors.append(f"Is User {useremail} tutor {is_tutor}")
def ToggleUserTutorinDb(user, is_tutor):
try:
user.tutor = is_tutor
db.session.commit()
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
db.session.rollback()
def DeleteUserFromDb(user): def DeleteUserFromDb(user):
try: try:

14
verse/admintool.py

@ -1,13 +1,15 @@
import sys import sys
from app import create_app, db
from sqlalchemy.exc import ( from sqlalchemy.exc import (
InvalidRequestError,
InterfaceError,
DataError,
DatabaseError, DatabaseError,
DataError,
InterfaceError,
InvalidRequestError,
) )
from usermodel import User # noqa: F401
from distribusimodel import Distribusis # noqa: F401 from app import create_app, db
from models.distribusi_model import Distribusis # noqa: F401
from models.user_model import User # noqa: F401
def admintool(): def admintool():

45
verse/app.py

@ -1,13 +1,14 @@
import os import os
import tomllib
from flask import Flask from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect from flask_wtf.csrf import CSRFProtect
from flask_login import (
LoginManager,
)
APP = Flask(__name__, static_folder="static")
db = SQLAlchemy() db = SQLAlchemy()
migrate = Migrate() migrate = Migrate()
bcrypt = Bcrypt() bcrypt = Bcrypt()
@ -15,18 +16,16 @@ login_manager = LoginManager()
def create_app(): def create_app():
APP = Flask(__name__, static_folder="static") APP.secret_key = os.urandom(24)
APP.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///distribusiverse.db"
APP.secret_key = "secret-key"
APP.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data/distribusiverse.db"
APP.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True APP.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
APP.config["MAX_CONTENT_LENGTH"] = 150 * 1024 * 1024 APP.config["MAX_CONTENT_LENGTH"] = 1024 * 1024 * 1024
APP.config["MAIL_SERVER"] = "mail.autonomic.zone" APP.config["MAIL_SERVER"] = "mail.autonomic.zone"
APP.config["MAIL_PORT"] = 587 APP.config["MAIL_PORT"] = 587
APP.config["MAIL_USE_SSL"] = False APP.config["MAIL_USE_SSL"] = False
APP.config["MAIL_USE_TLS"] = True APP.config["MAIL_USE_TLS"] = True
APP.config['MAIL_USERNAME'] = "noreply@vvvvvvaria.org" APP.config["MAIL_USERNAME"] = "noreply@vvvvvvaria.org"
login_manager.session_protection = "strong" login_manager.session_protection = "strong"
login_manager.login_view = "index" login_manager.login_view = "index"
@ -38,10 +37,36 @@ def create_app():
APP.config["UPLOAD_FOLDER"] = "tmpupload" APP.config["UPLOAD_FOLDER"] = "tmpupload"
APP.config["PUBLIC_THEMES"] = "themes/publicthemes" APP.config["PUBLIC_THEMES"] = "themes/publicthemes"
# user settings_file
settings()
csrf.init_app(APP) csrf.init_app(APP)
login_manager.init_app(APP) login_manager.init_app(APP)
db.init_app(APP) db.init_app(APP)
migrate.init_app(APP, db, render_as_batch=True) migrate.init_app(APP, db, render_as_batch=True)
bcrypt.init_app(APP) bcrypt.init_app(APP)
@APP.context_processor
def inject_title():
return dict(title=APP.config["title"])
return APP
def settings():
settings = settings_from_file()
APP.config.update(settings)
return APP return APP
def get_app():
return 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)
with open("settings.toml", "rb") as settings_file:
return tomllib.load(settings_file)

7
verse/deploydb.py

@ -1,11 +1,12 @@
def deploy(): def deploy():
"""Run deployment of database.""" """Run deployment of database."""
from flask_migrate import init, migrate, stamp, upgrade
from app import create_app, db from app import create_app, db
from flask_migrate import upgrade, migrate, init, stamp from models.distribusi_model import Distribusis # noqa: F401
# This model is required for flask_migrate to make the table # This model is required for flask_migrate to make the table
from usermodel import User # noqa: F401 from models.user_model import User # noqa: F401
from distribusimodel import Distribusis # noqa: F401
app = create_app() app = create_app()
app.app_context().push() app.app_context().push()

48
verse/distribusikan/distribusikan.py

@ -0,0 +1,48 @@
from flask import Blueprint
from flask_login import login_required
from distribusikan.distribusiselector import DistribusiSelector
# 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
distribusikan = Blueprint(
"distribusikan",
__name__,
template_folder="templates/distribusikan",
static_folder="static",
)
@distribusikan.route("/distribusi", methods=["GET", "POST"])
@login_required
def distribusi():
return DistribusiWorkflow()
@distribusikan.route("/upload", methods=["POST"])
@login_required
def upload():
return UploadPage()
@distribusikan.route("/theme", methods=["GET", "POST"])
@login_required
def theme():
return ThemeSelector()
@distribusikan.route("/editor", methods=["GET", "POST"])
@login_required
def editor():
return Editor()
@distribusikan.route("/selector", methods=["GET", "POST"])
@login_required
def selector():
return DistribusiSelector()

24
verse/distribusiselector.py → verse/distribusikan/distribusiselector.py

@ -1,29 +1,28 @@
import os import os
import shutil import shutil
from flask import flash, render_template from flask import flash, render_template
from flask_login import current_user from flask_login import current_user
from sqlalchemy.exc import ( from sqlalchemy.exc import (
DataError,
DatabaseError, DatabaseError,
DataError,
InterfaceError, InterfaceError,
InvalidRequestError, InvalidRequestError,
) )
from usermodel import User from app import db
from distribusimodel import Distribusis from distribusikan.distribusisinfo import DistribusisInfo
from distribusisinfo import DistribusisInfo
from forms.selectorform import SelectorForm
from forms.uploadform import UploadForm
from forms.distribusiform import DistribusiForm from forms.distribusiform import DistribusiForm
from forms.themeform import ThemeForm
from forms.publicthemeform import PublicThemeForm from forms.publicthemeform import PublicThemeForm
from forms.selectorform import SelectorForm
from forms.themeform import ThemeForm
from forms.uploadform import UploadForm
from models.distribusi_model import Distribusis
from models.user_model import User
# UserPengguna # UserPengguna
from statuspengguna.helper import UserHelper from statuspengguna.helper import UserHelper
from app import db
def DistribusiSelector(): def DistribusiSelector():
uploadform = UploadForm() uploadform = UploadForm()
@ -54,9 +53,8 @@ def AutoFillInUploadForm(uploadform, current_distribusi):
).first() ).first()
uploadform.sitename.data = distribusi.distribusiname uploadform.sitename.data = distribusi.distribusiname
uploadform.sitename.render_kw = {"readonly": True} uploadform.sitename.render_kw = {"readonly": True}
uploadform.term.data = distribusi.term uploadform.category.data = distribusi.category
uploadform.course.data = distribusi.course uploadform.year.data = distribusi.year
uploadform.academicyear.data = distribusi.year
uploadform.tags.data = distribusi.tags uploadform.tags.data = distribusi.tags
return uploadform return uploadform

4
verse/distribusisinfo.py → verse/distribusikan/distribusisinfo.py

@ -1,7 +1,7 @@
from flask_login import current_user from flask_login import current_user
from usermodel import User from models.distribusi_model import Distribusis
from distribusimodel import Distribusis from models.user_model import User
class DistribusisInfo: class DistribusisInfo:

44
verse/distribusiworkflow.py → verse/distribusikan/distribusiworkflow.py

@ -1,40 +1,34 @@
import os import os
import shutil import shutil
import zipfile import zipfile
# Tada!
from distribusi.cli import build_argparser
from distribusi.distribusi import distribusify
from flask import flash, redirect, render_template, url_for
from flask_login import current_user from flask_login import current_user
from flask import (
render_template,
redirect,
url_for,
flash,
)
from sqlalchemy.exc import ( from sqlalchemy.exc import (
InvalidRequestError, DatabaseError,
DataError, DataError,
InterfaceError, InterfaceError,
DatabaseError, InvalidRequestError,
) )
from app import db
from usermodel import User
from distribusimodel import Distribusis
# UserPengguna
from statuspengguna.helper import UserHelper
from distribusiselector import SelectorVisible
# Forms! from app import db
from forms.uploadform import UploadForm from distribusikan.distribusiselector import SelectorVisible
from distribusikan.distribusisinfo import DistribusisInfo
from forms.distribusiform import DistribusiForm from forms.distribusiform import DistribusiForm
from forms.themeform import ThemeForm
from forms.publicthemeform import PublicThemeForm from forms.publicthemeform import PublicThemeForm
from forms.selectorform import SelectorForm from forms.selectorform import SelectorForm
from forms.themeform import ThemeForm
from distribusisinfo import DistribusisInfo # Forms!
from forms.uploadform import UploadForm
from models.distribusi_model import Distribusis
from models.user_model import User
# Tada! # UserPengguna
from distribusi.cli import build_argparser from statuspengguna.helper import UserHelper
from distribusi.distribusi import distribusify
def DistribusiWorkflow(): def DistribusiWorkflow():
@ -78,7 +72,7 @@ def CleanUpDistribusiFiles(userfolder):
def RemoveMacFolders(path): def RemoveMacFolders(path):
for filename in os.listdir(path): for filename in os.listdir(path):
fullpath = os.path.join(path, filename) fullpath = os.path.join(path, filename)
if filename.startswith('.'): if filename.startswith("."):
if os.path.isdir(fullpath): if os.path.isdir(fullpath):
shutil.rmtree(fullpath) shutil.rmtree(fullpath)
else: else:
@ -101,7 +95,7 @@ def GetCssFile(distribusi):
def RunDistribusi(userfolder, cssfile): def RunDistribusi(userfolder, cssfile):
parser = build_argparser() parser = build_argparser()
args = parser.parse_args(["--menu-with-index", "-s", cssfile]) args = parser.parse_args(["-t", "--menu-with-index", "-s", cssfile])
distribusify(args, userfolder) distribusify(args, userfolder)

30
verse/editor.py → verse/distribusikan/editor.py

@ -1,28 +1,27 @@
import os import os
import shutil
import bleach import bleach
from bleach_allowlist import all_styles from bleach_allowlist import all_styles
import shutil
from flask import render_template from flask import render_template
from werkzeug.utils import secure_filename
from sqlalchemy.exc import ( from sqlalchemy.exc import (
DataError,
DatabaseError, DatabaseError,
DataError,
InterfaceError, InterfaceError,
InvalidRequestError, InvalidRequestError,
) )
from app import db from werkzeug.utils import secure_filename
from distribusimodel import Distribusis
from statuspengguna.helper import UserHelper
from distribusisinfo import DistribusisInfo
from forms.uploadform import UploadForm from app import db
from distribusikan.distribusisinfo import DistribusisInfo
from forms.distribusiform import DistribusiForm from forms.distribusiform import DistribusiForm
from forms.themeform import ThemeForm
from forms.publicthemeform import PublicThemeForm
from forms.editorform import EditorForm from forms.editorform import EditorForm
from forms.publicthemeform import PublicThemeForm
from forms.selectorform import SelectorForm from forms.selectorform import SelectorForm
from forms.themeform import ThemeForm
from forms.uploadform import UploadForm
from models.distribusi_model import Distribusis
from statuspengguna.helper import UserHelper
def Editor(): def Editor():
@ -68,7 +67,7 @@ def SaveUploadCssFile(editorform, newcssfolder):
cssfile.save(os.path.join(newcssfolder, cssfilename)) cssfile.save(os.path.join(newcssfolder, cssfilename))
openfile = open(os.path.join(newcssfolder, cssfilename), "r") openfile = open(os.path.join(newcssfolder, cssfilename), "r")
cleancss = bleach.clean(openfile.read(), all_styles) cleancss = bleach.clean(openfile.read(), all_styles)
cleancss = cleancss.replace('>', '>') cleancss = cleancss.replace(">", ">")
openfile.close() openfile.close()
cleanfile = open(os.path.join(newcssfolder, cssfilename), "w") cleanfile = open(os.path.join(newcssfolder, cssfilename), "w")
cleanfile.write(cleancss) cleanfile.write(cleancss)
@ -81,7 +80,7 @@ def WriteCssToFile(editorform, newcssfolder):
cssfilename = f"{secure_filename(editorform.cssname.data)}.css" cssfilename = f"{secure_filename(editorform.cssname.data)}.css"
cleancss = bleach.clean(editorform.css.data, all_styles) cleancss = bleach.clean(editorform.css.data, all_styles)
cleancss = cleancss.replace('>', '>') cleancss = cleancss.replace(">", ">")
with open(os.path.join(newcssfolder, cssfilename), "w") as cssfile: with open(os.path.join(newcssfolder, cssfilename), "w") as cssfile:
cssfile.write(cleancss) cssfile.write(cleancss)
cssfile.close cssfile.close
@ -91,8 +90,7 @@ def CopyPublicToUserFolder(editorform, publicfolder, newcssfolder):
if not os.path.exists(newcssfolder): if not os.path.exists(newcssfolder):
os.mkdir(newcssfolder) os.mkdir(newcssfolder)
copycssfile = os.path.join( copycssfile = os.path.join(
publicfolder, publicfolder, f"{secure_filename(editorform.cssname.data)}.css"
f"{secure_filename(editorform.cssname.data)}.css"
) )
print(f"copying file: {copycssfile}") print(f"copying file: {copycssfile}")
print(f"to folder: {newcssfolder}") print(f"to folder: {newcssfolder}")

2
verse/templates/distribusi.html → verse/distribusikan/templates/distribusikan/distribusi.html

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="buttons"> <div id="buttons">
<div class="overview"> <div class="overview">

2
verse/templates/distribusiworkflow/editcss.html → verse/distribusikan/templates/distribusikan/distribusiworkflow/editcss.html

@ -4,7 +4,7 @@
<p><a href="/editor">Go to CSS editor</a></p> <p><a href="/editor">Go to CSS editor</a></p>
{% else %} {% else %}
<p> <p>
You need to upload your files first before you can a css theme You need to upload your files first before you can select a css theme
for your files. for your files.
</p> </p>
<p><a href="#upload">Go to Step 1</a></p> <p><a href="#upload">Go to Step 1</a></p>

2
verse/templates/distribusiworkflow/launch.html → verse/distribusikan/templates/distribusikan/distribusiworkflow/launch.html

@ -3,7 +3,7 @@
<p class="tooltip">Run distribusi on your files. This will generate your website and make <p class="tooltip">Run distribusi on your files. This will generate your website and make
your content public. <span class="tooltiptext">Distribusi will unpack your zip file and turn it into a website! your content public. <span class="tooltiptext">Distribusi will unpack your zip file and turn it into a website!
</span></p> </span></p>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusi') }}"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.distribusi') }}">
{{ distribusiform.csrf_token }} {{ distribusiform.csrf_token }}
{% if files_uploaded or distribusi_live %} {% if files_uploaded or distribusi_live %}
<fieldset class="button required"> <fieldset class="button required">

2
verse/templates/distribusiworkflow/selector.html → verse/distribusikan/templates/distribusikan/distribusiworkflow/selector.html

@ -1,7 +1,7 @@
<div id="distribusi" class="workflow"> <div id="distribusi" class="workflow">
<h2>Welcome back to your Distribusi</h2> <h2>Welcome back to your Distribusi</h2>
<p>You have already uploaded a distribusi website:</p> <p>You have already uploaded a distribusi website:</p>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('selector') }}"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.selector') }}">
{{ selectorform.csrf_token }} {{ selectorform.csrf_token }}
<fieldset class="required"> <fieldset class="required">
{{ selectorform.distribusis.label }} {{ selectorform.distribusis.label }}

8
verse/templates/distribusiworkflow/theme.html → verse/distribusikan/templates/distribusikan/distribusiworkflow/theme.html

@ -4,7 +4,7 @@
step 3.</p> step 3.</p>
<p>Don't forget to press Save</p> <p>Don't forget to press Save</p>
<hr> <hr>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('theme') }}"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.theme') }}">
{{ themeform.csrf_token }} {{ themeform.csrf_token }}
<fieldset class="required"> <fieldset class="required">
{{ themeform.theme.label }} {{ themeform.theme.label }}
@ -16,14 +16,14 @@
</fieldset> </fieldset>
{% else %} {% else %}
<p> <p>
You need to upload your files first before you can a css theme You need to upload your files first before you can select a css theme
for your files. for your files.
</p> </p>
<a href="#upload">Go to Step 1</a> <a href="#upload">Go to Step 1</a>
{% endif %} {% endif %}
</form> </form>
<hr> <hr>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('theme') }}"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.theme') }}">
{{ publicthemeform.csrf_token }} {{ publicthemeform.csrf_token }}
<fieldset id="publicthemes" class="required"> <fieldset id="publicthemes" class="required">
{{ publicthemeform.publicthemes.label }} {{ publicthemeform.publicthemes.label }}
@ -35,7 +35,7 @@
</fieldset> </fieldset>
{% else %} {% else %}
<p> <p>
You need to upload your files first before you can a css theme You need to upload your files first before you can select a css theme
for your files. for your files.
</p> </p>
<a href="#upload">Go to Step 1</a> <a href="#upload">Go to Step 1</a>

23
verse/templates/distribusiworkflow/upload.html → verse/distribusikan/templates/distribusikan/distribusiworkflow/upload.html

@ -1,7 +1,7 @@
<div id="upload" class="workflow"> <div id="upload" class="workflow">
<h2>Step 1: Upload</h2> <h2>Step 1: Upload</h2>
<p>Upload your files here:</p> <p>Upload your files here:</p>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('upload') }}"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.upload') }}">
{{ uploadform.csrf_token }} {{ uploadform.csrf_token }}
<fieldset class="required"> <fieldset class="required">
{{ uploadform.sitename.label }} {{ uploadform.sitename.label }}
@ -11,28 +11,19 @@
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<fieldset class="required"> <fieldset class="required">
{{ uploadform.academicyear.label }} {{ uploadform.year.label }}
<div class="selector-style"> <div class="selector-style">
{{ uploadform.academicyear }} {{ uploadform.year }}
{% for message in uploadform.academicyear.errors %} {% for message in uploadform.year.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</div> </div>
</fieldset> </fieldset>
<fieldset class="required"> <fieldset class="required">
{{ uploadform.term.label }} {{ uploadform.category.label }}
<div class="selector-style"> <div class="selector-style">
{{ uploadform.term }} {{ uploadform.category }}
{% for message in uploadform.term.errors %} {% for message in uploadform.category.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</div>
</fieldset>
<fieldset class="required">
{{ uploadform.course.label }}
<div class="selector-style">
{{ uploadform.course }}
{% for message in uploadform.course.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</div> </div>

4
verse/templates/editor.html → verse/distribusikan/templates/distribusikan/editor.html

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<form method="POST" enctype="multipart/form-data" action="{{ url_for('editor') }}" class="editform"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.editor') }}" class="editform">
<div class="editareas"> <div class="editareas">
<div class="editarea editor"> <div class="editarea editor">
<fieldset class="required"> <fieldset class="required">

14
verse/themeselector.py → verse/distribusikan/themeselector.py

@ -1,15 +1,15 @@
import os import os
import shutil import shutil
from flask import render_template
from statuspengguna.helper import UserHelper from flask import render_template
from distribusisinfo import DistribusisInfo
from forms.uploadform import UploadForm from distribusikan.distribusisinfo import DistribusisInfo
from forms.distribusiform import DistribusiForm from forms.distribusiform import DistribusiForm
from forms.themeform import ThemeForm
from forms.publicthemeform import PublicThemeForm from forms.publicthemeform import PublicThemeForm
from forms.selectorform import SelectorForm from forms.selectorform import SelectorForm
from forms.themeform import ThemeForm
from forms.uploadform import UploadForm
from statuspengguna.helper import UserHelper
def ThemeSelector(): def ThemeSelector():
@ -30,9 +30,7 @@ def ThemeSelector():
) )
MoveCssToUserFolder(current_distribusi, copycssfile) MoveCssToUserFolder(current_distribusi, copycssfile)
return RenderDistribusiTemplate( return RenderDistribusiTemplate(
themeform, themeform, publicthemeform, current_distribusi
publicthemeform,
current_distribusi
) )

28
verse/upload.py → verse/distribusikan/upload.py

@ -1,22 +1,22 @@
import os import os
import shutil import shutil
from flask import flash from flask import flash
from flask_login import current_user from flask_login import current_user
from sqlalchemy.exc import ( from sqlalchemy.exc import (
IntegrityError, DatabaseError,
InvalidRequestError,
DataError, DataError,
IntegrityError,
InterfaceError, InterfaceError,
DatabaseError, InvalidRequestError,
) )
from app import db
from usermodel import User
from distribusimodel import Distribusis
from statuspengguna.helper import UserHelper from app import db
from distribusiselector import SelectCurrentDistribusi from distribusikan.distribusiselector import SelectCurrentDistribusi
from forms.uploadform import UploadForm from 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 UploadNewDistribusi(uploadfolder):
@ -27,9 +27,8 @@ def UploadNewDistribusi(uploadfolder):
newdistribusi = Distribusis( newdistribusi = Distribusis(
distribusiname=uploadform.sitename.data, distribusiname=uploadform.sitename.data,
userid=user.id, userid=user.id,
term=uploadform.term.data, category=uploadform.category.data,
course=uploadform.course.data, year=uploadform.year.data,
year=uploadform.academicyear.data,
tags=uploadform.tags.data, tags=uploadform.tags.data,
) )
user.currentdistribusi = uploadform.sitename.data user.currentdistribusi = uploadform.sitename.data
@ -71,9 +70,8 @@ def UploadUpdatedFiles(uploadfolder):
distribusi = Distribusis.query.filter_by( distribusi = Distribusis.query.filter_by(
distribusiname=current_distribusi distribusiname=current_distribusi
).first() ).first()
distribusi.term = uploadform.term.data distribusi.category = uploadform.category.data
distribusi.course = uploadform.course.data distribusi.year = uploadform.year.data
distribusi.year = uploadform.academicyear.data
distribusi.tags = uploadform.tags.data distribusi.tags = uploadform.tags.data
distribusi.visible = False distribusi.visible = False
db.session.commit() db.session.commit()

13
verse/uploadpage.py → verse/distribusikan/uploadpage.py

@ -1,20 +1,21 @@
from flask import render_template 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.distribusiform import DistribusiForm
from forms.themeform import ThemeForm
from forms.publicthemeform import PublicThemeForm from forms.publicthemeform import PublicThemeForm
from forms.selectorform import SelectorForm from forms.selectorform import SelectorForm
from forms.themeform import ThemeForm
# UserPengguna # UserPengguna
from statuspengguna.helper import UserHelper from statuspengguna.helper import UserHelper
from upload import UploadNewDistribusi, UploadUpdatedFiles
from distribusisinfo import DistribusisInfo
from distribusiselector import SelectorVisible
def UploadPage(uploadfolder): def UploadPage():
"render upload page section of distribusi workflow" "render upload page section of distribusi workflow"
uploadfolder = APP.config["UPLOAD_FOLDER"]
distribusiform = DistribusiForm() distribusiform = DistribusiForm()
themeform = ThemeForm() themeform = ThemeForm()
publicthemeform = PublicThemeForm() publicthemeform = PublicThemeForm()

8
verse/forms/admindistribusiform.py

@ -1,9 +1,7 @@
"""Form object declaration.""" """Form object declaration."""
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import ( from wtforms import BooleanField, SubmitField
BooleanField,
SubmitField,
)
class AdminDistribusiForm(FlaskForm): class AdminDistribusiForm(FlaskForm):
@ -15,7 +13,7 @@ class AdminDistribusiForm(FlaskForm):
class DistribusiListForm(AdminDistribusiForm): class DistribusiListForm(AdminDistribusiForm):
pass pass
for (i, distribusi) in enumerate(distribusis): for i, distribusi in enumerate(distribusis):
setattr( setattr(
DistribusiListForm, DistribusiListForm,
f"distribusi_{i}", f"distribusi_{i}",

12
verse/forms/adminuserform.py

@ -1,9 +1,7 @@
"""Form object declaration.""" """Form object declaration."""
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import ( from wtforms import BooleanField, SubmitField
SubmitField,
BooleanField,
)
class AdminUserForm(FlaskForm): class AdminUserForm(FlaskForm):
@ -13,7 +11,7 @@ class AdminUserForm(FlaskForm):
class UserListForm(AdminUserForm): class UserListForm(AdminUserForm):
pass pass
for (i, user) in enumerate(users): for i, user in enumerate(users):
setattr( setattr(
UserListForm, UserListForm,
f"user_{i}", f"user_{i}",
@ -22,8 +20,4 @@ class AdminUserForm(FlaskForm):
return UserListForm() return UserListForm()
tutors = SubmitField("Are tutors")
nottutors = SubmitField("Are not tutors")
delete = SubmitField("Delete") delete = SubmitField("Delete")

12
verse/forms/editorform.py

@ -1,15 +1,15 @@
"""Form to save your CSS editor work.""" """Form to save your CSS editor work."""
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField, FileSize
from wtforms import ( from wtforms import (
StringField,
TextAreaField,
BooleanField, BooleanField,
StringField,
SubmitField, SubmitField,
TextAreaField,
validators,
) )
from wtforms import validators
from wtforms.validators import Length from wtforms.validators import Length
from flask_wtf.file import FileField, FileAllowed, FileSize
from flask_wtf import FlaskForm
class EditorForm(FlaskForm): class EditorForm(FlaskForm):

8
verse/forms/forgotpasswordform.py

@ -1,12 +1,8 @@
"""Forgotten password form to help user.""" """Forgotten password form to help user."""
from wtforms import (
StringField,
SubmitField,
)
from wtforms import validators
from wtforms.validators import Length, Email
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, validators
from wtforms.validators import Email, Length
class ForgotPasswordForm(FlaskForm): class ForgotPasswordForm(FlaskForm):

9
verse/forms/loginform.py

@ -1,13 +1,8 @@
"""Login form to validate user.""" """Login form to validate user."""
from wtforms import (
StringField,
SubmitField,
PasswordField,
)
from wtforms import validators
from wtforms.validators import Length, Email
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField, validators
from wtforms.validators import Email, Length
class LoginForm(FlaskForm): class LoginForm(FlaskForm):

6
verse/forms/publicthemeform.py

@ -1,9 +1,7 @@
"""Form object declaration.""" """Form object declaration."""
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import ( from wtforms import RadioField, SubmitField
RadioField,
SubmitField,
)
class PublicThemeForm(FlaskForm): class PublicThemeForm(FlaskForm):

18
verse/forms/registerform.py

@ -1,22 +1,13 @@
"""Register form to make a new user.""" """Register form to make a new user."""
from wtforms import (
StringField,
SubmitField,
PasswordField,
)
from wtforms import validators
from wtforms.validators import Length, Email, EqualTo, ValidationError
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField, validators
from wtforms.validators import Email, EqualTo, Length, ValidationError
class RegisterForm(FlaskForm): class RegisterForm(FlaskForm):
"""Register for distribusi-verse form class""" """Register for distribusi-verse form class"""
def hremail(form, field):
if not field.data.endswith("@hr.nl"):
raise ValidationError("Only HRO accounts are allowed.")
username = StringField( username = StringField(
"Username:", "Username:",
validators=[validators.InputRequired(), Length(3, 150)], validators=[validators.InputRequired(), Length(3, 150)],
@ -27,9 +18,8 @@ class RegisterForm(FlaskForm):
validators=[ validators=[
validators.InputRequired(), validators.InputRequired(),
Email(), Email(),
Length(6, 64), Length(6, 128),
hremail, ],
]
) )
password = PasswordField( password = PasswordField(

8
verse/forms/resetpasswordform.py

@ -1,12 +1,8 @@
"""Reset Password Form form to reset a users PasswordField.""" """Reset Password Form form to reset a users PasswordField."""
from wtforms import (
SubmitField,
PasswordField,
)
from wtforms import validators
from wtforms.validators import Length, EqualTo
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import PasswordField, SubmitField, validators
from wtforms.validators import EqualTo, Length
class ResetPasswordForm(FlaskForm): class ResetPasswordForm(FlaskForm):

2
verse/forms/selectorform.py

@ -1,5 +1,5 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import SubmitField, SelectField from wtforms import SelectField, SubmitField
class SelectorForm(FlaskForm): class SelectorForm(FlaskForm):

6
verse/forms/themeform.py

@ -1,9 +1,7 @@
"""Form object declaration.""" """Form object declaration."""
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import ( from wtforms import RadioField, SubmitField
RadioField,
SubmitField,
)
class ThemeForm(FlaskForm): class ThemeForm(FlaskForm):

91
verse/forms/uploadform.py

@ -1,18 +1,20 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired, FileSize from flask_wtf.file import FileAllowed, FileField, FileRequired, FileSize
from wtforms import validators from wtforms import (
IntegerField,
SelectField,
StringField,
SubmitField,
validators,
)
from wtforms.validators import ( from wtforms.validators import (
DataRequired,
Length, Length,
NumberRange, NumberRange,
DataRequired,
ValidationError, ValidationError,
) )
from wtforms import (
SubmitField, from app import settings
StringField,
IntegerField,
SelectField,
)
class UploadForm(FlaskForm): class UploadForm(FlaskForm):
@ -22,64 +24,61 @@ class UploadForm(FlaskForm):
if field.data.lower() == "new": if field.data.lower() == "new":
raise ValidationError("Name has to be unique and not just new.") raise ValidationError("Name has to be unique and not just new.")
def category_choices():
APP = settings()
config_categories = APP.config["categories"]
categories = []
for config_category in config_categories:
categories.append((config_category, config_category))
return categories
def year_choices():
APP = settings()
start_time = APP.config["start_time"]
end_time = APP.config["end_time"]
year_range = range(start_time.year, end_time.year, 1)
year_choices = []
for year in year_range:
year_choices.append((str(year), str(year)))
return year_choices
sitename = StringField( sitename = StringField(
"Name of your website:", "Name of your archive section:",
validators=[validators.InputRequired(), Length(2, 100), distribusiname], validators=[
validators.InputRequired(),
Length(2, 100),
distribusiname,
],
) )
academicyear = SelectField( year = SelectField(
"Academic year:", "Year:",
validate_choice=True, validate_choice=True,
coerce=str, coerce=str,
choices=[ choices=year_choices,
(u'2021-2022', u'2021-2022'),
(u'2022-2023', u'2022-2023'),
(u'2023-2024', u'2023-2024'),
(u'2024-2025', u'2024-2025'),
(u'2020-2021', u'past: 2020-2021'),
],
option_widget=None, option_widget=None,
validators=[DataRequired()] validators=[DataRequired()],
) )
term = SelectField( category = SelectField(
"Term:", "Category:",
validate_choice=True, validate_choice=True,
coerce=str, coerce=str,
choices=[ choices=category_choices,
(u'1.2', u'1.2'),
(u'2.3', u'2.3'),
(u'3.1', u'3.1'),
(u'4.1', u'4.1'),
(u'4.2', u'4.2'),
],
option_widget=None, option_widget=None,
validators=[DataRequired()] validators=[DataRequired()],
) )
tags = StringField( tags = StringField(
"Add tags, seperated by commas. No need for the '#' sign:", "Add tags, seperated by commas. No need for the '#' sign:",
validators=[validators.InputRequired(), Length(2, 500)], validators=[validators.InputRequired(), Length(2, 500)],
) )
course = SelectField(
u'Course:',
validate_choice=True,
coerce=str,
choices=[
('hacking', u'Autonomous - Hacking'),
('digitalcraft', u'Autonomous - Digital Craft'),
('criticalstudies', u'Autonomous - Critical Studies'),
('publicprivate', u'Autonomous - Public&Private'),
],
option_widget=None,
validators=[DataRequired()]
)
zipfile = FileField( zipfile = FileField(
"Upload your zip file with content here:", "Upload your zip file with content here:",
validators=[ validators=[
FileAllowed(["zip"], "Zip archives only!"), FileAllowed(["zip"], "Zip archives only!"),
FileRequired(), FileRequired(),
FileSize( FileSize(
max_size=104857600, max_size=1073741824,
message="Zipfile size must be smaller than 100MB", message="Zipfile size must be smaller than 100MB",
), ),
], ],

0
verse/data/.gitignore → verse/instance/.gitignore

23
verse/migrations/env.py

@ -3,9 +3,8 @@ from __future__ import with_statement
import logging import logging
from logging.config import fileConfig from logging.config import fileConfig
from flask import current_app
from alembic import context from alembic import context
from flask import current_app
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
@ -14,17 +13,19 @@ config = context.config
# Interpret the config file for Python logging. # Interpret the config file for Python logging.
# This line sets up loggers basically. # This line sets up loggers basically.
fileConfig(config.config_file_name) fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env') logger = logging.getLogger("alembic.env")
# add your model's MetaData object here # add your model's MetaData object here
# for 'autogenerate' support # for 'autogenerate' support
# from myapp import mymodel # from myapp import mymodel
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
config.set_main_option( config.set_main_option(
'sqlalchemy.url', "sqlalchemy.url",
str(current_app.extensions['migrate'].db.get_engine().url).replace( str(current_app.extensions["migrate"].db.get_engine().url).replace(
'%', '%%')) "%", "%%"
target_metadata = current_app.extensions['migrate'].db.metadata ),
)
target_metadata = current_app.extensions["migrate"].db.metadata
# other values from the config, defined by the needs of env.py, # other values from the config, defined by the needs of env.py,
# can be acquired: # can be acquired:
@ -65,20 +66,20 @@ def run_migrations_online():
# when there are no changes to the schema # when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives): def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False): if getattr(config.cmd_opts, "autogenerate", False):
script = directives[0] script = directives[0]
if script.upgrade_ops.is_empty(): if script.upgrade_ops.is_empty():
directives[:] = [] directives[:] = []
logger.info('No changes in schema detected.') logger.info("No changes in schema detected.")
connectable = current_app.extensions['migrate'].db.get_engine() connectable = current_app.extensions["migrate"].db.get_engine()
with connectable.connect() as connection: with connectable.connect() as connection:
context.configure( context.configure(
connection=connection, connection=connection,
target_metadata=target_metadata, target_metadata=target_metadata,
process_revision_directives=process_revision_directives, process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args **current_app.extensions["migrate"].configure_args,
) )
with context.begin_transaction(): with context.begin_transaction():

17
verse/models/distribusi_file_model.py

@ -0,0 +1,17 @@
from app import db
class DistribusiFiles(db.Model):
"""Distribusi file model class for a single file in a distribusi"""
__tablename__ = "distribusi_files"
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(300), nullable=False, unique=True)
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)
tags = db.Column(db.String(500), nullable=True, unique=False)
def __repr__(self):
return "<Distribusi_File %r>" % self.distribusiname

6
verse/distribusimodel.py → verse/models/distribusi_model.py

@ -9,14 +9,10 @@ class Distribusis(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
distribusiname = db.Column(db.String(300), nullable=False, unique=True) distribusiname = db.Column(db.String(300), nullable=False, unique=True)
userid = db.Column(db.Integer, db.ForeignKey("users.id")) userid = db.Column(db.Integer, db.ForeignKey("users.id"))
term = db.Column(db.String(5), nullable=False, unique=False) category = db.Column(db.String(500), nullable=True, unique=False)
course = db.Column(db.String(500), nullable=True, unique=False)
# Academic year eg:2020-2021, so no need for a Datetime object
year = db.Column(db.String(9), nullable=True, unique=False) year = db.Column(db.String(9), nullable=True, unique=False)
tags = db.Column(db.String(500), nullable=True, unique=False) tags = db.Column(db.String(500), nullable=True, unique=False)
publictheme = db.Column(db.String(300), unique=True, nullable=True) publictheme = db.Column(db.String(300), unique=True, nullable=True)
visible = db.Column(db.Boolean, default=False) visible = db.Column(db.Boolean, default=False)
def __repr__(self): def __repr__(self):

5
verse/usermodel.py → verse/models/user_model.py

@ -1,6 +1,7 @@
from app import db
from flask_login import UserMixin from flask_login import UserMixin
from app import db
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
"""User model class for a user in distribusi-verse""" """User model class for a user in distribusi-verse"""
@ -14,8 +15,6 @@ class User(UserMixin, db.Model):
currentdistribusi = db.Column(db.String(300), nullable=True, unique=False) currentdistribusi = db.Column(db.String(300), nullable=True, unique=False)
resethash = db.Column(db.String(300), nullable=True, unique=True) resethash = db.Column(db.String(300), nullable=True, unique=True)
resettime = db.Column(db.DateTime) resettime = db.Column(db.DateTime)
#active = db.Column(db.Boolean, default=False)
tutor = db.Column(db.Boolean, default=False)
admin = db.Column(db.Boolean, default=False) admin = db.Column(db.Boolean, default=False)
def __repr__(self): def __repr__(self):

4
verse/settings.toml

@ -0,0 +1,4 @@
title = "Varia Archive X Distribusi-Verse"
categories = ["event","gathering","work session","workgroup","performance","music event"]
start_time = 2017-11-03
end_time = 2025-12-31

120
verse/start.py

@ -1,50 +1,41 @@
"""This is the main flask distribusi page""" """This is the main flask distribusi page"""
from datetime import timedelta from datetime import timedelta
from flask import ( from flask import (
render_template, Blueprint,
redirect, redirect,
url_for, render_template,
session,
send_from_directory, send_from_directory,
Blueprint, session,
) url_for,
from flask_login import (
logout_user,
login_required,
current_user,
) )
from flask_login import current_user, login_required, logout_user
from flask_mail import Mail from flask_mail import Mail
from flask_wtf.csrf import CSRFError from flask_wtf.csrf import CSRFError
from app import create_app, login_manager
from usermodel import User
from distribusimodel import Distribusis
# Use upload form to populate filters
from forms.uploadform import UploadForm
# Interface! these are seperate files in main folder # Interface! these are seperate files in main folder
from adminpage import AdminPage from adminpage import AdminPage
from editor import Editor from app import create_app, login_manager
from themeselector import ThemeSelector from distribusikan.distribusikan import distribusikan
from distribusiworkflow import DistribusiWorkflow from distribusikan.distribusisinfo import DistribusisInfo
from distribusiselector import DistribusiSelector
from uploadpage import UploadPage
# UserPengguna # Use upload form to populate filters
from 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.helper import UserHelper
from statuspengguna.loginuser import LoginUser from statuspengguna.loginuser import login_section
from statuspengguna.registeruser import RegisterUser from statuspengguna.registeruser import register_user
from statuspengguna.forgotpassword import ForgotPassword
from statuspengguna.resetpassword import ResetPassword
# Distribusi Information
from distribusisinfo import DistribusisInfo
APP = create_app() APP = create_app()
stash_page = Blueprint("stash_page", __name__, static_folder="stash") stash_page = Blueprint("stash_page", __name__, static_folder="stash")
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(stash_page)
mail = Mail(APP)
@APP.before_request @APP.before_request
@ -56,7 +47,6 @@ def session_handler():
@APP.route("/") @APP.route("/")
def index(): def index():
UserHelper.reset_user_state() UserHelper.reset_user_state()
# http://localhost:5000/themes/publicthemes/RomeroTape/blueskies.css
uploadform = UploadForm() uploadform = UploadForm()
distribusis = DistribusisInfo.visibledistribusis() distribusis = DistribusisInfo.visibledistribusis()
distribusisindex = {} distribusisindex = {}
@ -65,23 +55,19 @@ def index():
singledistribusi = { singledistribusi = {
"username": user.username, "username": user.username,
"publictheme": distribusi.publictheme, "publictheme": distribusi.publictheme,
"term": distribusi.term, "category": distribusi.category,
"course": distribusi.course,
"year": distribusi.year, "year": distribusi.year,
"tags": distribusi.tags.split(","), "tags": distribusi.tags.split(","),
} }
distribusisindex[distribusi.distribusiname] = singledistribusi distribusisindex[distribusi.distribusiname] = singledistribusi
years = uploadform.academicyear.choices years = uploadform.year.choices
terms = uploadform.term.choices categories = uploadform.category.choices
courses = uploadform.course.choices
adminuser = isadminuser() adminuser = isadminuser()
template = render_template( template = render_template(
"index.html", "base/index.html",
distribusisindex=distribusisindex, distribusisindex=distribusisindex,
years=years, years=years,
terms=terms, categories=categories,
courses=courses,
adminuser=adminuser, adminuser=adminuser,
) )
return template return template
@ -89,26 +75,7 @@ def index():
@APP.route("/help") @APP.route("/help")
def help(): def help():
return render_template("help.html") return render_template("base/help.html")
@APP.route("/distribusi", methods=["GET", "POST"])
@login_required
def distribusi():
return DistribusiWorkflow()
@APP.route("/upload", methods=["POST"])
@login_required
def upload():
uploadfolder = APP.config["UPLOAD_FOLDER"]
return UploadPage(uploadfolder)
@APP.route("/theme", methods=["GET", "POST"])
@login_required
def theme():
return ThemeSelector()
@APP.route("/publicthemes/<path>") @APP.route("/publicthemes/<path>")
@ -116,22 +83,9 @@ def publicthemes(path):
distribusi = Distribusis.query.filter_by(distribusiname=path).first() distribusi = Distribusis.query.filter_by(distribusiname=path).first()
publicthemefolder = f"publicthemes/{distribusi.distribusiname}/" publicthemefolder = f"publicthemes/{distribusi.distribusiname}/"
cssfile = f"{publicthemefolder}/{distribusi.publictheme}.css" cssfile = f"{publicthemefolder}/{distribusi.publictheme}.css"
print(cssfile)
return send_from_directory("themes", cssfile, as_attachment=True) return send_from_directory("themes", cssfile, as_attachment=True)
@APP.route("/editor", methods=["GET", "POST"])
@login_required
def editor():
return Editor()
@APP.route("/selector", methods=["GET", "POST"])
@login_required
def selector():
return DistribusiSelector()
@APP.route("/stash") @APP.route("/stash")
def shortstashurl(): def shortstashurl():
return redirect(url_for("index")) return redirect(url_for("index"))
@ -152,26 +106,6 @@ def logout():
return redirect(url_for("index")) return redirect(url_for("index"))
@APP.route("/login", methods=["GET", "POST"])
def login():
return LoginUser()
@APP.route("/register", methods=["GET", "POST"])
def register():
return RegisterUser()
@APP.route("/forgotpassword", methods=["GET", "POST"])
def forgotpassword():
return ForgotPassword(mail)
@APP.route("/resetpassword/<path>", methods=["GET", "POST"])
def resetpassword(path):
return ResetPassword(path)
@APP.errorhandler(CSRFError) @APP.errorhandler(CSRFError)
def handle_csrf_error(e): def handle_csrf_error(e):
return render_template("csrf_error.html", reason=e.description), 400 return render_template("csrf_error.html", reason=e.description), 400

2
verse/static/css/dropdown.css

@ -1,5 +1,5 @@
/* Dropdown Button */ /* Dropdown Button */
/* for sorting on Academicyear, Term, Course /* for sorting on year and category
*/ */
button { button {
background-color: #E0B0FF; background-color: #E0B0FF;

18
verse/static/css/style.css

@ -2,8 +2,8 @@ body
{ {
font-family: monospace, monospace; font-family: monospace, monospace;
font-size: 15px; font-size: 15px;
background-color: #272a33; background-color: #fdfdfd;
color:#E0B0FF; color:#29d148;
word-wrap: break-word; word-wrap: break-word;
line-height: 1.1; line-height: 1.1;
} }
@ -12,7 +12,7 @@ div#login{
width: 30%; width: 30%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
background-color:#272a33; background-color:#fdfdfd;
text-decoration: none; text-decoration: none;
} }
@ -24,10 +24,10 @@ div#login form {
} }
input[type=text], input[type=password], input[type=file] { input[type=text], input[type=password], input[type=file] {
color: white; color: #C397DF;
width: 18em; width: 18em;
max-width: 18em; max-width: 18em;
background-color: #2D3039; background-color: #fdfdfd;
border: 1px solid #E0B0FF; border: 1px solid #E0B0FF;
} }
@ -41,7 +41,7 @@ div#upload form {
padding-left: auto; padding-left: auto;
padding-right: auto; padding-right: auto;
width: 31em; width: 31em;
background-color:#30333f; background-color:#fdfdfd;
text-decoration: none; text-decoration: none;
scroll-behavior: smooth; scroll-behavior: smooth;
border-style: outset; border-style: outset;
@ -149,12 +149,6 @@ input[type="submit"]:disabled:focus {
background-color: #62b264; background-color: #62b264;
} }
#tutors {
color: black;
background-color: #62b264;
}
/* unvisited link */ /* unvisited link */
a:link { a:link {
color: #fff600; color: #fff600;

5
verse/static/js/dropdown.js

@ -24,9 +24,8 @@ function filterSelection(c, name, id) {
} }
function resetDropDownButtons(){ function resetDropDownButtons(){
document.getElementById("Academicyear").innerText = "Academic year"; document.getElementById("Year").innerText = "Year";
document.getElementById("Term").innerText = "Term"; document.getElementById("Category").innerText = "Category";
document.getElementById("Course").innerText = "Course";
allactivebuttons = document.getElementsByClassName("activebtn"); allactivebuttons = document.getElementsByClassName("activebtn");
for(var i = 0;allactivebuttons.length; i++) { for(var i = 0;allactivebuttons.length; i++) {
removeClass(allactivebuttons[i], "activebtn"); removeClass(allactivebuttons[i], "activebtn");

0
verse/statuspengguna/__init__.py

26
verse/statuspengguna/forgotpassword.py

@ -1,17 +1,31 @@
from uuid import uuid1
from datetime import datetime from datetime import datetime
from uuid import uuid1
from flask import Blueprint, render_template
from flask_mail import Mail, Message
from sqlalchemy.exc import ( from sqlalchemy.exc import (
DataError,
DatabaseError, DatabaseError,
DataError,
InterfaceError, InterfaceError,
InvalidRequestError, InvalidRequestError,
) )
from flask import render_template
from flask_mail import Message
from usermodel import User from app import db, get_app
from forms.forgotpasswordform import ForgotPasswordForm from forms.forgotpasswordform import ForgotPasswordForm
from app import db from models.user_model import User
mail = Mail(get_app())
forgot_password = Blueprint(
"forgotpassword",
__name__,
template_folder="templates/statuspengguna",
static_folder="static",
)
@forgot_password.route("/", methods=["GET", "POST"])
def forgotpassword():
return ForgotPassword(mail)
def ForgotPassword(mail): def ForgotPassword(mail):

18
verse/statuspengguna/helper.py

@ -1,17 +1,18 @@
import os import os
from flask_login import current_user
from flask import flash from flask import flash
from flask_login import current_user
from sqlalchemy.exc import ( from sqlalchemy.exc import (
DataError,
DatabaseError, DatabaseError,
DataError,
InterfaceError, InterfaceError,
InvalidRequestError, InvalidRequestError,
) )
from usermodel import User
from distribusimodel import Distribusis
from distribusisinfo import DistribusisInfo
from app import db from app import db
from distribusikan.distribusisinfo import DistribusisInfo
from models.distribusi_model import Distribusis
from models.user_model import User
class UserHelper: class UserHelper:
@ -76,10 +77,7 @@ class UserHelper:
def distribusi_limit_reached(): def distribusi_limit_reached():
user = User.query.filter_by(email=current_user.email).first() user = User.query.filter_by(email=current_user.email).first()
distribusiamount = len(DistribusisInfo.getuserdistribusis(user.email)) distribusiamount = len(DistribusisInfo.getuserdistribusis(user.email))
if user.tutor and distribusiamount > 14: if distribusiamount > 19:
print("tutor already has 15 distribusis") print("user already has 20 distribusis")
return True
if not user.tutor and distribusiamount > 4:
print("user already has 5 distribusis")
return True return True
return False return False

31
verse/statuspengguna/loginuser.py

@ -1,15 +1,31 @@
from flask import ( from flask import (
render_template, Blueprint,
abort,
flash,
redirect, redirect,
render_template,
request, request,
flash, send_from_directory,
session,
url_for, url_for,
abort,
) )
from usermodel import User
from forms.loginform import LoginForm
from flask_login import login_user
from flask_bcrypt import check_password_hash from flask_bcrypt import check_password_hash
from flask_login import login_user
from forms.loginform import LoginForm
from models.user_model import User
login_section = Blueprint(
"login",
__name__,
template_folder="templates/statuspengguna",
static_folder="static",
)
@login_section.route("/", methods=["GET", "POST"])
def login():
return LoginUser()
def LoginUser(): def LoginUser():
@ -21,11 +37,14 @@ def LoginUser():
loginform.password.errors.append("Invalid email or password!") loginform.password.errors.append("Invalid email or password!")
return render_template("login.html", loginform=loginform) return render_template("login.html", loginform=loginform)
if check_password_hash(user.password, loginform.password.data): if check_password_hash(user.password, loginform.password.data):
print(type(user))
login_user(user) login_user(user)
flash("Logged in successfully.", "success") flash("Logged in successfully.", "success")
next = request.args.get("next") next = request.args.get("next")
if next is not None and not is_safe_url(next): # noqa: F821 if next is not None and not is_safe_url(next): # noqa: F821
print(next)
return abort(400) return abort(400)
print("index")
return redirect(next or url_for("index")) return redirect(next or url_for("index"))
else: else:
flash("Invalid email or password!", "danger") flash("Invalid email or password!", "danger")

32
verse/statuspengguna/registeruser.py

@ -1,22 +1,30 @@
from flask import ( from flask import Blueprint, flash, redirect, render_template, url_for
render_template, from flask_bcrypt import generate_password_hash
redirect, from flask_login import login_user
flash,
url_for,
)
from sqlalchemy.exc import ( from sqlalchemy.exc import (
IntegrityError,
DataError,
DatabaseError, DatabaseError,
DataError,
IntegrityError,
InterfaceError, InterfaceError,
InvalidRequestError, InvalidRequestError,
) )
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
from usermodel import User
from forms.registerform import RegisterForm
from flask_login import login_user
from flask_bcrypt import generate_password_hash
from app import db from app import db
from forms.registerform import RegisterForm
from models.user_model import User
register_user = Blueprint(
"register",
__name__,
template_folder="templates/statuspengguna",
static_folder="static",
)
@register_user.route("/", methods=["GET", "POST"])
def register():
return RegisterUser()
def RegisterUser(): def RegisterUser():

34
verse/statuspengguna/resetpassword.py

@ -1,23 +1,33 @@
from datetime import datetime from datetime import datetime
from flask import (
render_template, from flask import flash, redirect, render_template, url_for
redirect, from flask_bcrypt import generate_password_hash
flash, from flask_login import login_user
url_for,
)
from sqlalchemy.exc import ( from sqlalchemy.exc import (
IntegrityError,
DataError,
DatabaseError, DatabaseError,
DataError,
IntegrityError,
InterfaceError, InterfaceError,
InvalidRequestError, InvalidRequestError,
) )
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
from usermodel import User
from forms.resetpasswordform import ResetPasswordForm
from flask_login import login_user
from flask_bcrypt import generate_password_hash
from app import db from app import db
from forms.resetpasswordform import ResetPasswordForm
from models.user_model import User
from statuspengguna import statuspengguna
reset_password = Blueprint(
"reset_password",
__name__,
template_folder="templates/statuspengguna",
static_folder="static",
)
@reset_password.route("/resetpassword/<path>", methods=["GET", "POST"])
def resetpassword(path):
return ResetPassword(path)
def ResetPassword(path): def ResetPassword(path):

69
verse/statuspengguna/static/css/dropdown.css

@ -0,0 +1,69 @@
/* 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;
}
}

50
verse/statuspengguna/static/css/editor.css

@ -0,0 +1,50 @@
.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;
}

53
verse/statuspengguna/static/css/selector.css

@ -0,0 +1,53 @@
.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;
}

281
verse/statuspengguna/static/css/style.css

@ -0,0 +1,281 @@
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
*/

6
verse/statuspengguna/static/icons/about.txt

@ -0,0 +1,6 @@
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))

BIN
verse/statuspengguna/static/icons/android-chrome-192x192.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
verse/statuspengguna/static/icons/android-chrome-512x512.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
verse/statuspengguna/static/icons/apple-touch-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
verse/statuspengguna/static/icons/favicon-16x16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

BIN
verse/statuspengguna/static/icons/favicon-32x32.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

BIN
verse/statuspengguna/static/icons/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
verse/statuspengguna/static/icons/site.webmanifest

@ -0,0 +1 @@
{"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"}

92
verse/statuspengguna/static/js/dropdown.js

@ -0,0 +1,92 @@
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");
}
}

19
verse/statuspengguna/static/js/editorupdate.js

@ -0,0 +1,19 @@
function update() {
var html = document.getElementById("html");
var css = document.getElementById("css");
var code = document.getElementById("code").contentWindow.document;
document.body.onkeyup = function(){
code.open();
code.writeln(html.value+"<style>"+css.value+"</style>");
code.close();
};
document.addEventListener("DOMContentLoaded", function(){
code.open();
code.writeln(html.value+"<style>"+css.value+"</style>");
code.close();
});
};
update();

25
verse/statuspengguna/static/js/script.js

@ -0,0 +1,25 @@
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")
// }

75
verse/statuspengguna/static/svg/arrow_1.svg

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="240"
height="320"
viewBox="0 0 63.5 84.666667"
version="1.1"
id="svgArrowOne"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
sodipodi:docname="arrow_1.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="false"
units="px"
inkscape:zoom="1"
inkscape:cx="236"
inkscape:cy="70"
inkscape:window-width="1916"
inkscape:window-height="1041"
inkscape:window-x="1366"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<circle
id="path865"
style="fill:#060000;stroke:#fcfffe;stroke-width:0.264583"
cx="167.50412"
cy="23.866825"
r="0.211455" />
<circle
id="path867"
style="fill:#e0b0ff;stroke:#fcfffe;stroke-width:0.264583;fill-opacity:1"
cx="167.50412"
cy="23.866825"
r="0.211455" />
<circle
id="path2716"
style="fill:#d28cff;stroke:#fcfffe;stroke-width:0.264583"
cx="31.623327"
cy="-54.739456"
r="0.1057275" />
<circle
id="path2718"
style="fill:#d28cff;stroke:#fcfffe;stroke-width:0.264583"
cx="31.623327"
cy="-54.739456"
r="0.1057275" />
<path
style="fill:#d28cff;fill-opacity:1;stroke:none;stroke-width:0.799999"
id="path2732"
d="m 19.948434,3.1817159 c 1.290131,0.6550689 2.512225,1.4411814 3.755485,2.1807733 1.793529,1.0335667 3.565214,2.1021852 5.308482,3.2186226 0.483624,0.3097273 0.976024,0.6070071 1.443413,0.9407313 0.457224,0.3264641 0.885243,0.6920049 1.327862,1.0380079 2.123546,1.795334 3.693565,4.041838 4.640125,6.658655 0.242448,0.67026 0.417224,1.3631 0.625834,2.04465 0.468096,1.928539 0.850726,3.889399 1.025174,5.869019 0.146409,1.661472 0.09044,3.337613 0.06891,5.002416 -0.136327,2.53704 -0.886677,4.958631 -1.507659,7.401952 -0.562415,2.08257 -2.195008,3.525988 -3.642546,5.02172 -1.668862,1.796187 -3.430227,3.511629 -4.96204,5.428684 -1.232474,1.501685 -2.187443,3.200289 -3.147382,4.881737 -1.04435,1.719871 -1.86704,3.57665 -2.548164,5.467477 -0.710967,2.173634 -0.907385,4.460639 -1.024063,6.730664 0.02091,1.346742 0.05687,2.707192 0.493364,3.995719 0.26353,0.777933 0.396191,0.938664 0.795115,1.653278 0.654367,1.054661 1.365786,2.066507 2.102377,3.064017 0,0 3.841737,-0.248322 3.841737,-0.248322 v 0 c -0.75511,-0.983935 -1.542788,-1.939549 -2.17802,-3.011543 -0.382704,-0.652497 -0.537692,-0.849823 -0.799641,-1.562883 -0.455204,-1.239128 -0.493109,-2.567154 -0.52219,-3.871724 0.107265,-2.234121 0.278343,-4.485997 1.012129,-6.617327 0.708365,-1.865136 1.556754,-3.687384 2.575162,-5.404406 0.953056,-1.662009 1.928715,-3.32604 3.127534,-4.825259 1.51579,-1.902804 3.233504,-3.631578 4.905512,-5.395754 1.510255,-1.536197 3.212571,-3.04624 3.747334,-5.218498 0.540033,-2.47709 1.199993,-4.93743 1.330515,-7.483557 0.02558,-1.263529 0.06192,-2.073423 0.0096,-3.332359 C 41.644752,24.222411 41.147902,21.675409 40.546318,19.16389 40.076453,17.470971 39.976922,16.848842 39.271149,15.277213 38.380408,13.293695 37.001362,11.650365 35.365848,10.236089 34.920391,9.8793346 34.491789,9.5004423 34.02947,9.1658256 33.563896,8.8288536 33.065526,8.5396762 32.585453,8.223701 30.879732,7.1010218 29.144655,6.0327407 27.391958,4.9847267 26.212505,4.238663 25.04853,3.4635151 23.848505,2.7523961 c 0,0 -3.900058,0.4293198 -3.900058,0.4293198 z" />
<path
style="fill:#d28cff;fill-opacity:1;stroke:none;stroke-width:0.799999"
id="path2736"
d="m 17.143337,69.050471 c 1.633713,0.894598 3.185906,1.900385 4.678149,3.014075 1.115423,0.934426 2.160169,1.94547 3.113059,3.044203 1.826876,2.397014 -1.146831,1.665475 4.779041,1.263248 0.383141,-1.686153 0.758291,-3.374861 1.165924,-5.055598 0.206131,-0.876517 0.390895,-1.75066 0.653066,-2.611993 0,0 -3.799028,-0.103103 -3.799028,-0.103103 v 0 c -0.220279,0.87104 -0.384717,1.750875 -0.582906,2.627255 -0.396558,1.670132 -0.773763,3.348908 -1.349638,4.968864 1.273906,0.05246 2.548034,0.09977 3.821714,0.157393 0.02885,0.0013 -0.07003,0.03579 -0.08584,0.01161 -0.06107,-0.09337 -0.07566,-0.209923 -0.112684,-0.315174 -0.133537,-0.379648 -0.146335,-0.493044 -0.361696,-0.837912 -0.08452,-0.135356 -0.188793,-0.257328 -0.283188,-0.385993 -1.017286,-1.099923 -2.077482,-2.15043 -3.232635,-3.10924 -1.452916,-1.095015 -2.937697,-2.134709 -4.528692,-3.02179 0,0 -3.87465,0.354142 -3.87465,0.354142 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

12
verse/statuspengguna/static/svg/arrow_2.svg

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="240" height="320" version="1.1" viewBox="0 0 63.5 84.667" xmlns="http://www.w3.org/2000/svg">
<circle cx="167.5" cy="23.867" r=".21146" fill="#060000" stroke="#fcfffe" stroke-width=".26458"/>
<circle cx="167.5" cy="23.867" r=".21146" fill="#e0b0ff" stroke="#fcfffe" stroke-width=".26458"/>
<g fill="#d28cff">
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
<path d="m29.598 1.3629c0.29231 0.93057 0.37445 1.9094 0.44145 2.8774 0.01955 0.99076 0.29893 1.9483 0.43855 2.9253 0.04901 1.1628 0.31687 2.2714 0.7734 3.3386 0.60807 1.244 1.6241 2.1846 2.6231 3.1104 0.76132 0.7147 1.5224 1.4053 2.414 1.9528 0.85013 0.49091 1.8227 0.71794 2.7627 0.97569 1.4036 0.22312 2.1015 0.68412 3.1203 1.648 1.1101 1.0733 1.7779 2.4463 2.4311 3.8184 0.5827 1.142 0.76303 2.3828 0.73638 3.6503-0.08765 1.5129-0.67764 2.6923-1.5432 3.8976-0.84272 1.225-2.1589 1.8621-3.4214 2.5577-1.3039 0.74177-2.5458 1.586-3.8965 2.2408-1.5184 0.78493-3.077 1.4779-4.6378 2.1709-1.2968 0.70155-2.6371 1.3714-3.7845 2.3055-1.5755 1.3431-2.9301 2.8922-4.2407 4.4866-1.0773 1.3646-2.2517 2.7443-2.9262 4.3683-0.12219 0.2942-0.20988 0.60157-0.31482 0.90236-0.3176 1.1367-0.50828 2.2967-0.36852 3.4764 0.04693 0.39614 0.19687 1.057 0.27962 1.4461 0.30758 1.2289 0.84547 2.3725 1.4798 3.4625 0.70009 1.3121 1.6509 2.4545 2.6734 3.5243 1.0661 1.0736 2.1636 2.1225 3.154 3.2675 0.78168 0.87789 1.0179 1.9433 1.1372 3.0756 0.03544 0.92388 0.11577 1.8587 0.07073 2.7841-0.01773 0.36408-0.1093 0.87867-0.16701 1.2409-0.09034 1.015-0.40258 1.9744-0.65202 2.9542-0.22907 0.93923-0.24592 1.9133-0.26994 2.875 0.01627 0.16165 0.03253 0.3233 0.04879 0.48495 0 0 3.8677-0.04599 3.8677-0.04599l-0.06033-0.42856c0.0271-0.93446 0.05799-1.8763 0.28398-2.7884 0.25461-0.98959 0.52171-1.9723 0.61567-2.9949 0.18691-1.3678 0.16751-2.7404 0.09837-4.1178-0.10464-1.2084-0.33697-2.37-1.1681-3.3092-0.98696-1.161-2.0984-2.2119-3.1752-3.2885-1.0093-1.0344-1.9609-2.1448-2.6312-3.4346-0.61359-1.0586-1.166-2.1573-1.4984-3.3403-0.02657-0.1263-0.26629-1.2542-0.28656-1.3963-0.16054-1.1256 0.0079-2.2354 0.32978-3.3186 0.10325-0.28669 0.1907-0.57958 0.30976-0.86007 0.67556-1.5915 1.8191-2.9377 2.8755-4.2834 1.3254-1.6128 2.6979-3.0863 4.332-4.3948 1.1702-0.86077 2.4531-1.5682 3.751-2.2163 1.5673-0.6912 3.1104-1.4304 4.6479-2.1857 1.3437-0.6747 2.5823-1.5317 3.8988-2.2576 1.3084-0.71914 2.6043-1.447 3.4487-2.7293 0.89598-1.305 1.4437-2.5132 1.5458-4.1199 0.04656-1.326-0.09489-2.646-0.72638-3.8363-0.70909-1.3836-1.3422-2.8175-2.4524-3.9439-0.95909-0.99062-1.6743-1.6678-3.0935-1.8533-0.92413-0.22693-1.8609-0.47835-2.7296-0.86415-0.37733-0.23195-0.7701-0.43303-1.1225-0.70362-0.56026-0.43018-0.30161-0.27292-0.85155-0.76539-0.15495-0.13876-0.31951-0.26638-0.47927-0.39957-0.94654-0.91944-2.0219-1.7437-2.6195-2.9576-0.46026-1.0278-0.6805-2.1099-0.72907-3.2345-0.13163-0.97099-0.41801-1.9171-0.4545-2.8972-0.062288-0.99479-0.16352-1.9871-0.37122-2.9636l-3.9178 0.11147z"/>
<path d="m22.025 71.064c1.1076 0.81571 1.8963 1.9751 2.6453 3.1096 0.63322 0.93788 1.2476 1.8855 1.6118 2.9617 0.28693 0.82149 0.62284 1.6252 1.0669 2.3743 1.0301 1.4304 1.5291 0.58576 4.4538 0.16545 0.14811-0.02127 0.42422-1.2095 0.46069-1.36 0.25411-0.96357 0.61812-1.6049 1.3587-2.2583 0.75053-0.69358 1.7635-0.9666 2.6863-1.3526 1.0408-0.39579 2.1353-0.68763 3.1287-1.1962 0.12805-0.06035 0.2561-0.12069 0.38414-0.18103 0 0-3.8517-0.43584-3.8517-0.43584-0.12618 0.06059-0.25236 0.12119-0.37854 0.18179-1.0095 0.4429-2.0552 0.79223-3.0867 1.1786-0.97696 0.38612-1.9942 0.73078-2.7537 1.4922-0.6784 0.69992-1.1041 1.5244-1.3418 2.4736-0.08563 0.30808-0.14275 0.60119-0.30832 0.87972-0.05051 0.08497-0.29376 0.21564-0.1952 0.22324 1.2279 0.09468 2.4644-0.01566 3.6945 0.04656 0.07116 0.0036-0.09855 0.1467-0.16824 0.13185-0.0954-0.02032-0.13522-0.1406-0.20282-0.2109-0.48406-0.68952-0.78002-1.4975-1.0935-2.2751-0.36094-1.1214-0.97173-2.1151-1.6698-3.0606-0.6571-0.99538-1.3971-2.1349-2.2397-2.9817-0.09081-0.09127-0.19994-0.16228-0.29991-0.24342l-3.9009 0.33725z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

12
verse/statuspengguna/static/svg/arrow_3.svg

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="240" height="320" version="1.1" viewBox="0 0 63.5 84.667" xmlns="http://www.w3.org/2000/svg">
<circle cx="167.5" cy="23.867" r=".21146" fill="#060000" stroke="#fcfffe" stroke-width=".26458"/>
<circle cx="167.5" cy="23.867" r=".21146" fill="#e0b0ff" stroke="#fcfffe" stroke-width=".26458"/>
<g fill="#d28cff">
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
<path d="m56.965 3.4219c0.12058 1.1521-0.2318 2.054-0.81004 3.0337-1.0875 1.5907-2.5336 2.8659-4.0843 3.9867-2.1505 1.3863-4.4585 2.4853-6.8625 3.3491-2.7815 0.78715-5.6335 1.3087-8.4879 1.7525-3.134 0.44066-6.2863 0.71397-9.4313 1.0632-3.4224 0.40837-6.8733 1.0287-10.07 2.3675-0.54185 0.22694-1.0603 0.5062-1.5904 0.75929-0.89954 0.54647-1.6469 0.92677-2.4068 1.6606-1.3388 1.2929-2.0863 2.9786-2.778 4.6654-0.87449 2.3542-1.198 4.6214-1.1672 7.1174 0.18717 1.5284 0.31332 3.0753 0.6288 4.5852 0.091869 0.43967 0.3934 1.5785 0.51155 2.0327 0.39771 1.1363 0.63254 2.4445 1.5286 3.3253 0.14764 0.14511 0.3231 0.25888 0.48465 0.38832 0.20368 0.10006 0.39798 0.22205 0.61104 0.30019 0.42615 0.15629 1.2969 0.32297 1.7205 0.41193 1.1822 0.24832 2.3631 0.46842 3.5556 0.66151 1.9566 0.35044 3.9445 0.66205 5.8228 1.331 1.3844 0.50374 2.5213 1.4098 3.6121 2.3711 1.0386 0.99728 2.1001 1.9882 2.7426 3.3026 0.16634 0.3403 0.28751 0.70088 0.43126 1.0513 0.86215 2.4558 1.0225 5.0522 0.9327 7.631-0.07031 1.5349-0.29391 3.1185-1.156 4.4316-0.12638 0.19249-0.28466 0.36202-0.42699 0.54304-0.81011 0.9275-1.7382 1.735-2.5931 2.6178-0.73658 0.82062-1.6053 0.85394-2.5586 1.1927-0.94832 0.22808-1.8262 0.65178-2.7421 0.97327-1.1741 0.61913-2.4826 0.76669-3.7501 1.0714-1.2105 0.21338-2.3976 0.48893-3.5183 1.0015-0.67364 0.34917-1.2076 0.81882-1.6407 1.4361l3.8658 0.2497c0.43909-0.54046 0.98065-0.94428 1.6142-1.2389 1.1138-0.46371 2.2851-0.70648 3.4664-0.93518 1.2893-0.27722 2.5716-0.54674 3.7736-1.1162 0.91684-0.34231 1.829-0.69308 2.7728-0.9567 1.0196-0.30093 1.8099-0.54985 2.5486-1.3801 0.87014-0.87766 1.7909-1.7075 2.5674-2.6739 0.13769-0.20005 0.28985-0.39088 0.41308-0.60015 0.82211-1.3961 1.0263-3.0131 1.0537-4.6056 0.05629-2.6169-0.0783-5.2743-0.90909-7.7781-0.13345-0.36853-0.2428-0.74671-0.40035-1.1056-0.60136-1.3698-1.6464-2.4153-2.7131-3.4287-1.0905-1.0008-2.2045-1.983-3.6142-2.5099-0.24452-0.10125-0.48125-0.22386-0.73356-0.30376-0.26822-0.08493-0.54885-0.12408-0.82261-0.18901-1.4524-0.34445-2.9022-0.68462-4.3809-0.90277-1.1723-0.18185-2.3227-0.40058-3.4815-0.65277-0.54954-0.11959-1.1101-0.19272-1.6518-0.34365-0.20174-0.05621-0.39234-0.14664-0.58852-0.21996-0.15779-0.10023-0.32954-0.18128-0.47337-0.30068-0.94359-0.78332-1.2089-2.059-1.6142-3.1487-0.16754-0.59766-0.40066-1.3998-0.53448-2.0013-0.33082-1.487-0.45328-3.013-0.62991-4.523-0.03942-2.3761 0.26799-4.7319 1.1144-6.9675 0.36685-0.8728 0.62759-1.5813 1.1166-2.3961 0.98125-1.6348 2.2842-2.7836 3.9639-3.6713 0.51139-0.24392 1.0106-0.5151 1.5342-0.73175 3.1926-1.3212 6.6474-1.892 10.059-2.2825 3.192-0.33755 6.3934-0.58867 9.5696-1.062 2.856-0.4763 5.7214-0.9862 8.4967-1.8227 2.4241-0.91126 4.7643-2.0293 6.9388-3.445 1.5814-1.1701 3.0403-2.506 4.1596-4.1381 0.60624-1.0679 0.95747-1.9925 0.94037-3.2331h-3.9289z"/>
<path d="m12.023 67.543c0.49829 0.89935 0.97249 1.783 1.2618 2.7744 0.05193 0.50463 0.27477 0.97017 0.38441 1.4597 0.09562 0.42695 0.06722 0.87236 0.05564 1.3052-0.02684 0.65007-0.04547 1.3125-0.23635 1.9412-0.02066 0.06805-0.31569 0.75033-0.34246 0.81269-1.0192 2.7097-0.33194 1.7204 3.7574 1.4613 0.62653-0.65486 1.2245-1.3292 1.8921-1.9454 0.77212-0.76506 1.7378-1.0867 2.7527-1.378 1.0883-0.39202 2.2359-0.50662 3.3814-0.5819 0.64057-0.03615 1.2822-0.04273 1.9235-0.05158 0 0-3.7766-0.59155-3.7766-0.59155-0.64632 0.01315-1.2932 0.02268-1.9381 0.07203-1.1598 0.09954-2.3084 0.26952-3.4108 0.66115-1.0413 0.32357-1.9937 0.77464-2.7811 1.553-0.66771 0.6262-1.2491 1.3374-1.9404 1.9381 1.2373 0.03262 2.4793-0.01378 3.712 0.09787 0.08161 0.0074-0.02469 0.17515-0.08494 0.23068-0.03731 0.03439-0.0096-0.10235-1.85e-4 -0.15222 0.07511-0.39908 0.24315-0.78076 0.38022-1.1601 0.37962-0.92942 0.58939-1.8859 0.57777-2.896 0.01506-0.47523 0.04582-0.96424-0.0577-1.4334-0.10794-0.48914-0.37419-0.93937-0.40173-1.4497-0.15402-0.55207-0.32508-1.0754-0.57723-1.592-0.14541-0.29797-0.33208-0.5746-0.4773-0.87265-0.05226-0.10725-0.08657-0.22235-0.12986-0.33352l-3.9242 0.13069z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

5
verse/templates/forgotpassword.html → verse/statuspengguna/templates/statuspengguna/forgotpassword.html

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="mainworkflow"> <div id="mainworkflow">
<div class="workflow"> <div class="workflow">
@ -6,7 +6,8 @@
<p> <p>
Enter the email address that was used to register with Distribusiverse. Enter the email address that was used to register with Distribusiverse.
</p> </p>
<form class="form" action="{{ url_for('forgotpassword') }}" method="post"> <form class="form" action="{{ url_for('forgotpassword.forgotpassword') }}"
method="post">
{{ forgotpasswordform.csrf_token }} {{ forgotpasswordform.csrf_token }}
<fieldset class="required"> <fieldset class="required">
{{ forgotpasswordform.email.label }} {{ forgotpasswordform.email.label }}

6
verse/templates/login.html → verse/statuspengguna/templates/statuspengguna/login.html

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="login"> <div id="login">
<form class="form" action="{{ url_for('login') }}" method="post"> <form class="form" action="{{ url_for('login.login') }}" method="post">
{{ loginform.csrf_token }} {{ loginform.csrf_token }}
<fieldset class="required"> <fieldset class="required">
{{ loginform.email.label }} {{ loginform.email.label }}
@ -19,7 +19,7 @@
</fieldset> </fieldset>
<fieldset class="button required"> <fieldset class="button required">
{{ loginform.submit }} {{ loginform.submit }}
<a href="/forgotpassword">Forgot Password?</a> <a href="forgotpassword">Forgot Password?</a>
</fieldset> </fieldset>
</form> </form>
</div> </div>

4
verse/templates/register.html → verse/statuspengguna/templates/statuspengguna/register.html

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="login"> <div id="login">
<form class="form" action="{{ url_for('register') }}" method="post"> <form class="form" action="{{ url_for('register.register') }}" method="post">
{{ registerform.csrf_token }} {{ registerform.csrf_token }}
<fieldset class="required"> <fieldset class="required">
{{ registerform.username.label }} {{ registerform.username.label }}

2
verse/templates/resetpassword.html → verse/statuspengguna/templates/statuspengguna/resetpassword.html

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="login"> <div id="login">
{% if linkvalid%} {% if linkvalid%}

11
verse/templates/admin.html → verse/templates/base/admin.html

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="buttons"> <div id="buttons">
<div class="overview"> <div class="overview">
@ -14,9 +14,6 @@
</div> </div>
<div class="maincontent"> <div class="maincontent">
<h2>Admin Page</h2> <h2>Admin Page</h2>
<p>Here you can bulk delete distribusis and users or make users into tutors
<strong> These actions cannot be undone! </strong>
</p>
<div id="distribusiverse" class="maincontent"> <div id="distribusiverse" class="maincontent">
<h2>List of distribusis</h2> <h2>List of distribusis</h2>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('admin') }}"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('admin') }}">
@ -53,12 +50,6 @@
</fieldset> </fieldset>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<fieldset class="button required">
{{ adminuserform.tutors }}
</fieldset>
<fieldset class="button required">
{{ adminuserform.nottutors }}
</fieldset>
<fieldset class="button required"> <fieldset class="button required">
{{ adminuserform.delete }} {{ adminuserform.delete }}
</fieldset> </fieldset>

2
verse/templates/base.html → verse/templates/base/base.html

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Autonomous Practices X Distribusi-Verse</title> <title>{{title}}</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css')}}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css')}}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/selector.css')}}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/selector.css')}}">
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">

20
verse/templates/base/filtermenu.html

@ -0,0 +1,20 @@
{% block menu %}
<button onclick="filterSelection('all')" id="removefilter">Remove filter</button>
<div class="dropdown">
<button id="Year" class="dropbtn">Year</button>
<div class="dropdown-content">
{% for year in years %}
<button type="button" name="button" onclick="filterSelection('{{ year[0] }}', '{{ year[1] }}', 'Year')" >{{ year[1] }}</button>
{% endfor %}
</div>
</div>
<div class="dropdown">
<button id="Category" class="dropbtn">Category</button>
<div class="dropdown-content">
{% for category in categories %}
<button type="button" name="button" onclick="filterSelection('{{ category[0] }}', '{{ category[1] }}', 'Category')" >{{ category[1] }}</button>
{% endfor %}
</div>
</div>
<input id="tagsearch" type="text" placeholder="Search..">
{% endblock menu %}

2
verse/templates/help.html → verse/templates/base/help.html

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="buttons"> <div id="buttons">
<div class="overview"> <div class="overview">

11
verse/templates/index.html → verse/templates/base/index.html

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "base/base.html" %}
{% block main %} {% block main %}
<div id="buttons"> <div id="buttons">
@ -46,19 +46,14 @@
<div class="maincontent"> <div class="maincontent">
<p>Distribusi is a content management system for the web that produces static index pages based on folders in the files system. It is inspired by the automatic index functions featured in several popular web servers. Distribusi works by traversing the file system and directory hierarchy to automatically list all the files in the directory, detect the file types and providing them with relevant html classes and tags for easy styling. <p>Distribusi is a content management system for the web that produces static index pages based on folders in the files system. It is inspired by the automatic index functions featured in several popular web servers. Distribusi works by traversing the file system and directory hierarchy to automatically list all the files in the directory, detect the file types and providing them with relevant html classes and tags for easy styling.
</p> </p>
<hr>
<p>
This particular work in progress project <strong>Distribusi-verse</strong> is an attempt to make distribusi into a webinterface that can be operated remotely without any knowlegde of CLI. Attempting to combine the ideas of distribusi with the ideas of a <a href="https://tildeverse.org/">Tildeverse</a> and <a href="https://tilde.club/">Tilde club</a>, but also be neither of these ideas. See a full list of tildeverse members <a href="https://tildeverse.org/members/">here</a>.</p>
<hr>
<p>This project is made for <a href="https://www.wdka.nl/practices/autonomous-practices/">Autonomous Practices </a>at the <a href="https://www.wdka.nl/">WDKA</a> in Rotterdam.</p>
</div> </div>
<!-- a div with all the distribusis listed in the distribusiverse --> <!-- a div with all the distribusis listed in the distribusiverse -->
<div id="distribusiverse" class="maincontent"> <div id="distribusiverse" class="maincontent">
<h2>List of distribusis</h2> <h2>List of distribusis</h2>
{% include 'filtermenu.html' %} {% include 'base/filtermenu.html' %}
<ul id="distribusi-index"> <ul id="distribusi-index">
{% for name, distribusi in distribusisindex.items() %} {% for name, distribusi in distribusisindex.items() %}
<li class='distribusi filter {{ distribusi["term"] }} {{ distribusi["year"] }} {{ distribusi["course"] }}'> <li class='distribusi filter {{ distribusi["category"] }} {{ distribusi["year"] }} '>
<a href='stash/{{name}}/index.html'>{{distribusi["username"]}}:{{name}}</a> <a href='stash/{{name}}/index.html'>{{distribusi["username"]}}:{{name}}</a>
{% for tag in distribusi["tags"] %} {% for tag in distribusi["tags"] %}
<span class="tags">{{tag}}</span> <span class="tags">{{tag}}</span>

28
verse/templates/filtermenu.html

@ -1,28 +0,0 @@
{% block menu %}
<button onclick="filterSelection('all')" id="removefilter">Remove filter</button>
<div class="dropdown">
<button id="Academicyear" class="dropbtn">Academic year</button>
<div class="dropdown-content">
{% for year in years %}
<button type="button" name="button" onclick="filterSelection('{{ year[0] }}', '{{ year[1] }}', 'Academicyear')" >{{ year[1] }}</button>
{% endfor %}
</div>
</div>
<div class="dropdown">
<button id="Term" class="dropbtn">Term</button>
<div class="dropdown-content">
{% for term in terms %}
<button type="button" name="button" onclick="filterSelection('{{ term[0] }}', '{{ term[1] }}', 'Term')" >{{ term[1] }}</button>
{% endfor %}
</div>
</div>
<div class="dropdown">
<button id="Course" class="dropbtn">Course</button>
<div class="dropdown-content">
{% for course in courses %}
<button type="button" name="button" onclick="filterSelection('{{ course[0] }}' , '{{ course[1] }}', 'Course')" >{{ course[1] }}</button>
{% endfor %}
</div>
</div>
<input id="tagsearch" type="text" placeholder="Search..">
{% endblock menu %}
Loading…
Cancel
Save