Compare commits

62 Commits

Author SHA1 Message Date
cube
23b0819079 in front log 2026-05-01 19:59:00 +01:00
cube
9fcaf72457 images with same file name wont overwrite each other 2026-05-01 17:26:20 +01:00
cube
0df4bf926b remove potential html from title of member page 2026-05-01 17:09:05 +01:00
cube
fe26621221 remove potential html from title of member page 2026-05-01 17:08:18 +01:00
cube
b47fdac633 allow img tags to be used in member names 2026-05-01 17:03:23 +01:00
cube
b18c83a8ca image asset style update 2026-05-01 16:56:13 +01:00
cube
a21dc5f73f adjusted some styling 2026-05-01 16:11:39 +01:00
cube
0d4eec9c80 misc image uploads for use in custom sections, blog posts, or wherever you want!!!!! 2026-05-01 16:11:32 +01:00
cube
8db34a6d74 custom sections styling removal so custom style tags can be written 2026-05-01 01:47:28 +01:00
cube
a79dc7742b fix #5 2026-05-01 00:37:20 +01:00
cube
76b5ebb53d fix #54 2026-04-30 12:56:03 +01:00
cube
ab2d4b04ad forgot full list lol 2026-04-30 12:50:58 +01:00
cube
63895052f0 fix #45 2026-04-30 12:49:13 +01:00
cube
6fce468dc0 fix add to front location on member page 2026-04-30 12:29:18 +01:00
cube
7e6043891d fix #55 2026-04-30 12:22:32 +01:00
cube
c500214da0 maybe #72 2026-04-03 23:16:17 +01:00
cube
6ebc172aac rename sections 2026-04-03 22:49:23 +01:00
cube
2b8ce9c4c8 added section show fields and section title fields to export and import 2026-04-03 22:27:24 +01:00
cube
0fcb5e9b7b fix schema 2026-04-03 21:50:26 +01:00
cube
b40cb00d4d fix #15 REQUIRES DB INIT 2026-04-03 21:49:39 +01:00
cube
567f736d0d fix #68 2026-04-03 20:49:47 +01:00
cube
a2f47b4730 fix #70 2026-04-03 19:04:00 +01:00
cube
1df0feeea0 fix #69 2026-04-03 19:03:28 +01:00
cube
d76a02ed26 fix #53 2026-04-03 16:22:51 +01:00
cube
ab734d7ef1 no need to use br tags on blog posts 2026-04-03 01:34:35 +01:00
cube
8b78fa2480 remove need to manually use <br> tags in member bios 2026-04-02 15:27:19 +01:00
cube
fdcf2c3d96 fix #33 2026-04-02 15:26:30 +01:00
cube
86989a00fd readme 2026-03-31 23:20:50 +01:00
cube
19f6e3940e dark fix 2026-03-31 18:42:39 +01:00
cube
60a5d598fc dark theme update 2026-03-31 18:38:46 +01:00
cube
249ff7a5f3 fix #56 2026-03-31 17:01:07 +01:00
cube
931448bc05 scheme for sections and pages no need to init db because nothing is implemented 2026-03-31 16:54:54 +01:00
cube
03863b0250 readme 2026-03-31 14:54:17 +01:00
cube
f4fd5a6dac mobile nav plus additional nav options in admin panel for compactness 2026-03-31 14:36:52 +01:00
cube
17c3ca4a90 mobile view wip 2026-03-31 13:21:56 +01:00
cube
d0c38799e2 tweaks 2026-03-31 02:57:49 +01:00
cube
1d62847284 green 2026-03-31 02:38:39 +01:00
cube
d8708fe173 dark green 2026-03-31 02:26:12 +01:00
cube
5d8939bad3 reorder 2026-03-31 02:12:00 +01:00
cube
12c2a13c89 ugly christmas theme for certain specific individuals............... 2026-03-31 02:05:08 +01:00
cube
ca9d826386 reorder case insensitive 2026-03-31 01:58:59 +01:00
cube
6df8673ac1 reorder tweak 2026-03-31 01:51:59 +01:00
cube
4312e3e683 red 2026-03-31 01:50:00 +01:00
cube
174dfa3b52 rearrange 2026-03-31 01:47:37 +01:00
cube
e03ce3174a fix last updated 2026-03-31 01:43:20 +01:00
cube
8a6599354e green 2026-03-31 01:28:47 +01:00
cube
525d4ee431 green 2026-03-31 01:27:40 +01:00
cube
2c0f4e1fab blinkies size 2026-03-31 01:16:57 +01:00
cube
e55b0b7613 dark-blue theme 2026-03-31 01:10:45 +01:00
cube
f3124628cb readme and tweaks 2026-03-31 00:39:30 +01:00
cube
2de4f9af9c fixed latest issues with fresh database 2026-03-30 22:24:27 +01:00
cube
1ee6a96d58 front log and related datetime stuff 2026-03-30 22:20:16 +01:00
cube
db4a7cc46e tweaks 2026-03-30 16:48:04 +01:00
cube
9483193ebb full system zip export and import works. check readme 2026-03-30 16:18:29 +01:00
cube
f8efe51891 sqlite is just trying to embarrass me now 2026-03-30 15:15:24 +01:00
cube
e7cc0ed609 oops 2026-03-30 15:12:59 +01:00
cube
665c8e905d import and export entire system should fully work now but dont rely on anything here. keep your own personal backups elsewhere like a spreadsheet or something 2026-03-30 15:11:20 +01:00
cube
b907c1a546 working on imports and exports. everything in that regard is broken rn 2026-03-30 14:41:29 +01:00
cube
d20f558232 working on imports and exports. everything in that regard is broken rn 2026-03-30 14:40:02 +01:00
cube
96230b38ef working on imports and exports. everything in that regard is broken rn 2026-03-30 14:35:21 +01:00
cube
00171b10e5 working on imports and exports. everything in that regard is broken rn 2026-03-30 14:32:15 +01:00
cube
f7f3c6f100 working on imports and exports. everything in that regard is broken rn 2026-03-30 14:06:45 +01:00
31 changed files with 956 additions and 288 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.venv/
.vscode/
*.pyc
__pycache__/
@@ -16,3 +17,4 @@ build/
/myriad/static/blinkies
/myriad/static/stamps
myriad/static/tmp
myriad/static/misc

141
README.md
View File

@@ -1,6 +1,6 @@
# myriad
flask app for plurals to publicly share member lists
flask app for plural systems to publicly share member lists. the software is in basically a usable state right now, just be sure to keep regular backups. documentation is fairly limited but we're working on that. drop an email to `myriad [at] cubes [dot] link` if you wanna contact us about this project :)
logged in users are presumed to all be admins with distinction only between being logged in and not
@@ -10,140 +10,29 @@ the blinkies and stamps stuff is literally just because i want an easy way to up
make sure blinkies are actually blinkie-sized, and stamps are likewise stamp-sized. too much variation in size will make the layout weird. there's not validation because its designed just to make uploading the images not require ftp + manual code as i had been doing before.
# dev set up (windows)
# A note on data export/import
- after cloning, run `py -3 -m venv .venv` in the root directory and then `.venv\Scripts\activate`
- then `pip install Flask` inside the virtual env
- you might also need to init a database, so use `flask --app myriad init-db`
- to start the site use `flask --app myriad run --debug`
there are currently various ways to export and import data for use within myriad. individual member exports and imports deal only with the fields (though icons, stamps, and blinkies can be exported to a zip folder). they're designed to be used with an active database for whatever purpose the user requires.
do not deploy this way, the packaged flask server is not secure. production instructions will be provided when the project is ready
the other option is to export the entire system to json or zip file. **importing an entire system is best used on a completely empty database, as it deals with inserting id fields**. a full system import is useful for when an update is released that requires the database to be re-initialised, you can quickly restore information and images you had before.
- you will need to run `.venv\Scripts\activate` from the folder every time you start working on it
- re-building the entire database with `flask --app myriad init-db` (losing all the data inside) will be necessary as development continues. DO NOT STORE ANYTHING IMPORTANT DURING DEVELOPMENT
- start the site with `flask --app myriad run --debug` as usual
both are a little janky as they've just been implemented rather quickly to cover the base requirement of being able to migrate the database in some form. **don't rely on them, be sure to keep your own backups**.
# prod set up (linux)
**individual member import via zip folder is not currently possible**
it is recommended to run waitress in a tmux window for easier management
# Development set up (Windows)
create new tmux window
[Dev instructions are here](https://tea.cubes.link/cube/myriad/wiki/Development-setup-%28Windows%29)
`tmux new -s myriad`
# Deployment
detach from tmux window
[Deployment instructions are here](https://tea.cubes.link/cube/myriad/wiki/deployment-instructions)
`ctrl+b` -> `d`
# Usage
attach to existing tmux window
- The software here is free to use, and there's no requirement to link back
- Edit the styles and functionality to suit your needs. I'm sure some of you out there are far better with CSS than us
`tmux attach -t myriad`
# Preview
## installing
clone the repo and set up virtual env
`git clone https://tea.cubes.link/cube/myriad.git`
`cd myriad`
`python3 -m venv venv`
`source venv/bin/activate`
install necessary packages in the virtual env
`pip install flask`
`pip install waitress`
make sure all directories exist
`mkdir instance`
`mkdir myriad/static/icons`
`mkdir myriad/static/blinkies`
`mkdir myriad/static/stamps`
`mkdir myriad/static/tmp`
copy sample from readme and paste into the following
`sudo nano instance/config.py`
in a window where you can copy the output, (or in python itself) generate a secret key and copy it to the clipboard
`python -c 'import secrets; print(secrets.token_hex())'`
add a new line to config.py and paste in the key
`SECRET_KEY = '(paste generated key here)'`
initialize the database
`flask --app myriad init-db`
start running the site
`waitress-serve --port=80 --call 'myriad:create_app'`
after running and setting up first user stop waitress (CTRL+C), then edit the config to disable user registration for security
`sudo nano instance/config.py`
`REGISTRATION = False`
once you have disabled registration you will need to go to /auth/login in order to log in. removing the link in the sidebar just (slightly) obfuscates the possibilty from passing users
## updating
stop waitress (CTRL+C) and use `git pull` to pull changes
start waitress again to reload
if it is a database changing update, use backup features, re-init db, then re-import from backup
# config
- create `config.py` in the instance folder and customise the following settings to your needs
```
REGISTRATION = True # Make sure to disable in production
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} # Can be anything you want
ICON_UPLOAD_FOLDER = 'myriad/static/icons' # where member icons will be stored
BLINKIES_UPLOAD_FOLDER = 'myriad/static/blinkies' # where site assets "blinkies" will be stored
STAMPS_UPLOAD_FOLDER = 'myriad/static/stamps' # where site assets "stamps" will be stored
THEMES_FOLDER = 'myriad/static/themes' # where theme choices will be stored
TMP_FOLDER = 'myriad/static/tmp' # folder for creating export files
```
# usage
- the software here is free to use, and there's no requirement to link back
- edit the styles and functionality to suit your needs. i'm sure some of you out there are far better with CSS than I am
# dependencies
- Flask
# preview
- these screenshots are all from the public viewer's perspective
- homepage with pinned members
![homepage with pinned members](https://i.ibb.co/mF52TSnM/Screenshot-2026-03-19-025454.png)
- main blog post feed
![main blog post feed](https://i.ibb.co/tj5WCsx/Screenshot-2026-03-19-025836.png)
- member page showing their uploaded icons (just about)
![member page](https://i.ibb.co/8LJBwVf2/Screenshot-2026-03-19-030004.png)
- groups view
![groups view](https://i.ibb.co/mF9JFR7x/Screenshot-2026-03-19-030108.png)
- a sample part of the administration
![sample administration area](https://i.ibb.co/pr6qFywy/Screenshot-2026-03-19-030406.png)
[Check out our own personal instance](https://system.cubes.link)

View File

@@ -1,9 +1,11 @@
import os
import os, datetime
from flask import Flask
from myriad.utilities import server_time, get_datetime_str, remove_html
from myriad.db import get_db
def create_app(test_config=None):
def create_app():
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_pyfile('config.py')
@@ -33,6 +35,16 @@ def create_app(test_config=None):
def get_themes():
themes = os.listdir(app.config["THEMES_FOLDER"])
return themes
return dict(get_themes=get_themes)
def w_server_time():
return server_time()
def w_get_datetime_str(dt_obj):
return get_datetime_str(dt_obj)
def get_member(mid):
db = get_db()
member = db.execute("SELECT * FROM member WHERE id=(?)",(mid,)).fetchone()
return member
return dict(get_themes=get_themes, server_time=w_server_time, get_datetime_str=w_get_datetime_str, get_member=get_member, remove_html=remove_html)
return app

View File

@@ -14,9 +14,7 @@ def login_required(view):
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for('auth.login'))
return view(**kwargs)
return wrapped_view
@@ -27,9 +25,7 @@ def load_logged_in_user():
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
'SELECT * FROM user WHERE id = ?', (user_id,)
).fetchone()
g.user = get_db().execute('SELECT * FROM user WHERE id = ?', (user_id,)).fetchone()
@@ -50,10 +46,7 @@ def register():
if error is None:
try:
db.execute(
"INSERT INTO user (username, password) VALUES (?, ?)",
(username, generate_password_hash(password)),
)
db.execute("INSERT INTO user (username, password) VALUES (?, ?)",(username, generate_password_hash(password)),)
db.commit()
except db.IntegrityError:
error = f"User {username} is already registered."
@@ -71,9 +64,7 @@ def login():
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM user WHERE username = ?', (username,)
).fetchone()
user = db.execute('SELECT * FROM user WHERE username = ?', (username,)).fetchone()
if user is None:
error = 'Incorrect username.'

View File

@@ -3,6 +3,7 @@ from flask import (
)
from werkzeug.utils import secure_filename
import os, uuid
from myriad.utilities import server_time, get_datetime_obj, get_datetime_str
from myriad.auth import login_required
from myriad.db import get_db
@@ -29,15 +30,16 @@ def blog():
@login_required
def new():
db = get_db()
members = db.execute("SELECT id,member_name FROM member").fetchall()
members = db.execute("SELECT id,member_name FROM member ORDER BY member_name COLLATE NOCASE").fetchall()
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
mid = request.form["mid"]
privacy = request.form["privacy"]
created = get_datetime_obj(server_time())
db.execute("INSERT INTO blog (member_id, title, content, public) VALUES (?, ?, ?, ?)",(mid, title, content, privacy))
db.execute("INSERT INTO blog (member_id, title, content, public, created) VALUES (?, ?, ?, ?, ?)",(mid, title, content, privacy, created))
db.commit()
return redirect(url_for('blog.blog'))

View File

@@ -12,7 +12,24 @@ bp = Blueprint('home', __name__)
def index():
db = get_db()
fronters = db.execute("SELECT * FROM member WHERE front=(?) ORDER BY member_name",(1,)).fetchall()
homepage = db.execute("SELECT * FROM member WHERE homepage=(?) ORDER BY member_name",(1,)).fetchall()
homepage = db.execute("SELECT * FROM member WHERE homepage=(?) ORDER BY member_name COLLATE NOCASE",(1,)).fetchall()
latest_start = db.execute("SELECT start_time FROM front_log ORDER BY start_time DESC").fetchone()
latest_end = db.execute("SELECT end_time FROM front_log ORDER BY end_time DESC").fetchone()
latest = None
if latest_start and latest_end:
start = latest_start[0]
end = latest_end[0]
if end:
if start > end:
latest = start
else:
latest = end
else:
latest = start
elif latest_start and not latest_end:
latest = latest_start[0]
icons={}
for member in homepage:
@@ -24,12 +41,12 @@ def index():
else:
icons[member[0]] = None
return render_template('index.html', front_list=fronters, home_pins=homepage, icons=icons)
return render_template('index.html', front_list=fronters, home_pins=homepage, icons=icons, last_updated=latest)
@bp.route('/full')
def full_list():
db = get_db()
members = db.execute('SELECT * FROM member ORDER BY member_name').fetchall()
members = db.execute('SELECT * FROM member ORDER BY member_name COLLATE NOCASE').fetchall()
icons={}
for member in members:
@@ -52,8 +69,14 @@ def page(mid):
all_icons = db.execute("SELECT icon_location FROM icons WHERE member_id=(?)",(mid,)).fetchall()
blinkies = db.execute("SELECT blinkie_location FROM blinkies WHERE member_id=(?)",(mid,)).fetchall()
stamps = db.execute("SELECT stamp_location FROM stamps WHERE member_id=(?)",(mid,)).fetchall()
blog_public = db.execute("SELECT * FROM blog WHERE member_id=(?) AND public=(?)",(mid,1)).fetchall()
sections = db.execute("SELECT * FROM sections WHERE member_id=(?) ORDER BY position ASC",(mid,)).fetchall()
return render_template('page.html', member=member, blog=blog, icon=icon, all_icons=all_icons, blinkies=blinkies, stamps=stamps)
blog_public_show = False
if len(blog_public) > 0:
blog_public_show = True
return render_template('page.html', member=member, blog=blog, icon=icon, all_icons=all_icons, blinkies=blinkies, stamps=stamps, blog_public_show=blog_public_show, sections=sections)
@bp.route("/groups")
def groups():

View File

@@ -2,9 +2,11 @@ from flask import Blueprint, flash, g, redirect, render_template, request, sessi
from werkzeug.utils import secure_filename
import os, uuid, json, zipfile, datetime
from myriad.utilities import server_time, get_datetime_obj, server_time_obj
from myriad.auth import login_required
from myriad.db import get_db
from myriad.utilities import get_datetime_str
bp = Blueprint('manage', __name__, url_prefix='/manage')
@@ -16,11 +18,18 @@ def new():
subtitle = request.form["subtitle"]
bio = request.form['bio']
user_id = g.user[0]
date_created = get_datetime_obj(server_time())
privacy = request.form["privacy"]
db = get_db()
db.execute("INSERT INTO member (user_id, member_name, bio, subtitle) VALUES (?, ?, ?, ?)",(user_id, name, bio, subtitle))
db.execute("INSERT INTO member (user_id, member_name, bio, subtitle, created, public) VALUES (?, ?, ?, ?, ?, ?)",(user_id, name, bio, subtitle, date_created, privacy))
db.commit()
return redirect(url_for('home.full_list'))
last = db.execute('SELECT last_insert_rowid()').fetchone()
new_mid = last[0]
return redirect(url_for('manage.edit', mid=new_mid))
return render_template('manage/new.html')
@@ -29,7 +38,6 @@ def new():
def import_member_route():
db = get_db()
response=""
data=None
mid=None
if request.method=="POST":
@@ -72,7 +80,31 @@ def edit(mid):
db.execute("UPDATE member SET member_name=(?), bio=(?), subtitle=(?), public=(?), theme=(?) WHERE id=(?)",(name, bio, subtitle, privacy, theme, mid))
db.commit()
edit_location="details"
edit_location = "details"
if "page_settings" in request.form:
show_groups = "show_groups" in request.form
show_blog = "show_blog" in request.form
show_icons = "show_icons" in request.form
show_blinkies = "show_blinkies" in request.form
show_stamps = "show_stamps" in request.form
db.execute("UPDATE member SET show_groups=(?), show_blog=(?), show_icons=(?), show_blinkies=(?), show_stamps=(?) WHERE id=(?)",(show_groups, show_blog, show_icons, show_blinkies, show_stamps, mid))
db.commit()
edit_location = "page_settings"
if "section_titles" in request.form:
groups_title = request.form["groups_title"]
blog_title = request.form["blog_title"]
icons_title = request.form["icons_title"]
blinkies_title = request.form["blinkies_title"]
stamps_title = request.form["stamps_title"]
db.execute("UPDATE member SET groups_title=(?), blog_title=(?), icons_title=(?), blinkies_title=(?), stamps_title=(?) WHERE id=(?)",(groups_title, blog_title, icons_title, blinkies_title, stamps_title, mid))
db.commit()
edit_location = "page_settings"
if "file" in request.files:
# here we are just saving the uploaded file to the icons folder.
@@ -84,21 +116,21 @@ def edit(mid):
db.execute("INSERT INTO icons (member_id, icon_location) VALUES (?, ?)", (mid, filename))
db.commit()
edit_location="icons"
edit_location = "icons"
if "gid_add" in request.form:
gid = request.form["gid_add"]
db.execute("INSERT INTO group_members (group_id,member_id) VALUES (?,?)",(gid,mid))
db.commit()
edit_location="groups"
edit_location = "groups"
elif "gid_remove" in request.form:
gid = request.form["gid_remove"]
db.execute("DELETE FROM group_members WHERE group_id=(?) AND member_id=(?)",(gid,mid))
db.commit()
edit_location="groups"
edit_location = "groups"
if "blinkie" in request.files:
file = request.files["blinkie"]
@@ -107,7 +139,7 @@ def edit(mid):
db.execute("INSERT INTO blinkies (member_id, blinkie_location) VALUES (?, ?)", (mid, filename))
db.commit()
edit_location="blinkies"
edit_location = "blinkies"
if "stamp" in request.files:
file = request.files["stamp"]
@@ -116,13 +148,34 @@ def edit(mid):
db.execute("INSERT INTO stamps (member_id, stamp_location) VALUES (?, ?)", (mid, filename))
db.commit()
edit_location="stamps"
edit_location = "stamps"
if "new_section" in request.form:
section_title = request.form["section_title"]
section_content = request.form["section_content"]
db.execute("INSERT INTO sections (member_id, title, content) VALUES (?, ?, ?)", (mid, section_title, section_content))
db.commit()
edit_location = "sections"
if "update_section" in request.form:
section_id = request.form["section_id"]
section_title = request.form["section_title"]
section_content = request.form["section_content"]
section_position = request.form["section_pos"]
db.execute("UPDATE sections SET title=(?), content=(?), position=(?) WHERE id=(?)",(section_title, section_content, section_position, section_id))
db.commit()
edit_location = "sections"
member = db.execute("SELECT * FROM member WHERE id=(?)",(mid,)).fetchone()
icons = db.execute("SELECT * FROM icons WHERE member_id=(?)",(mid,)).fetchall()
blinkies = db.execute("SELECT * FROM blinkies WHERE member_id=(?)",(mid,)).fetchall()
stamps = db.execute("SELECT * FROM stamps WHERE member_id=(?)",(mid,)).fetchall()
sections = db.execute("SELECT * FROM sections WHERE member_id=(?)",(mid,)).fetchall()
groups = db.execute("SELECT * FROM groups").fetchall()
member_groups = db.execute("SELECT * FROM group_members WHERE member_id=(?)",(mid,)).fetchall()
@@ -142,7 +195,18 @@ def edit(mid):
themes = os.listdir(current_app.config["THEMES_FOLDER"])
return render_template("manage/edit.html", member=member, icons=icons, unjoined_groups=unjoined_groups, joined_groups=joined_groups, themes=themes, edit_location=edit_location, blinkies=blinkies, stamps=stamps)
return render_template("manage/edit.html", member=member, icons=icons, unjoined_groups=unjoined_groups, joined_groups=joined_groups, themes=themes, edit_location=edit_location, blinkies=blinkies, stamps=stamps, sections=sections)
@bp.route("/delete_section/<sid>")
@login_required
def delete_section(sid):
db = get_db()
mid = db.execute("SELECT member_id FROM sections WHERE id=(?)",(sid,)).fetchone()[0]
db.execute("DELETE FROM sections WHERE id=(?)",(sid,))
db.commit()
return redirect(url_for("manage.edit", mid=mid))
@bp.route("/set_main_icon/<mid>/<icon_id>")
@login_required
@@ -194,8 +258,13 @@ def add_to_front(mid,location):
db.execute("UPDATE member SET front=(?) WHERE id=(?)",(1, mid))
db.commit()
db.execute("INSERT INTO front_log (member_id, start_time) VALUES (?, ?)",(mid, server_time_obj()))
db.commit()
if location == "home":
return redirect(url_for('index'))
elif location == "mid":
return redirect(url_for('home.page', mid=mid))
else:
return redirect(url_for('home.full_list'))
@@ -206,11 +275,27 @@ def remove_front(mid, location):
db.execute("UPDATE member SET front=(?) WHERE id=(?)",(0, mid))
db.commit()
current_entry = db.execute("SELECT id FROM front_log WHERE member_id=(?) AND end_time IS NULL",(mid,)).fetchone()
current_entry_id = current_entry[0]
db.execute("UPDATE front_log SET end_time=(?) WHERE id=(?)",(server_time_obj(), current_entry_id))
db.commit()
if location == "home":
return redirect(url_for('index'))
elif location == "mid":
return redirect(url_for('home.page', mid=mid))
else:
return redirect(url_for('home.full_list'))
@bp.route("/delete_front_log/<fid>")
@login_required
def delete_front_log(fid):
db = get_db()
db.execute("DELETE FROM front_log WHERE id=(?)",(fid,))
db.commit()
return redirect(url_for('manage.admin'))
@bp.route("/add_to_home/<mid>/<location>")
@login_required
@@ -221,6 +306,8 @@ def add_to_home(mid, location):
if location == "home":
return redirect(url_for('index'))
elif location == "mid":
return redirect(url_for('home.page', mid=mid))
else:
return redirect(url_for('home.full_list'))
@@ -233,6 +320,8 @@ def remove_home(mid,location):
if location == "home":
return redirect(url_for('index'))
elif location == "mid":
return redirect(url_for('home.page', mid=mid))
else:
return redirect(url_for('home.full_list'))
@@ -255,12 +344,9 @@ def import_groups(groups):
def import_member(member):
db = get_db()
date_raw = member["date-created"].split(",")
date = date_raw[0]
day,month,year = date.split("/")
time = date_raw[1]
hour,minute,second = time.split(":")
date_created = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))
mid = member["id"]
date_created_obj = get_datetime_obj(member["date-created"])
name = member["name"]
subtitle = member["subtitle"]
@@ -272,9 +358,65 @@ def import_member(member):
privacy = 0
theme = member["theme"]
homepage = member["homepage-pin"]
user_id = 0
main_icon_id = ""
groups = member["groups"]
for group in groups:
db.execute("INSERT INTO group_members (group_id, member_id) VALUES (?, ?)",(group, mid))
db.commit()
blog = member["blog"]
for post in blog:
post_date_created = get_datetime_obj(post["date-created"])
title = post["title"]
content = post["content"]
if post["privacy"] == "public":
privacy = 1
else:
privacy = 0
db.execute("INSERT INTO blog (member_id, created, title, content, public) VALUES (?, ?, ?, ?, ?)",(mid, post_date_created, title, content, privacy))
db.commit()
icons = member["icons"]
for icon in icons:
db.execute("INSERT INTO icons (member_id, icon_location) VALUES (?, ?)",(mid, icon))
db.commit()
if icon == member["main-icon"]:
last = db.execute('SELECT last_insert_rowid()').fetchone()
main_icon_id = last[0]
blinkies = member["blinkies"]
for blinkie in blinkies:
db.execute("INSERT INTO blinkies (member_id, blinkie_location) VALUES (?, ?)",(mid, blinkie))
db.commit()
stamps = member["stamps"]
for stamp in stamps:
db.execute("INSERT INTO stamps (member_id, stamp_location) VALUES (?, ?)",(mid, stamp))
db.commit()
show_blog = member["show-blog"]
show_icons = member["show-icons"]
show_blinkies = member["show-blinkies"]
show_stamps = member["show-stamps"]
show_groups = member["show-groups"]
blog_title = member["blog-title"]
icons_title = member["icons-title"]
blinkies_title = member["blinkies-title"]
stamps_title = member["stamps-title"]
groups_title = member["groups-title"]
db.execute("INSERT INTO member (id,created,user_id, member_name,subtitle, bio,public,theme,homepage,main_icon,show_blog,show_icons,show_blinkies,show_stamps,show_groups,blog_title,icons_title,blinkies_title,stamps_title,groups_title) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(mid, date_created_obj, user_id, name, subtitle, description,privacy, theme, homepage, main_icon_id, show_blog, show_icons, show_blinkies, show_stamps, show_groups, blog_title, icons_title, blinkies_title, stamps_title, groups_title))
db.commit()
#db.execute("INSERT INTO member (created,user_id, member_name, bio,public) VALUES (?, ?, ?, ?, ?)",(data[0], data[1], data[2], data[3], data[4]))
#db.commit()
@bp.route("/admin", methods=("GET", "POST"))
@@ -295,15 +437,35 @@ def admin():
for member in members:
import_member(member)
return "<a href='/'>go home</a>"
elif "zip" in request.files:
return "upload zip"
file = request.files["zip"]
with zipfile.ZipFile(file, "r") as zipf:
for f in zipf.namelist():
dirs = f.split("/")
content_type = dirs[2]
filename = dirs[3]
if content_type == "tmp":
system_content = zipf.read(f)
system_json = json.loads(system_content.decode())
groups = system_json["groups"]
import_groups(groups)
for m in system_json["members"]:
import_member(m)
else:
zipf.extract(f)
return "<a href='/'>go home</a>"
users = db.execute("SELECT * FROM user").fetchall()
front_log = db.execute("SELECT * FROM front_log ORDER BY start_time DESC").fetchall()
return render_template("manage/admin.html", users=users)
return render_template("manage/admin.html", users=users, front_log=front_log)
@@ -318,20 +480,25 @@ def generate_json(mid):
member = db.execute("SELECT * FROM member WHERE id=(?)",(mid,)).fetchone()
groups_r = db.execute("SELECT group_id FROM group_members WHERE member_id=(?)",(mid,)).fetchall()
blog_r = db.execute("SELECT * FROM blog WHERE member_id=(?)",(mid,)).fetchall()
icons_r = db.execute("SELECT icon_location FROM icons WHERE member_id=(?)",(mid,)).fetchall()
blinkies_r = db.execute("SELECT blinkie_location FROM blinkies WHERE member_id=(?)",(mid,)).fetchall()
stamps_r = db.execute("SELECT stamp_location FROM stamps WHERE member_id=(?)",(mid,)).fetchall()
if member[9] == 1:
privacy = "public"
member_privacy = "public"
else:
privacy = "private"
member_privacy = "private"
date_created = member[2].strftime("%d/%m/%Y, %H:%M:%S")
date_created = get_datetime_str(member[2])
homepage = member[7]
groups = []
for group in groups_r:
groups.append(group[0])
blog = []
for post in blog_r:
d_c = post[2].strftime("%d/%m/%Y, %H:%M:%S")
d_c = get_datetime_str(post[2])
if post[5] == 1:
priv = "public"
@@ -350,6 +517,21 @@ def generate_json(mid):
blog.append(p)
icons = []
for icon in icons_r:
icons.append(icon[0])
main_icon_id = member[6]
main_icon = db.execute("SELECT icon_location FROM icons WHERE id=(?)",(main_icon_id,)).fetchone()
blinkies = []
for blinkie in blinkies_r:
blinkies.append(blinkie[0])
stamps = []
for stamp in stamps_r:
stamps.append(stamp[0])
data = {
"id":mid,
@@ -358,11 +540,29 @@ def generate_json(mid):
"name":member[3],
"subtitle":member[4],
"description":member[5],
"privacy":privacy,
"privacy":member_privacy,
"theme":member[10],
"groups":groups,
"blog":blog
"blog":blog,
"icons":icons,
"blinkies":blinkies,
"stamps":stamps,
"homepage-pin":homepage,
"show-blog":member[11],
"show-icons":member[12],
"show-blinkies":member[13],
"show-stamps":member[14],
"show-groups":member[15],
"blog-title":member[16],
"icons-title":member[17],
"blinkies-title":member[18],
"stamps-title":member[19],
"groups-title":member[20]
}
if main_icon:
data["main-icon"] = main_icon[0]
else:
data["main-icon"] = ""
return data
@@ -571,6 +771,22 @@ def assets():
filename = file.filename
file.save(os.path.join(current_app.config["STAMPS_UPLOAD_FOLDER"], filename))
if "image" in request.files:
file = request.files["image"]
filename = file.filename
fname, ftype = filename.split(".")
i = 0
if os.path.exists(os.path.join(current_app.config["MISC_UPLOAD_FOLDER"], filename)):
i = 2
while os.path.exists(os.path.join(current_app.config["MISC_UPLOAD_FOLDER"], fname+str(i)+"."+ftype)):
i += 1
if i == 0:
file.save(os.path.join(current_app.config["MISC_UPLOAD_FOLDER"], filename))
else:
file.save(os.path.join(current_app.config["MISC_UPLOAD_FOLDER"], fname+str(i)+"."+ftype))
icons = db.execute("SELECT * FROM icons").fetchall()
icon_storage = os.listdir(current_app.config["ICON_UPLOAD_FOLDER"])
@@ -593,8 +809,9 @@ def assets():
blinkies = os.listdir(current_app.config["BLINKIES_UPLOAD_FOLDER"])
stamps = os.listdir(current_app.config["STAMPS_UPLOAD_FOLDER"])
images = os.listdir(current_app.config["MISC_UPLOAD_FOLDER"])
return render_template("manage/assets.html", icons=unlinked_icons, icon_storage=i_storage, blinkies=blinkies, stamps=stamps)
return render_template("manage/assets.html", icons=unlinked_icons, icon_storage=i_storage, blinkies=blinkies, stamps=stamps, images=images)
@bp.route("/delete_idb")
@login_required

View File

@@ -6,7 +6,9 @@ DROP TABLE IF EXISTS group_members;
DROP TABLE IF EXISTS blog;
DROP TABLE IF EXISTS blinkies;
DROP TABLE IF EXISTS stamps;
DROP TABLE IF EXISTS front_log;
DROP TABLE IF EXISTS sections;
DROP TABLE IF EXISTS pages;
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -17,7 +19,7 @@ CREATE TABLE user (
CREATE TABLE member (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created TIMESTAMP,
member_name TEXT NOT NULL,
subtitle TEXT DEFAULT "",
bio TEXT,
@@ -26,6 +28,16 @@ CREATE TABLE member (
front BOOLEAN NOT NULL DEFAULT 0,
public BOOLEAN NOT NULL DEFAULT 1,
theme TEXT NOT NULL DEFAULT 'default',
show_blog BOOLEAN NOT NULL DEFAULT 1,
show_icons BOOLEAN NOT NULL DEFAULT 1,
show_blinkies BOOLEAN NOT NULL DEFAULT 1,
show_stamps BOOLEAN NOT NULL DEFAULT 1,
show_groups BOOLEAN NOT NULL DEFAULT 1,
blog_title TEXT DEFAULT "Blog",
icons_title TEXT DEFAULT "Icons",
blinkies_title TEXT DEFAULT "Blinkies",
stamps_title TEXT DEFAULT "Stamps",
groups_title TEXT DEFAULT "Groups",
FOREIGN KEY (user_id) REFERENCES user (id),
FOREIGN KEY (main_icon) REFERENCES icons (id)
);
@@ -55,7 +67,7 @@ CREATE TABLE group_members (
CREATE TABLE blog (
id INTEGER PRIMARY KEY AUTOINCREMENT,
member_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created TIMESTAMP,
title TEXT,
content TEXT,
public BOOLEAN NOT NULL DEFAULT 1,
@@ -76,10 +88,26 @@ CREATE TABLE stamps (
FOREIGN KEY (member_id) REFERENCES member (id)
);
CREATE TABLE front_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
member_id INTEGER NOT NULL,
start_time TIMESTAMP,
end_time TIMESTAMP,
FOREIGN KEY (member_id) REFERENCES member (id)
);
CREATE TABLE sections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
member_id INTEGER NOT NULL,
title TEXT,
content TEXT,
position INTEGER,
FOREIGN KEY (member_id) REFERENCES member (id)
);
CREATE TABLE pages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
content TEXT,
position INTEGER
);

BIN
myriad/static/lock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -42,35 +42,6 @@ form textarea{
#main{
background-color: rgb(255, 255, 255, 0);
border-radius: 5px;
width: 60%;
margin:auto;
padding:20px;
display:flex;
margin-top:20px;
}
#nav{
margin-right:20px;
flex:20%;
height:fit-content;
position:sticky;
position: -webkit-sticky;
}
.navitem{
display:block;
}
.container{
background-color:#e6f7ff;
border-color:#99dfff;
border-style:solid;
border-width:2px;
flex: 80%;
padding: 10px;
border-radius:5px;
}
.profile{
margin:15px;
@@ -124,6 +95,33 @@ form textarea{
height:100px;
float:left;
}
.manage_images_3{
display:block;
width:180px;
height:100px;
float:left;
}
.blinkie
{
height:20px;
width:auto;
}
.stamp
{
height:56px;
width:auto;
}
.mng_img
{
height:56px;
width:auto;
object-fit:scale-down;
max-width:180px;
overflow:hidden;
}
@@ -164,9 +162,9 @@ form textarea{
.title{
font-size:20px;
display:block;
margin-top:20px;
margin-bottom:20px;
display:block;
}
.heading.big{
font-size:20px;
@@ -182,6 +180,17 @@ form textarea{
display:block;
}
.lock{
float:left;
margin-right:10px;
}
.minilock{
width:16px;
height:auto;
float:left;
margin-right:10px;
}
#blog{
max-height:300px;
overflow-y:scroll;
@@ -225,16 +234,96 @@ form textarea{
font-size:10px;
}
.log{
max-height:300px;
overflow-y:scroll;
}
.image-sections{
margin-top:20px;
margin-bottom:20px;
}
#mobile-nav{
display:none;
background-color:#e6f7ff;
border-color:#99dfff;
border-style:solid;
border-width:2px;
border-radius:5px;
}
#main{
background-color: rgb(255, 255, 255, 0);
border-radius: 5px;
width: 60%;
margin:auto;
padding:20px;
display:flex;
margin-top:20px;
}
#nav{
margin-left:-20px;
margin-right:20px;
flex:25%;
height:fit-content;
position:sticky;
position: -webkit-sticky;
}
.navitem{
display:block;
}
.container{
background-color:#e6f7ff;
border-color:#99dfff;
border-style:solid;
border-width:2px;
border-radius:5px;
flex: 80%;
padding: 10px;
}
@media (max-width: 1000px) {
.mobile{
display:none;
}
@media (max-width: 1000px)
{
#main{
width:90%;
width:100%;
margin-left:-20px;
}
.profile{
width:100%;
margin:auto;
margin-top:20px;
}
#nav{
display:none;
}
.mobile{
display:inline;
}
#mobile-nav
{
display:inline-block;
top:0;
position:absolute;
padding:10px;
margin-left:20px;
margin-top:10px;
}
.container{
margin-top:20px;
margin-left:auto;
margin-right:auto;
width:300px;
}
.icon{
width:80px;
height:80px;
}
.manage_icons_3, .manage_blinkies_3, .manage_stamps_3{
clear:both;
}
}

View File

@@ -0,0 +1,43 @@
.christmas{
background-color:#fac9c9;
border-color:#da1414;
color:black;
}
.christmas .heading
{
background-color:#c4efd2;
border-color:#4e9e81;
color:black;
}
.christmas .heading b
{
color:black;
}
.christmas a{
color: #329d76;
}
.christmas a:hover{
color: #22694f;
}
.christmas ::selection {
background:#da1414;
color: white;
text-shadow: none;
}
.christmas ::-moz-selection {
background: #da1414;
color: #EEE;
text-shadow: none;
}
.christmas .icon{
border-color:#da1414;
}
body.christmas{
background: linear-gradient(90deg, #5abf72 0%, #da1414 50%, #bf8282 100%);
scrollbar-color:#da1414 #f49191;
}
.christmas .container, .christmas #mobile-nav{
background-color:#fac9c9;
border-color:#da1414;
}

View File

@@ -0,0 +1,43 @@
.dark-blue{
background-color:#c9cbfa;
border-color:#1438da;
color:black;
}
.dark-blue .heading
{
background-color:#9691f4;
border-color:#142eda;
color:black;
}
.dark-blue .heading b
{
color:black;
}
.dark-blue a{
color: #141bda;
}
.dark-blue a:hover{
color: #0e1798;
}
.dark-blue ::selection {
background:#144fda;
color: white;
text-shadow: none;
}
.dark-blue ::-moz-selection {
background: #144fda;
color: #EEE;
text-shadow: none;
}
.dark-blue .icon{
border-color:#1438da;
}
body.dark-blue{
background: linear-gradient(90deg, #4685fb 0%, #142bda 50%, #0e1598 100%);
scrollbar-color:#a414da #d991f4;
}
.dark-blue .container, .dark-blue #mobile-nav{
background-color:#c9cffa;
border-color:#142bda;
}

View File

@@ -0,0 +1,43 @@
.dark-green{
background-color:#92dea3;
border-color:rgb(60, 138, 105);
color:black;
}
.dark-green .heading
{
background-color:#75d78b;
border-color:#4c9576;
color:black;
}
.dark-green .heading b
{
color:black;
}
.dark-green a{
color: #379162;
}
.dark-green a:hover{
color: #19533b;
}
.dark-green ::selection {
background:#44b889;
color: white;
text-shadow: none;
}
.dark-green ::-moz-selection {
background: #44b889;
color: #EEE;
text-shadow: none;
}
.dark-green .icon{
border-color:#0e985e;
}
body.dark-green{
background: linear-gradient(90deg, #6dad9e 0%, #41da86 50%, #36b78c 100%);
scrollbar-color:#5ed3a2 #a8d0c5;
}
.dark-green .container, .dark-green #mobile-nav{
background-color:#c9fae8;
border-color:#59d7af;
}

View File

@@ -1,5 +1,5 @@
.dark{
background-color:#1B1F23;
background-color:#2c3339;
border-color:#000000;
color:#D0D5DA;
}
@@ -8,7 +8,7 @@
}
.dark .heading
{
background-color:#292F35;
background-color:#1B1F23;
border-color:#000000;
color:#D0D5DA;
}
@@ -23,12 +23,12 @@
color: #D0D5DA;
}
.dark ::selection {
background:#D0D5DA;
background:#1B1F23;
color: white;
text-shadow: none;
}
.dark ::-moz-selection {
background: #D0D5DA;
background: #1B1F23;
color: #EEE;
text-shadow: none;
}
@@ -40,7 +40,15 @@ body.dark{
background: linear-gradient(90deg, #1B1F23 0%, #1B1F23 50%, #1B1F23 100%);
scrollbar-color:#292F35 #373b3e;
}
.dark .container{
background-color:#1B1F23;
.dark .container, .dark #mobile-nav{
background-color:#2c3339;
border-color:black;
}
.dark .post{
border-color:black;
background-color:#1B1F23 ;
color:white;
}
.dark #blog .timestamp{
color:#959595;
}

View File

@@ -38,3 +38,7 @@
background: linear-gradient(90deg, #00b7ff 0%, #57c785 50%, #eddd53 100%);
scrollbar-color:#008bcc #b3e7ff;
}
.default .container, .default #mobile-nav{
background-color:#e6f7ff;
border-color:#99dfff;
}

View File

@@ -0,0 +1,43 @@
.green{
background-color:#c9facf;
border-color:#14da84;
color:black;
}
.green .heading
{
background-color:#91f4b7;
border-color:#14da88;
color:black;
}
.green .heading b
{
color:black;
}
.green a{
color: #11c668;
}
.green a:hover{
color: #0e985e;
}
.green ::selection {
background:#14da8b;
color: white;
text-shadow: none;
}
.green ::-moz-selection {
background: #14da8b;
color: #EEE;
text-shadow: none;
}
.green .icon{
border-color:#14da88;
}
body.green{
background: linear-gradient(90deg, #67e2ab 0%, #14da8b 50%, #0e986a 100%);
scrollbar-color:#14da88 #91f4d8;
}
.green .container, .green #mobile-nav{
background-color:#c9fae8;
border-color:#14da9b;
}

View File

@@ -38,7 +38,7 @@ body.pink-lighter{
background: linear-gradient(90deg, #f5c2ea 0%, #e89dfb 50%, #f6959c 100%);
scrollbar-color:#a414da #d991f4;
}
.pink-lighter .container{
.pink-lighter .container, .pink-lighter #mobile-nav{
background-color:#fac9f6;
border-color:#da14c6;
}

View File

@@ -34,10 +34,10 @@
}
body.pink{
background: linear-gradient(90deg, #f8a1e5 0%, #d55ff3 50%, #f5c35e 100%);
background: linear-gradient(90deg, rgba(235, 202, 202, 1) 0%, rgba(201, 77, 255, 1) 50%, rgba(242, 234, 124, 1) 100%);
scrollbar-color:#a414da #d991f4;
}
.pink .container{
.pink .container, .pink #mobile-nav{
background-color:#fac9f6;
border-color:#da14c6;
}

View File

@@ -38,7 +38,7 @@ body.purple{
background: linear-gradient(90deg, #fb46c0 0%, #a414da 50%, #720e98 100%);
scrollbar-color:#a414da #d991f4;
}
.purple .container{
.purple .container, .purple #mobile-nav{
background-color:#ecc9fa;
border-color:#a414da;
}

View File

@@ -0,0 +1,43 @@
.red{
background-color:#fac9c9;
border-color:#da1414;
color:black;
}
.red .heading
{
background-color:#f49191;
border-color:#da1414;
color:black;
}
.red .heading b
{
color:black;
}
.red a{
color: #c61111;
}
.red a:hover{
color: #980e0e;
}
.red ::selection {
background:#da1414;
color: white;
text-shadow: none;
}
.red ::-moz-selection {
background: #da1414;
color: #EEE;
text-shadow: none;
}
.red .icon{
border-color:#da1414;
}
body.red{
background: linear-gradient(90deg, #fb4646 0%, #da1414 50%, #980e0e 100%);
scrollbar-color:#da1414 #f49191;
}
.red .container, .red #mobile-nav{
background-color:#fac9c9;
border-color:#da1414;
}

View File

@@ -8,10 +8,14 @@
<link rel="stylesheet" href="{{ url_for('static', filename='themes/'+theme) }}">
{% endfor %}
<meta name="viewport" content="width=device-width">
<div id="main">
<div class="container" id="nav">
<div class="navitem">{{ server_time() }}</div>
<div class="heading">Myriad</div>
<div class="navitem">> <a href="{{ url_for('home.index') }}">Home</a></div>
<div class="navitem">> <a href="{{ url_for('home.full_list') }}">Full List</a></div>
@@ -20,8 +24,8 @@
{% if g.user %}
<div class="heading">Manage</div>
<div class="navitem">> <a href="{{ url_for('manage.new') }}">Add New Member</a></div>
<div class="navitem">> <a href="{{ url_for('manage.import_member') }}">Import New Member</a></div>
<div class="navitem">> <a href="{{ url_for('manage.groups') }}">Member groups</a></div>
<div class="navitem">> <a href="{{ url_for('manage.import_member_route') }}">Import New Member</a></div>
<div class="navitem">> <a href="{{ url_for('manage.groups') }}">Manage groups</a></div>
<div class="navitem">> <a href="{{ url_for('manage.assets') }}">Site Assets</a></div>
<div class="navitem">> <a href="{{ url_for('manage.admin') }}">Site Administration</a></div>
{% endif %}
@@ -47,6 +51,14 @@
</div>
<div id="mobile-nav">
<a href="{{ url_for('home.index') }}">Home</a> |
<a href="{{ url_for('home.full_list') }}">Full List</a> |
<a href="{{ url_for('home.groups') }}">Groups</a> |
<a href="{{ url_for('blog.blog') }}">Blog</a> |
{% if g.user %}<a class="mobile" href="{{ url_for('manage.admin') }}">Admin</a>{% endif %}
</div>
{% block content %}{% endblock %}

View File

@@ -7,6 +7,8 @@
{% block content %}
<div class="container">
{% if g.user %}<a class="mobile" href="{{ url_for('blog.new') }}">New post</a>{% endif %}
{% for post in blog %}
{% set op = member_ids[post[1]] %}
{% if not g.user and op[9]==0 %}
@@ -19,9 +21,9 @@
<img src="{{ url_for('static', filename='any.jpg') }}" class="icon">
{% endif %}
<div class="title">{{post[3]|safe}}</div>
<div class="timestamp">{{post[2]}} - <a href="{{ url_for('home.page', mid=post[1]) }}">{{op[3]}}</a> {% if g.user %}{% if op[9]==0 %}(Private){% else %}(Public)</b>{% endif %}{% endif %}</div>
<div class="timestamp">{{ get_datetime_str(post[2]) }} - <a href="{{ url_for('home.page', mid=post[1]) }}">{{op[3]}}</a> {% if g.user %}{% if op[9]==0 %}(Private){% else %}(Public)</b>{% endif %}{% endif %}</div>
<div class="content">
{{post[4]|safe}}
{{post[4].replace('\n', '<br>')|safe}}
</div>
<br class="clear" />
{% if g.user %}

View File

@@ -13,7 +13,7 @@
{% for member in memberlist %}
{% if not g.user and member[9]==0 %}
{% else %}
<a href="#m{{ member[0] }}">{{ member[3] }}</a> |
<a href="#m{{ member[0] }}">{{ member[3]|safe }}</a> |
{% endif %}
{% endfor %}
</div>
@@ -25,14 +25,14 @@
{% else %}
<div class="profile {{member[10]}}" id="m{{ member[0] }}">
<div class="heading"><b>{{ member[3]|safe }}</b> {{ member[4]|safe }}</div>
<div class="heading">{% if member[9]==0 %}<img class="minilock" src="{{ url_for('static', filename='lock.png') }}">{% endif %}<b>{{ member[3]|safe }}</b> {{ member[4]|safe }}</div>
{% if icons[member[0]] %}
<img src="{{ url_for('static', filename='icons/'+icons[member[0]]) }}" class="icon">
{% else %}
<img src="{{ url_for('static', filename='any.jpg') }}" class="icon">
{% endif %}
<div class="bio">
{{ member[5]|safe }}
{{ member[5].replace('\n', '<br>')|safe }}
</div>
<br class="clear" />
<div class="heading links"><a href="{{ url_for('home.page', mid=member[0]) }}">View Page</a>{% if g.user %} &#9734 {% if member[8]==0 %}<a href="{{ url_for('manage.add_to_front', mid=member[0],location='full') }}">Add to Front</a>{% else %}<a href="{{ url_for('manage.remove_front', mid=member[0],location='full') }}">Remove from Front</a>{% endif %} &#9734 <a href="{{ url_for('manage.edit', mid=member[0]) }}">Edit</a> &#9734 {% if member[7]==0 %}<a href="{{ url_for('manage.add_to_home', mid=member[0],location='full') }}">Pin to Homepage</a>{% else %}<a href="{{ url_for('manage.remove_home', mid=member[0],location='full') }}">Unpin from Homepage</a>{% endif %}{% endif %}</div>

View File

@@ -12,7 +12,10 @@
{{group[2]}}<br><br>
{% if group[0] in group_members %}
{% for member in group_members[group[0]] %}
&#10032; <a href="{{ url_for('home.page', mid=member[0]) }}">{{ member[3] }}</a> <br>
{% if not g.user and member[9]==0 %}
{% else %}
&#10032; <a href="{{ url_for('home.page', mid=member[0]) }}">{{ member[3]|safe }}</a> <br>
{% endif %}
{% endfor %}
{% endif %}
</div>

View File

@@ -4,13 +4,14 @@
{% block content %}
<div class="container">
{% if front_list|length > 0 %}
<div id="frontbanner" class="heading">
<b>currently fronting: </b> {% for member in front_list %}{% if not g.user and member[9]==0 %}{% else %}<a href="{{ url_for('home.page', mid=member[0]) }}">{{ member[3] }}</a> {% if front_list.index(member) != front_list|length -1 %}&{% endif %}{% endif %} {% endfor %}
</div>
{% if front_list|length > 0 %}
<b>currently fronting: </b> {% for member in front_list %}{% if not g.user and member[9]==0 %}{% else %}<a href="{{ url_for('home.page', mid=member[0]) }}">{{ member[3]|safe }}</a> {% if front_list.index(member) != front_list|length -1 %}&{% endif %}{% endif %} {% endfor %}
{% else %}
<p>There are currently no members listed as fronting</p>
<i><sub>There are currently no members listed as fronting</sub></i>
{% endif %}
{% if last_updated %}<br><i><sub>last updated: {{ get_datetime_str(last_updated) }}</sub></i>{% endif %}
</div>
{% for member in home_pins %}
@@ -19,14 +20,14 @@
<div class="profile {{member[10]}}" id="m{{ member[0] }}">
<div class="heading"><b>{{ member[3]|safe }}</b> {{ member[4]|safe }}</div>
<div class="heading">{% if member[9]==0 %}<img class="minilock" src="{{ url_for('static', filename='lock.png') }}">{% endif %}<b>{{ member[3]|safe }}</b> {{ member[4]|safe }}</div>
{% if icons[member[0]] %}
<img src="{{ url_for('static', filename='icons/'+icons[member[0]]) }}" class="icon">
{% else %}
<img src="{{ url_for('static', filename='any.jpg') }}" class="icon">
{% endif %}
<div class="bio">
{{ member[5]|safe }}
{{ member[5].replace('\n', '<br>')|safe }}
</div>
<br class="clear" />
<div class="heading links"><a href="{{ url_for('home.page', mid=member[0]) }}">View Page</a>{% if g.user %} &#9734 {% if member[8]==0 %}<a href="{{ url_for('manage.add_to_front', mid=member[0],location='home') }}">Add to Front</a>{% else %}<a href="{{ url_for('manage.remove_front', mid=member[0],location='home') }}">Remove from Front</a>{% endif %} &#9734 <a href="{{ url_for('manage.edit', mid=member[0]) }}">Edit</a> &#9734 {% if member[7]==0 %}<a href="{{ url_for('manage.add_to_home', mid=member[0],location='home') }}">Pin to Homepage</a>{% else %}<a href="{{ url_for('manage.remove_home', mid=member[0],location='home') }}">Unpin from Homepage</a>{% endif %}{% endif %}</div>

View File

@@ -6,9 +6,25 @@
{% block content %}
<div class="container">
<div class="mobile">
<div class="heading">Manage Members</div>
<a href="{{ url_for('manage.new') }}">Add new member</a><br><br>
<a href="{{ url_for('manage.groups') }}">Manage groups</a><br><br>
<a href="{{ url_for('manage.assets') }}">Site assets</a>
</div>
<div class="heading">Front Log</div>
<div class="maintext">Front change history</div>
<div class="log">
{% for front in front_log %}
<p><b>{{ get_datetime_str(front[2]) }}</b> - {{ get_member(front[1])[3]|safe }}{% if front[3] %} <i>(ended: {{ get_datetime_str(front[3]) }})</i> | <a class="danger" href="{{ url_for('manage.delete_front_log', fid=front[0]) }}">delete</a>{% endif %}</p>
{% endfor %}
</div>
<div class="heading">Backup</div>
<a href="{{ url_for('manage.export_system') }}">Export entire system TEXT ONLY</a><br>
<a href="{{ url_for('manage.export_system_full') }}">Export entire system WITH ATTACHMENTS</a>
<a href="{{ url_for('manage.export_system') }}">Export entire system as JSON</a> (without images)<br>
<a href="{{ url_for('manage.export_system_full') }}">Export entire system as ZIP</a> (with images)
<hr>
@@ -30,10 +46,6 @@
<hr>
<a class="danger">Clear database</a> WARNING: this action is permanent and irreversible<br>
<br><br>
<div class="heading big pink">WIP ZONE</div>
<div class="heading">Site Users</div>
<div class="maintext">These are the usernames that can log in to the site</div>
@@ -45,9 +57,6 @@
<div class="heading">Admin Log</div>
<div class="maintext">Actions taken by registered users</div>
<div class="heading">Front Log</div>
<div class="maintext">Front change history</div>
<div class="heading">Site Theme</div>
<div class="maintext">Choose the overall theme for the site here</div>
<form>

View File

@@ -36,9 +36,44 @@
{% endif %}
<div class="heading big">Blinkies</div>
<p>Not implemented yet (sorry!)</p>
<div class="heading big">Stamps</div>
<p>Not implemented yet (sorry!)</p>
<hr>
<div class="heading big">Upload Images</div>
<p>Upload images for use in pages and custom sections.
Just right click, copy image URL, then use it in the HTML
however you like. You can use style tags to customize how the
images look/behave on your page, just be careful not to use class or
id selectors that already exist (or do, if you are wanting to overwrite
their behaviour!). We will make a list of in-use class and ids here, soon.
</p>
<form method="post" enctype="multipart/form-data" id="image">
<input type="file" name="image" class="mobile-edit">
<input type="submit" value="Upload to Site">
</form>
<div class="manage_images">
<div class="manage_images_2">
{% for image in images %}
<div class="manage_images_3">
<img class="mng_img" src="{{ url_for('static', filename='misc/'+image) }}">
<br class="clear" />
{% set imgurl = url_for('static', filename='misc/'+image) %}
<a href="" onclick="copyURL('{{imgurl}}')">Copy Image URL</a> &#9734 <a href="">Delete</a>
</div>
{% endfor %}
</div>
</div>
<script>
function copyURL(a) {
navigator.clipboard.writeText(a);
}
</script>
</div>

View File

@@ -1,13 +1,13 @@
{% extends 'base.html' %}
{% block header %}
<div class="title">{% block title %}Edit {{ member[3] }}{% endblock %}</div>
<div class="title">{% block title %}Edit {{ remove_html(member[3]) }}{% endblock %}</div>
{% endblock %}
{% block content %}
<div class="container">
<a href="{{url_for('home.page', mid=member[0])}}">View {{member[3]}}'s page</a>
<a href="{{url_for('home.page', mid=member[0])}}">View {{member[3]|safe}}'s page</a>
<br class="clear" />
<div class="heading">Edit Details</div>
@@ -33,9 +33,40 @@
<input type="submit" value="Submit">
</form>
<a href="{{ url_for('manage.delete', mid=member[0]) }}" class="danger">Delete member</a> - WARNING: this is permanent and cannot be undone!
<div class="heading">Public Page Settings</div>
<p>Select which sections to show on this member's public page</p>
<form method="post" id="page_settings">
<input type="checkbox" id="show_groups" name="show_groups" {% if member[15] %}checked{% endif %}>
<label for="show_groups">Groups</label><br>
<input type="checkbox" id="show_blog" name="show_blog" {% if member[11] %}checked{% endif %}>
<label for="show_blog">Blog</label><br>
<input type="checkbox" id="show_icons" name="show_icons" {% if member[12] %}checked{% endif %}>
<label for="show_icons">Icons</label><br>
<input type="checkbox" id="show_blinkies" name="show_blinkies" {% if member[13] %}checked{% endif %}>
<label for="show_blinkies">Blinkies</label><br>
<input type="checkbox" id="show_stamps" name="show_stamps" {% if member[14] %}checked{% endif %}>
<label for="show_stamps">Stamps</label><br>
<input type="submit" name="page_settings" value="Submit">
</form>
<hr>
<p>Customize section titles. Leave blank to hide the heading bar for that section altogether.</p>
<form method="post">
<label for="groups_title">Groups</label>
<input name="groups_title" id="groups_title" value="{{ member[20] }}"><br>
<label for="blog_title">Blog</label>
<input name="blog_title" id="blog_title" value="{{ member[16] }}"><br>
<label for="icons_title">Icons</label>
<input name="icons_title" id="icons_title" value="{{ member[17] }}"><br>
<label for="blinkies_title">Blinkies</label>
<input name="blinkies_title" id="blinkies_title" value="{{ member[18] }}"><br>
<label for="stamps_title">Stamps</label>
<input name="stamps_title" id="stamps_title" value="{{ member[19] }}"><br>
<input type="submit" name="section_titles" value="Submit">
</form>
{% if unjoined_groups or joined_groups %}
<div class="heading">Manage groups</div>
<form method="post" id="groups">
@@ -55,11 +86,12 @@
</select>
<input type="submit" value="Remove from group">
</form>
{% endif %}
<div class="heading">Manage Icons</div>
<form method="post" enctype="multipart/form-data" id="icons">
<input type="file" name="file">
<input type="file" name="file" class="mobile-edit">
<input type="submit" value="Upload New Icon">
</form>
@@ -80,7 +112,7 @@
<div class="heading">Manage Blinkies</div>
<form method="post" enctype="multipart/form-data" id="blinkies">
<input type="file" name="blinkie">
<input type="file" name="blinkie" class="mobile-edit">
<input type="submit" value="Upload to Blinkies">
</form>
@@ -88,7 +120,7 @@
<div class="manage_images_2">
{% for blinkie in blinkies %}
<div class="manage_blinkies_3">
<img src="{{ url_for('static', filename='blinkies/'+blinkie[2]) }}">
<img class="blinkie" src="{{ url_for('static', filename='blinkies/'+blinkie[2]) }}">
<br class="clear" />
<a href="{{ url_for('manage.delete_blinkie', mid=member[0], blinkie_id=blinkie[0]) }}">Delete</a>
</div>
@@ -99,7 +131,7 @@
<div class="heading">Manage Stamps</div>
<form method="post" enctype="multipart/form-data" id="stamps">
<input type="file" name="stamp">
<input type="file" name="stamp" class="mobile-edit">
<input type="submit" value="Upload to Stamps">
</form>
@@ -107,7 +139,7 @@
<div class="manage_images_2">
{% for stamp in stamps %}
<div class="manage_stamps_3">
<img src="{{ url_for('static', filename='stamps/'+stamp[2]) }}">
<img class="stamp" src="{{ url_for('static', filename='stamps/'+stamp[2]) }}">
<br class="clear" />
<a href="{{ url_for('manage.delete_stamp', mid=member[0], stamp_id=stamp[0]) }}">Delete</a>
</div>
@@ -115,6 +147,36 @@
</div>
</div>
<hr>
<div class="heading">Custom Page Sections</div>
<p>Here you can make sections for your page, embed whatever you like</p>
<form method="post" id="sections">
<label for="section_title">Section Title</label>
<input name="section_title" id="section_title"><br>
<label for="section_content">Section Content</label>
<textarea name="section_content" id="section_content">Hello World!</textarea><br><br>
<input type="submit" name="new_section" value="Create New Section">
</form>
<hr>
{% for section in sections %}
<form method="post">
<label for="section_pos">Position</label>
<input name="section_pos" id="section_pos" value="{{section[4]}}"><br>
<label for="section_title">Section Title</label>
<input name="section_title" id="section_title" value="{{section[2]}}"><br>
<label for="section_content">Section Content</label>
<textarea name="section_content" id="section_content">{{section[3]}}</textarea><br><br>
<input type="hidden" id="section_id" name="section_id" value="{{section[0]}}">
<input type="submit" name="update_section" value="Update Section">
</form>
<a href="{{ url_for('manage.delete_section', sid=section[0]) }}">Delete section</a>
{% endfor %}
<div class="heading">Manage Member Data</div>
<a href="{{ url_for('manage.export_fields', mid=member[0]) }}">Export Fields</a><br>
@@ -123,6 +185,9 @@
{% if stamps %}<a href="{{ url_for('manage.export_stamps', mid=member[0]) }}">Export stamps</a><br>{% endif %}
{% if icons or blinkies or stamps %}<a href="{{ url_for('manage.export_member', mid=member[0]) }}">Export all member data</a><br>{% endif %}
<hr>
<div class="heading danger">Danger zone</div>
<a href="{{ url_for('manage.delete', mid=member[0]) }}" class="danger">Delete member</a> - WARNING: this is permanent and cannot be undone!
{% if edit_location %}

View File

@@ -13,6 +13,10 @@
<input name="subtitle" id="subtitle"><br>
<label for="bio">Description</label>
<textarea name="bio" id="bio"></textarea><br>
<input type="radio" id="public" name="privacy" value=1 checked>
<label for="public">Public</label><br>
<input type="radio" id="private" name="privacy" value=0>
<label for="private">Private</label><br>
<input type="submit" value="Submit">
</form>

View File

@@ -1,5 +1,5 @@
{% extends 'base.html' %}
{% block title %}{{ member[3] }}{% endblock %}
{% block title %}{{ remove_html(member[3]) }}{% endblock %}
{% block content %}
@@ -12,62 +12,94 @@
{% else %}
{% if g.user %}
<a href="{{url_for('manage.edit', mid=member[0])}}">Edit member</a>
<a href="{{url_for('manage.edit', mid=member[0])}}">Edit member</a> &#9734 {% if member[8]==0 %}<a href="{{ url_for('manage.add_to_front', mid=member[0],location='mid') }}">Add to Front</a>{% else %}<a href="{{ url_for('manage.remove_front', mid=member[0],location='mid') }}">Remove from Front</a>{% endif %} &#9734 {% if member[7]==0 %}<a href="{{ url_for('manage.add_to_home', mid=member[0],location='mid') }}">Pin to Homepage</a>{% else %}<a href="{{ url_for('manage.remove_home', mid=member[0],location='mid') }}">Unpin from Homepage</a>{% endif %}
<br class="clear" />
{% endif %}
{% if member[8]==1 %}<div class="heading"><i>Currently Fronting</i></div>{% endif %}
{% if icon %}
<img class="icon" src="{{ url_for('static', filename='icons/'+icon[0]) }}">
{% else %}
<img class="icon" src="{{ url_for('static', filename='any.jpg') }}">
{% endif %}
<div class="title">{{member[3]}}</div>
<div class="title">{% if member[9]==0 %}<img class="lock" src="{{ url_for('static', filename='lock.png') }}">{% endif %}{{member[3]|safe}}</div>
<div class="maintext">
{{member[5]|safe}}
{{member[5].replace('\n', '<br>')|safe}}
</div>
<br class="clear" />
{% if blog|length > 0 %}
<div class="heading big">{{member[3]}}'s blog</div>
<div id="blog">
{% for post in blog %}
{% if not g.user and post[5]==0 %}
{% if member[11] %}
{% if blog|length > 0 %}
{% if not g.user and not blog_public_show %}
{% else %}
<div class="post">
<div class="title">{{post[3]|safe}}</div>
<div class="timestamp">{{post[2]}} {% if g.user %}{% if post[5]==0 %}(Private){% else %}(Public){% endif %}{% endif %}</div>
<div class="content">
{{post[4]|safe}}
</div>
{% if g.user %}<a href="{{url_for('blog.toggle',pid=post[0], location=member[0])}}">Toggle privacy</a> | <a href="{{url_for('blog.edit',pid=post[0],location=member[0])}}">Edit Post</a> | <a href="{{url_for('blog.delete', pid=post[0], location=member[0])}}" class="danger">Delete post</a>{% endif %}
</div>
{% if member[16] %}<div class="heading big">{{ member[16] }}</div>{% endif %}
{% endif %}
<div id="blog">
{% for post in blog %}
{% if not g.user and post[5]==0 %}
{% else %}
<div class="post">
<div class="title">{{post[3]|safe}}</div>
<div class="timestamp">{{post[2]}} {% if g.user %}{% if post[5]==0 %}(Private){% else %}(Public){% endif %}{% endif %}</div>
<div class="content">
{{post[4].replace('\n', '<br>')|safe}}
</div>
{% if g.user %}<a href="{{url_for('blog.toggle',pid=post[0], location=member[0])}}">Toggle privacy</a> | <a href="{{url_for('blog.edit',pid=post[0],location=member[0])}}">Edit Post</a> | <a href="{{url_for('blog.delete', pid=post[0], location=member[0])}}" class="danger">Delete post</a>{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endif %}
{% for section in sections %}
{% if section[2] %}<div class="heading big">{{ section[2] }}</div>{% endif %}
{{ section[3]|safe }}
{% endfor %}
<!-- {% if groups|length > 0 %}
<div class="heading big">{{ member[20] }}</div>
{% for group in groups %}
{% endfor %}
{% endif %} -->
{% if member[13] %}
{% if blinkies|length > 0 %}
{% if member[18] %}<div class="heading big">{{ member[18] }}</div>{% endif %}
<div class="image-section">
{% for blinkie in blinkies %}
<img class="blinkie" src="{{ url_for('static', filename='blinkies/'+blinkie[0]) }}">
{% endfor %}
</div>
{% endif %}
{% if all_icons|length > 1 %}
<div class="heading big">Icons</div>
{% for i in all_icons %}
<img class="icon" src="{{ url_for('static', filename='icons/'+i[0]) }}">
{% endfor %}
</div>
{% endif %}
{% endif %}
<br class="clear" />
{% if blinkies|length > 0 %}
<div class="heading big">Blinkies</div>
{% for blinkie in blinkies %}
<img src="{{ url_for('static', filename='blinkies/'+blinkie[0]) }}">
{% endfor %}
{% if member[14] %}
{% if stamps|length > 0 %}
{% if member[19] %}<div class="heading big">{{ member[19] }}</div>{% endif %}
<div class="image-section">
{% for stamp in stamps %}
<img class="stamp" src="{{ url_for('static', filename='stamps/'+stamp[0]) }}">
{% endfor %}
</div>
{% endif %}
{% endif %}
<br class="clear" />
{% if stamps|length > 0 %}
<div class="heading big">Stamps</div>
{% for stamp in stamps %}
<img src="{{ url_for('static', filename='stamps/'+stamp[0]) }}">
{% endfor %}
{% if member[12] %}
{% if all_icons|length > 1 %}
{% if member[17] %}<div class="heading big">{{ member[17] }}</div>{% endif %}
<div class="image-section">
{% for i in all_icons %}
<img class="icon" src="{{ url_for('static', filename='icons/'+i[0]) }}">
{% endfor %}
</div>
{% endif %}
{% endif %}
<br class="clear" />
{% endif %}

25
myriad/utilities.py Normal file
View File

@@ -0,0 +1,25 @@
import datetime, re
def server_time():
raw = datetime.datetime.now()
real = raw.strftime("%d/%m/%Y, %H:%M:%S")
return real
def server_time_obj():
return datetime.datetime.now()
def get_datetime_obj(dt_string):
date_raw = dt_string.split(",")
date = date_raw[0]
day,month,year = date.split("/")
time = date_raw[1]
hour,minute,second = time.split(":")
dt_obj = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))
return dt_obj
def get_datetime_str(dt_obj):
return dt_obj.strftime("%d/%m/%Y, %H:%M:%S")
def remove_html(mystring):
newstring = re.sub('<[^<]+?>', '', mystring)
return newstring