Code-Input was updated to 1.2.2 and currently works! I have the upload functionality working and have begun working on an undo button. It currently saves all the files to a dir everytime you access /templates but I haven't implemented a full undo yet.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
57
app/forms.py
57
app/forms.py
@ -1,10 +1,55 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms.fields import SubmitField
|
||||
from wtforms.fields import (SubmitField,
|
||||
PasswordField,
|
||||
StringField,
|
||||
TextAreaField,
|
||||
IntegerField,
|
||||
BooleanField,
|
||||
RadioField)
|
||||
from wtforms.validators import InputRequired, Length
|
||||
from flask_wtf.file import FileField, FileRequired
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask_codemirror.fields import CodeMirrorField
|
||||
|
||||
class TemplateForm(FlaskForm):
|
||||
template_code = CodeMirrorField(
|
||||
language='htmlembedded',
|
||||
config={'lineNumbers': 'true'})
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
class ApiKey(FlaskForm):
|
||||
api_key = PasswordField('Api Key',
|
||||
validators=[InputRequired(),
|
||||
Length(min=20, max=25)]
|
||||
)
|
||||
|
||||
|
||||
class TemplateForm(FlaskForm):
|
||||
name = StringField("Template File Name",
|
||||
validators=[InputRequired()])
|
||||
|
||||
body = TextAreaField("Template Code",
|
||||
validators=[InputRequired()])
|
||||
|
||||
submit = SubmitField('Upload Templates')
|
||||
|
||||
# template_code = CodeMirrorField( language='htmlembedded',
|
||||
# config={'lineNumbers': 'true'})
|
||||
|
||||
class CsvForm(FlaskForm):
|
||||
file = FileField(validators=[FileRequired()])
|
||||
all_or_some = RadioField("All or Some",
|
||||
choices=['All learners in all groups',
|
||||
'Learners only in adjacent groups'],
|
||||
validators=[InputRequired()])
|
||||
|
||||
|
||||
|
||||
class CourseForm(FlaskForm):
|
||||
title = StringField('Title',
|
||||
validators=[InputRequired(),
|
||||
Length(min=10, max=100)])
|
||||
description = TextAreaField('Course Description',
|
||||
validators=[InputRequired(),
|
||||
Length(max=200)])
|
||||
price = IntegerField('Price', validators=[InputRequired()])
|
||||
level = RadioField('Level',
|
||||
choices=['Beginner', 'Intermediate', 'Advanced'],
|
||||
validators=[InputRequired()])
|
||||
available = BooleanField('Available', default='checked')
|
||||
|
||||
|
||||
143
app/routes.py
143
app/routes.py
@ -13,6 +13,7 @@ from flask import (
|
||||
session,
|
||||
make_response,
|
||||
url_for,
|
||||
g,
|
||||
)
|
||||
from werkzeug.utils import secure_filename
|
||||
from app import app, forms
|
||||
@ -26,6 +27,7 @@ ALLOWED_EXTENSIONS = {"csv"}
|
||||
# Global Variables
|
||||
url = "https://api.northpass.com/"
|
||||
|
||||
|
||||
def download_csv():
|
||||
if request.method == "GET":
|
||||
download = make_response(session["dfcsv"])
|
||||
@ -57,11 +59,13 @@ def allowed_file(filename):
|
||||
def key_required(check):
|
||||
@wraps(check)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if session.get('key') is None:
|
||||
return redirect('/',code=302)
|
||||
if session.get("key") is None:
|
||||
return redirect("/", code=302)
|
||||
return check(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
def ask_key():
|
||||
"""This is the main function that asks for the API Key.
|
||||
@ -71,8 +75,9 @@ def ask_key():
|
||||
specials = '"!@#$%^&*()-+?_=,<>/"'
|
||||
if request.method == "POST":
|
||||
session["key"] = request.form.get("apikey")
|
||||
if (any(char in specials for char in session["key"]) or
|
||||
re.search(r"[\s]", session["key"])):
|
||||
if any(char in specials for char in session["key"]) or re.search(
|
||||
r"[\s]", session["key"]
|
||||
):
|
||||
error = "Invalid Key."
|
||||
session.clear()
|
||||
return render_template("index.html", title="Home", error=error)
|
||||
@ -100,7 +105,7 @@ def render_home():
|
||||
def clear_session():
|
||||
if session.get("key"):
|
||||
session.clear()
|
||||
error="Session Cleared!"
|
||||
error = "Session Cleared!"
|
||||
return render_template("index.html", error=error, title="Home, New session")
|
||||
return render_template("index.html", title="Home, New session")
|
||||
|
||||
@ -109,6 +114,7 @@ def clear_session():
|
||||
def table():
|
||||
return render_template("table.html", tables=[session["table"]], titles=["Table"])
|
||||
|
||||
|
||||
@app.route("/upload_file", methods=["GET", "POST"])
|
||||
@key_required
|
||||
def upload_file():
|
||||
@ -133,11 +139,12 @@ def upload_file():
|
||||
return divide_values(file)
|
||||
return render_template("home.html", title="Bulk Add")
|
||||
|
||||
|
||||
def divide_values(file):
|
||||
emails = []
|
||||
groups = []
|
||||
selection = request.form.get('learner-groups')
|
||||
if request.form['submit']:
|
||||
selection = request.form.get("learner-groups")
|
||||
if request.form["submit"]:
|
||||
if selection == "all-groups":
|
||||
for item in file[1:]:
|
||||
emails.append(item[0])
|
||||
@ -160,13 +167,12 @@ def divide_values(file):
|
||||
return api_csv_parse(emails, groups)
|
||||
return emails
|
||||
|
||||
if request.form['preview']:
|
||||
error="Preview Button Still Under Construction. Try again later."
|
||||
if request.form["preview"]:
|
||||
error = "Preview Button Still Under Construction. Try again later."
|
||||
return render_template("bulk_add.html", error=error, title="Preview Not Yet")
|
||||
|
||||
return render_template(
|
||||
"bulk_add.html", title="Uploaded File"
|
||||
)
|
||||
return render_template("bulk_add.html", title="Uploaded File")
|
||||
|
||||
|
||||
def api_csv_parse(emails, groups):
|
||||
if emails and groups:
|
||||
@ -175,13 +181,15 @@ def api_csv_parse(emails, groups):
|
||||
return api_add_ppl(emails)
|
||||
elif groups:
|
||||
return api_add_groups(groups)
|
||||
return render_template("bulk_add.html", table=htmlcsv, title="CSV Submission")
|
||||
return render_template("bulk_add.html", title="CSV Submission")
|
||||
|
||||
|
||||
@app.route("/bulk_add_opts", methods=["GET", "POST"])
|
||||
@key_required
|
||||
def bulk_add_opts():
|
||||
return render_template("bulk_add.html", titles="Bulk Add Options")
|
||||
|
||||
|
||||
@app.route("/bulk_add", methods=["GET", "POST"])
|
||||
@key_required
|
||||
def bulk_add():
|
||||
@ -223,8 +231,8 @@ def api_add_ppl(emails):
|
||||
payload2 = []
|
||||
endpoint = "v2/bulk/people"
|
||||
for email in emails:
|
||||
payload2.append({"email": email })
|
||||
payload = {"data": {"attributes": {"people": payload2 }}}
|
||||
payload2.append({"email": email})
|
||||
payload = {"data": {"attributes": {"people": payload2}}}
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
@ -239,8 +247,8 @@ def api_add_groups(groups):
|
||||
payload2 = []
|
||||
endpoint = "v2/bulk/people"
|
||||
for group in groups:
|
||||
payload2.append({"groups" : group })
|
||||
payload = {"data": {"attributes": {"people": payload2 }}}
|
||||
payload2.append({"groups": group})
|
||||
payload = {"data": {"attributes": {"people": payload2}}}
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
@ -257,9 +265,7 @@ def api_add_ppl_groups(emails, groups):
|
||||
combinations = list(itertools.product(emails, groups))
|
||||
for combo in combinations:
|
||||
payload2.append({"email": combo[0], "groups": combo[1]})
|
||||
payload = {
|
||||
"data": {"attributes": {"people": payload2 }}
|
||||
}
|
||||
payload = {"data": {"attributes": {"people": payload2}}}
|
||||
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
@ -267,8 +273,8 @@ def api_add_ppl_groups(emails, groups):
|
||||
"X-Api-Key": session["key"],
|
||||
}
|
||||
return payload
|
||||
#response = requests.post(url + endpoint, json=payload, headers=headers)
|
||||
#return check_response(response)
|
||||
# response = requests.post(url + endpoint, json=payload, headers=headers)
|
||||
# return check_response(response)
|
||||
|
||||
|
||||
def check_response(response):
|
||||
@ -286,11 +292,12 @@ def check_response(response):
|
||||
error = "Something went wrong, but I'm not sure what."
|
||||
return render_template("bulk_add.html", title="Shrug", error=error)
|
||||
|
||||
|
||||
@app.route("/load_templates", methods=["GET", "POST"])
|
||||
@key_required
|
||||
def load_templates():
|
||||
count = 0
|
||||
templates= []
|
||||
templates = []
|
||||
|
||||
while True:
|
||||
count += 1
|
||||
@ -300,33 +307,44 @@ def load_templates():
|
||||
"content-type": "application/json",
|
||||
"X-Api-Key": session["key"],
|
||||
}
|
||||
response = requests.get(url+endpoint, headers=headers)
|
||||
response = requests.get(url + endpoint, headers=headers)
|
||||
data = response.json()
|
||||
nextlink = data["links"]
|
||||
for response in data["data"]:
|
||||
name, body = (
|
||||
response["attributes"]["name"], response["attributes"]["body"])
|
||||
templates.append((name,body))
|
||||
last_updated = response["attributes"]["updated_at"].split("T")
|
||||
last_updated = last_updated[0]
|
||||
name, body, last_updated = (
|
||||
response["attributes"]["name"],
|
||||
response["attributes"]["body"],
|
||||
last_updated,
|
||||
)
|
||||
templates.append((name, body, last_updated))
|
||||
|
||||
if "next" not in nextlink:
|
||||
break
|
||||
|
||||
return render_template("templates.html",
|
||||
title="Templates",
|
||||
templates=templates,
|
||||
)
|
||||
save_templates_backup(templates)
|
||||
return render_template(
|
||||
"templates.html",
|
||||
title="Templates",
|
||||
templates=templates,
|
||||
)
|
||||
|
||||
return render_template("options.html")
|
||||
|
||||
|
||||
@app.route("/templates", methods=["GET", "POST"])
|
||||
@key_required
|
||||
def templates():
|
||||
if request.method == "POST":
|
||||
if request.form['submit-template']:
|
||||
name = request.form.get('template_name')
|
||||
body = request.form.get("loaded-content")
|
||||
if request.form["submit-template"]:
|
||||
name = request.form.get("template_name")
|
||||
body = request.form.get("body")
|
||||
g.last_template = name
|
||||
if body == "":
|
||||
error = "Ooph. Looks like you didn't load the changes before submitting."
|
||||
error = (
|
||||
"Ooph. Looks like you didn't load the changes before submitting."
|
||||
)
|
||||
return render_template("templates.html", error=error)
|
||||
else:
|
||||
endpoint = "v2/custom_templates"
|
||||
@ -335,22 +353,21 @@ def templates():
|
||||
"content-type": "application/json",
|
||||
"X-Api-Key": session["key"],
|
||||
}
|
||||
payload = {"custom_template": {
|
||||
"name":name,
|
||||
"body":body
|
||||
}}
|
||||
response = requests.post(url+endpoint, json=payload, headers=headers)
|
||||
payload = {"custom_template": {"name": name, "body": body}}
|
||||
response = requests.post(url + endpoint, json=payload, headers=headers)
|
||||
return check_templates(response)
|
||||
return load_templates()
|
||||
|
||||
|
||||
def check_templates(response):
|
||||
print(response)
|
||||
response = str(response)
|
||||
if "201" in response:
|
||||
error = "Success! Templates Uploaded."
|
||||
return render_template("templates.html",
|
||||
title="Templates Added",
|
||||
error=error)
|
||||
button = "Undo"
|
||||
return render_template(
|
||||
"templates.html", title="Templates Added", error=error, button=button
|
||||
)
|
||||
elif "403" in response:
|
||||
error = "Uh oh. Looks like you don't have appropriate privileges."
|
||||
return render_template("templates.html", error=error)
|
||||
@ -362,28 +379,44 @@ def check_templates(response):
|
||||
return render_template("templates.html", title="Shrug", error=error)
|
||||
|
||||
|
||||
@app.route("/bulk_courses_to_groups", methods=["GET", "POST"])
|
||||
def save_templates_backup(templates):
|
||||
g.client_path = os.path.join(UPLOAD_FOLDER, session["school"])
|
||||
if os.path.exists(g.client_path):
|
||||
pass
|
||||
else:
|
||||
os.mkdir(g.client_path)
|
||||
|
||||
for tupe in templates:
|
||||
file_name = tupe[0] + ".liquid"
|
||||
file_body = tupe[1]
|
||||
complete_path = os.path.join(g.client_path, file_name)
|
||||
|
||||
with open(complete_path, "w+") as temp:
|
||||
temp.write(file_body)
|
||||
temp.close
|
||||
|
||||
|
||||
@app.route("/undo_template", methods=["POST"])
|
||||
@key_required
|
||||
def bulk_courses_to_groups():
|
||||
pass
|
||||
def undo_template():
|
||||
print(g.client_path)
|
||||
template_path = os.path.join(g.client_path, g.last_template)
|
||||
if request.method == "POST":
|
||||
if request.form["undo_templates"]:
|
||||
if os.path.exists(template_path):
|
||||
print(template_path)
|
||||
|
||||
|
||||
@app.route("/bulk_invite_ppl", methods=["GET", "POST"])
|
||||
@key_required
|
||||
def bulk_invite_ppl():
|
||||
pass
|
||||
|
||||
@app.route('/cmtest', methods = ['GET', 'POST'])
|
||||
@app.route("/cmtest", methods=["GET", "POST"])
|
||||
def cmtest():
|
||||
form = TemplateForm()
|
||||
if form.validate_on_submit():
|
||||
text = form.template_code.data
|
||||
return render_template('templates.html', form=form)
|
||||
|
||||
return render_template("templates.html", form=form)
|
||||
|
||||
|
||||
app.secret_key = "@&I\x1a?\xce\x94\xbb0w\x17\xbf&Y\xa2\xc2(A\xf5\xf2\x97\xba\xeb\xfa"
|
||||
|
||||
|
||||
#if __name__ == "__main__":
|
||||
# if __name__ == "__main__":
|
||||
# ask_key()
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
<a class="np-card" href="{% route course_enrollment, code: course.enrollment_code %}">
|
||||
<div class="np-card-container">
|
||||
{% if course.ribbon %}
|
||||
<div class="np-card-ribbon">
|
||||
{{ course.ribbon }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<video class='left-video' autoplay muted loop>
|
||||
<source src="{{course.promo_video_embed_url}}" type="video/mp4" alt="{{ course.name }}">
|
||||
</video>
|
||||
<div class="np-card-content-bottom">
|
||||
<h3 class="np-card-content-title np-color-white">
|
||||
{{ course.name }}
|
||||
</h3>
|
||||
<div class='np-card-content-description'>
|
||||
{{ course.full_description }}
|
||||
</div>
|
||||
|
||||
<div class="np-card-content-progress np-button-color">
|
||||
{% t shared.progress, count: course.progress %}
|
||||
</div>
|
||||
<div class="np-card-content-category">
|
||||
{{course.categories.first.name}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<style>
|
||||
.np-card {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
.np-card-content-category {
|
||||
border-top: 1px solid lightgray;
|
||||
padding: 10px;
|
||||
}
|
||||
.np-card-content-description {
|
||||
display: block;
|
||||
color: lightgray;
|
||||
}
|
||||
.np-card-content-bottom {
|
||||
padding: 15px;
|
||||
}
|
||||
.np-button-color {
|
||||
color: lightslategray;
|
||||
font-size: 15px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,44 @@
|
||||
<div class="np-card np-no-horizontal-padding">
|
||||
<div class="np-card-container">
|
||||
<div class="np-learning-path np-featured-course">
|
||||
<div class="np-featured-course-img-container" style="overflow: hidden">
|
||||
<video class='featured-video' autoplay muted loop style="margin-left: -60px;">
|
||||
<source id="featured_video" src="{{course.promo_video_embed_url}}" type="video/mp4" alt="{{ course.name }}" />
|
||||
</video>
|
||||
<div class="np-card-image-content-top">
|
||||
</div>
|
||||
</div>
|
||||
<div class="np-card-text-wrapper">
|
||||
<div class="np-card-content np-card-padding np-card-content-vertical">
|
||||
<h3 class="np-card-content-title">
|
||||
{{ course.name }}
|
||||
</h3>
|
||||
<div class="np-card-content-description">
|
||||
{{ course.full_description }}
|
||||
</div>
|
||||
|
||||
<div class="np-card-content-footer" style='display: flex; flex-direction: row; justify-content: space-between;'>
|
||||
<a
|
||||
class="np-top-button np-button-font-color np-button" href="{% route course_enrollment, code: course.enrollment_code %}"
|
||||
>
|
||||
{% if course.enrolled? == false %}
|
||||
{% t shared.enroll %}
|
||||
{% elsif course.started? == false %}
|
||||
{% t shared.start, key: current_school.course_vocabulary %}
|
||||
{% elsif course.completed? %}
|
||||
{% t shared.course.view, key: current_school.course_vocabulary %}
|
||||
{% else %}
|
||||
{% t shared.continue %}
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="np-card-image-content-bottom" style="position: unset;">
|
||||
<div class="np-card-content-progress np-button-color">
|
||||
{% t shared.progress, count: course.progress %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,80 @@
|
||||
<footer class="np-footer">
|
||||
<div class="np-footer-top">
|
||||
{% if website_footer.show_navigation_links? %}
|
||||
<div class="np-footer-navigation">
|
||||
<ul class="np-footer-navigation-list">
|
||||
{% for website_navigation in navigations.footer_navigations %}
|
||||
{% if website_navigation.external? %}
|
||||
<li class="np-footer-navigation-item">
|
||||
<a
|
||||
class="np-footer-navigation-link np-button-color"
|
||||
href="{{ website_navigation.path }}"
|
||||
{% if website_navigation.external? %} target="_blank" {% endif %}
|
||||
>
|
||||
{{ website_navigation.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if current_school.logo_url %}
|
||||
<h2 class="np-footer-logo">
|
||||
<a href="{% route home %}">
|
||||
<img
|
||||
alt="{{ current_school.name }}"
|
||||
class="np-footer-logo-image"
|
||||
src="{{ current_school.logo_url }}"
|
||||
/>
|
||||
</a>
|
||||
</h2>
|
||||
{% else %}
|
||||
<div class="np-school-name np-header-font-color">
|
||||
{{ current_school.name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="np-footer-bottom">
|
||||
<nav class="np-footer-social-links">
|
||||
{% if website_footer.show_social_media_links? %}
|
||||
<ul class="np-footer-social-links-list">
|
||||
{% for social_media_link in website_footer.social_media_links %}
|
||||
<li class="np-footer-social-links-item">
|
||||
<a
|
||||
class="np-footer-social-links-link np-button-color"
|
||||
href="{{ social_media_link.link }}"
|
||||
target="_blank" title="{{ social_media_link.name }}"
|
||||
>
|
||||
<i class="np-footer-social-links-icon
|
||||
np-button-color
|
||||
fab fa-{{ social_media_link.name }}"
|
||||
></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
{% if website_footer.show_customer_service_email? and
|
||||
website_footer.school_customer_service_email
|
||||
%}
|
||||
<div class="np-footer-support">
|
||||
<div class="np-footer-support-item np-footer-support-help np-fc-white np-opacity-50">
|
||||
{% t .need_help %}
|
||||
</div>
|
||||
<div class="np-footer-support-item np-footer-support-email np-fc-white np-opacity-50">
|
||||
{% t .email %}
|
||||
</div>
|
||||
<a
|
||||
class="np-footer-support-item np-footer-support-link np-button-color"
|
||||
href="mailto:{{ website_footer.school_customer_service_email }}"
|
||||
>
|
||||
support@menlosecurity.com
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</footer>
|
||||
11
app/static/files/[Sandbox] Norm's Academy/_head.html.liquid
Normal file
11
app/static/files/[Sandbox] Norm's Academy/_head.html.liquid
Normal file
@ -0,0 +1,11 @@
|
||||
{% styles default %}
|
||||
{% styles colors %}
|
||||
{% styles custom %}
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@200;300;400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.css" integrity="sha512-wR4oNhLBHf7smjy0K4oqzdWumd+r5/+6QO/vDda76MW5iug4PT7v86FoEkySIJft3XA0Ae6axhIvHrqwm793Nw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js" type="text/javascript"></script>
|
||||
183
app/static/files/[Sandbox] Norm's Academy/_header.html.liquid
Normal file
183
app/static/files/[Sandbox] Norm's Academy/_header.html.liquid
Normal file
@ -0,0 +1,183 @@
|
||||
<header class="np-header np-header-color" >
|
||||
<div class="np-header-content">
|
||||
<div class="np-hidden-desktop np-header-mobile-menu-nav">
|
||||
{% if current_person.signed_in? %}
|
||||
<button
|
||||
data-toggle-class="np-hidden"
|
||||
class="np-header-mobile-menu-nav-button fal fa-times np-hidden np-header-font-color"
|
||||
data-toggle-target=".np-header-mobile-avatar-menu,
|
||||
.np-header-mobile-menu-content, .np-main, .np-footer"
|
||||
></button>
|
||||
<button
|
||||
data-test="open-mobile-menu"
|
||||
data-toggle-class="np-hidden"
|
||||
class="np-header-mobile-menu-nav-button np-header-mobile-avatar-menu"
|
||||
data-toggle-target=".fa-times, .np-header-mobile-menu-content, .np-main, .np-footer"
|
||||
>
|
||||
<img
|
||||
alt="{{ current_person.name }}"
|
||||
class="np-header-avatar-image"
|
||||
src="{{ current_person.avatar_url }}"
|
||||
/>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if current_school.logo_url %}
|
||||
<h1 class="np-header-logo">
|
||||
<a href="{% route home %}">
|
||||
<img
|
||||
alt="{{ current_school.name }}"
|
||||
class="np-header-logo-image"
|
||||
src="{{ current_school.logo_url }}"
|
||||
/>
|
||||
</a>
|
||||
</h1>
|
||||
{% else %}
|
||||
<a href="{% route home %}" class="np-school-name np-header-font-color">
|
||||
{{ current_school.name }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="np-hidden-mobile np-header-desktop-nav">
|
||||
<ul class="np-header-desktop-nav-list">
|
||||
{% for website_navigation in navigations.header_navigations_external %}
|
||||
<li class= "np-header-desktop-nav-item">
|
||||
<a
|
||||
href="{{ website_navigation.path }}"
|
||||
class="np-header-desktop-nav-link np-header-font-color"
|
||||
target="_blank"
|
||||
>
|
||||
{{ website_navigation.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if current_person.signed_in? %}
|
||||
<div class="np-hidden-mobile np-header-search np-header-search-expanded">
|
||||
<form action="{% route search %}" method="get" data-test="desktop-search">
|
||||
<input
|
||||
aria-label="{% t .search %}"
|
||||
class="np-header-search-input np-header-font-background-color"
|
||||
type="text"
|
||||
name="q"
|
||||
placeholder="{% t .search %}"
|
||||
/>
|
||||
<i class="np-header-search-icon far fa-search"></i>
|
||||
</form>
|
||||
</div>
|
||||
<div class="np-hidden-mobile np-header-avatar">
|
||||
<button
|
||||
class="np-header-avatar-button"
|
||||
data-test="open-desktop-menu"
|
||||
data-toggle-class-on-target="np-hidden"
|
||||
data-toggle-target=".np-header-avatar-tooltip"
|
||||
data-toggle-outside
|
||||
>
|
||||
<img
|
||||
alt="{{ current_person.name }}"
|
||||
class="np-header-avatar-image"
|
||||
src="{{ current_person.avatar_url }}"
|
||||
>
|
||||
</button>
|
||||
<div class="np-header-avatar-tooltip np-hidden" role="tooltip">
|
||||
<span class="np-header-avatar-tooltip-arrow-up"></span>
|
||||
<div class="np-header-avatar-tooltip-learner">
|
||||
<div class="np-header-avatar-tooltip-learner-name">
|
||||
{{ current_person.name }}
|
||||
</div>
|
||||
<div class="np-header-avatar-tooltip-learner-email">
|
||||
{{ current_person.email }}
|
||||
</div>
|
||||
</div>
|
||||
<nav class="np-header-avatar-tooltip-navigation">
|
||||
<a class="np-header-avatar-tooltip-navigation-link" onclick="buildURL(window.location.pathname)" style="cursor:pointer">
|
||||
Default
|
||||
</a>
|
||||
{% unless current_school.sso_active? %}
|
||||
<a
|
||||
class="np-header-avatar-tooltip-navigation-link"
|
||||
href="{% route account %}"
|
||||
>
|
||||
{% t .profile_settings %}
|
||||
</a>
|
||||
{% endunless %}
|
||||
<a
|
||||
class="np-header-avatar-tooltip-navigation-link np-danger"
|
||||
href="{% route logout %}"
|
||||
>
|
||||
{% t .sign_out %}
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<a
|
||||
class="np-header-sign-in np-header-desktop-nav-link np-header-font-color"
|
||||
href="{% route login %}"
|
||||
>
|
||||
{% t shared.sign_in %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="np-hidden-desktop">
|
||||
<div class="np-header-mobile-menu-content np-hidden">
|
||||
{% if current_person.signed_in? %}
|
||||
<img
|
||||
alt="{{ current_person.name }}"
|
||||
class="np-header-mobile-menu-content-avatar"
|
||||
src="{{ current_person.avatar_url }}"
|
||||
/>
|
||||
<div class="np-header-mobile-menu-content-name">
|
||||
{{ current_person.name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="np-header-mobile-menu-content-nav">
|
||||
<form
|
||||
class="np-header-search"
|
||||
data-test="mobile-search"
|
||||
method="get"
|
||||
action="{% route search %}"
|
||||
>
|
||||
<input
|
||||
aria-label="{% t .search %}"
|
||||
class="np-header-search-input"
|
||||
type="text"
|
||||
name="q"
|
||||
placeholder="{% t .search %}"
|
||||
/>
|
||||
<i class="np-header-search-icon far fa-search"></i>
|
||||
</form>
|
||||
{% for website_navigation in navigations.header_navigations %}
|
||||
<a
|
||||
href="{{ website_navigation.path }}"
|
||||
class="np-header-mobile-menu-content-button"
|
||||
{% if website_navigation.external? %} target="_blank" {% endif %}
|
||||
>
|
||||
{{ website_navigation.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
<div class="np-header-mobile-menu-content-line"></div>
|
||||
{% unless current_school.sso_active? %}
|
||||
<a
|
||||
class="np-header-mobile-menu-content-button"
|
||||
href="{% route account %}"
|
||||
>
|
||||
{% t .profile_settings %}
|
||||
</a>
|
||||
{% endunless %}
|
||||
<a
|
||||
class="np-header-mobile-menu-content-button np-danger"
|
||||
href="{% route logout %}"
|
||||
>
|
||||
{% t .sign_out %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "messages" %}
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
<div class="np-homepage-featured np-faq np-max-width">
|
||||
<div class="np-homepage-featured-text">
|
||||
<div class="np-homepage-headline">
|
||||
Frequently Asked Questions
|
||||
</div>
|
||||
</div>
|
||||
<div class="row np-faqs">
|
||||
<div class="col-md-6">
|
||||
<div class="np-accordion">
|
||||
<div class="accordion-btn">
|
||||
<i class="fal fa-plus"></i>
|
||||
<div class="accordion-title"><strong>Q:</strong> I'm not in shape. Can I still go to a climbing gym?</div>
|
||||
</div>
|
||||
<div class="accordion-panel">
|
||||
<div class="accordion-panel-content">
|
||||
<p><strong>A:</strong> Absolutely. Climbing is a wonderful full-body and full-mind workout. If you're looking to get in shapre but tired of pumping iron, climbing is a fantastic option.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="np-accordion">
|
||||
<div class="accordion-btn">
|
||||
<i class="fal fa-plus"></i>
|
||||
<div class="accordion-title"><strong>Q:</strong> I'm scared of heights. Should I try climbing?</div>
|
||||
</div>
|
||||
<div class="accordion-panel">
|
||||
<div class="accordion-panel-content">
|
||||
<p><strong>A:</strong> Many climbers have a fear of heights! However, it goes away when you use your own strength and technique to get to the top of a wall. Start small by climbing in a gym first.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="np-accordion">
|
||||
<div class="accordion-btn">
|
||||
<i class="fal fa-plus"></i>
|
||||
<div class="accordion-title"><strong>Q:</strong> Should I hire someone to belay me at a gym?</div>
|
||||
</div>
|
||||
<div class="accordion-panel">
|
||||
<div class="accordion-panel-content">
|
||||
<p><strong>A:</strong> You absolutely can as a quick introduction. However, what would be even better is to grab a friend and take a belay class at your local gym.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="np-accordion">
|
||||
<div class="accordion-btn">
|
||||
<i class="fal fa-plus"></i>
|
||||
<div class="accordion-title"><strong>Q:</strong> What is bouldering?</div>
|
||||
</div>
|
||||
<div class="accordion-panel">
|
||||
<div class="accordion-panel-content">
|
||||
<p><strong>A:</strong> Great question! Bouldering is a form of climbing, done on shorter walls. You use a crash pad under your climb to cushion your fall. Be warned, bouldering is much more powerful and has a steeper learning curve.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="np-accordion">
|
||||
<div class="accordion-btn">
|
||||
<i class="fal fa-plus"></i>
|
||||
<div class="accordion-title"><strong>Q:</strong> What are those people with all that gear hanging from their harness doing?</div>
|
||||
</div>
|
||||
<div class="accordion-panel">
|
||||
<div class="accordion-panel-content">
|
||||
<p><strong>A:</strong> Chances are you saw a trad climber! Short for traditional climbing, that gear serves as removable protection. When the climbing pair is done with the climb, nothing is left on the rock.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="np-accordion">
|
||||
<div class="accordion-btn">
|
||||
<i class="fal fa-plus"></i>
|
||||
<div class="accordion-title"><strong>Q:</strong> None of my friends are interested in climbing. How can I find partners?</div>
|
||||
</div>
|
||||
<div class="accordion-panel">
|
||||
<div class="accordion-panel-content">
|
||||
<p><strong>A:</strong>The best way to meet people is to join a gym a start climbing! Climbers are social and you'll start meeting people. You can also use online resources like Mountain Project's Partner Finder.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
/
|
||||
<style>
|
||||
.fa-plus {
|
||||
background-image: unset;
|
||||
background: #0297FA;
|
||||
}
|
||||
.fa-minus {
|
||||
background-image: unset;
|
||||
background: #0297FA; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let accordions = document.getElementsByClassName("accordion-btn");
|
||||
|
||||
for (let i = 0; i < accordions.length; i++) {
|
||||
accordions[i].addEventListener("click", function() {
|
||||
this.querySelector('.fal').classList.toggle("fa-plus");
|
||||
this.querySelector('.fal').classList.toggle("fa-minus");
|
||||
|
||||
let panel = this.nextElementSibling;
|
||||
panel.classList.toggle("panel-open");
|
||||
if (panel.style.maxHeight) {
|
||||
panel.style.maxHeight = null;
|
||||
} else {
|
||||
panel.style.maxHeight = panel.scrollHeight + "px";
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,91 @@
|
||||
<div class="np-homepage-featured np-homepage-featured-items np-max-width">
|
||||
<div class="np-homepage-featured-text">
|
||||
<div class="np-homepage-headline">
|
||||
Recommended Gear
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="featured-photography-carousel np-carousel np-carousel-bg-blue" id="featured-photography-carousel">
|
||||
<div class="np-carousel-card">
|
||||
<div>
|
||||
<img src=https://5prq858zcb-flywheel.netdna-ssl.com/wp-content/uploads/2016/12/50823_4WhiteBG-1-600x625.png" alt="Featured Photography" class='customer-carousel-image'/>
|
||||
<div class="slide-label">Misty Mountain Cadillav Harness</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="np-carousel-card">
|
||||
<div>
|
||||
<img src="https://outdoorgearlab-mvnab3pwrvp3t0.stackpathdns.com/photos/18/97/311203_1848_XXXL.jpg" alt="Featured Photography" class='customer-carousel-image'/>
|
||||
<div class="slide-label">Gri-Gri Belay Device</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="np-carousel-card">
|
||||
<div>
|
||||
<img src="https://lcdn.sportiva.com/pub/media/catalog/product/8/0/800_yellow_katanalace_1_7_4.jpg" alt="Featured Photography" class='customer-carousel-image'/>
|
||||
<div class="slide-label">La Sportiva Katana Lace Shoes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="np-carousel-card">
|
||||
<div>
|
||||
<img src="https://s3.amazonaws.com/static.northpass.com/demos/hsbc-old-logo.png" alt="Featured Photography" class='customer-carousel-image'/>
|
||||
<div class="slide-label">Metolious Helmet</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="np-carousel-card">
|
||||
<div>
|
||||
<img src="https://s3.amazonaws.com/static.northpass.com/demos/sandia-labs-logo.jpg" alt="Featured Photography" class='customer-carousel-image'/>
|
||||
<div class="slide-label">Unknown</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
|
||||
$("#featured-photography-carousel").slick({
|
||||
slidesToShow: 3.5,
|
||||
prevArrow: '<i class="fal fa-chevron-left"></i>',
|
||||
nextArrow: '<i class="fal fa-chevron-right"></i>',
|
||||
infinite: false,
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1350,
|
||||
settings: {
|
||||
slidesToShow: 2.3,
|
||||
}
|
||||
},
|
||||
{
|
||||
breakpoint: 1024,
|
||||
settings: {
|
||||
slidesToShow: 2,
|
||||
}
|
||||
},
|
||||
{
|
||||
breakpoint: 768,
|
||||
settings: {
|
||||
slidesToShow: 1,
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.featured-photography-carousel .fa-chevron-right::before {
|
||||
right: -6px !important;
|
||||
}
|
||||
.customer-carousel-image {
|
||||
width: 292px;
|
||||
height: 292px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.featured-photography-carousel .slick-initialized .slick-slide {
|
||||
margin: 10px 10px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,39 @@
|
||||
{% include "header" %}
|
||||
{% include "course_version_outdated_alert", courses: courses.in_catalog %}
|
||||
<div class="np-homepage-hero">
|
||||
<div class="np-homepage-hero-image">
|
||||
</div>
|
||||
<div class="np-homepage-hero-content">
|
||||
<div class="np-homepage-headline np-header-font-color">
|
||||
Menlo Security Resrouces
|
||||
</div>
|
||||
<div class="np-homepage-subheadline np-header-font-color">
|
||||
Resources to help you make the most of your Security Training
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "sub_navigation" %}
|
||||
{% include "courses_catalog" %}
|
||||
</main>
|
||||
{% include "footer" %}
|
||||
|
||||
<style>
|
||||
.np-card-content-description {
|
||||
height: 100px;
|
||||
}
|
||||
.np-card-content-title {
|
||||
height: 58px;
|
||||
overflow: clip;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
[].slice.call(document.getElementsByClassName("np-card-content-description")).forEach(function(element) {
|
||||
if(element.innerText.length > 150){
|
||||
element.innerText = element.innerText.slice(0,150) + '...'
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,33 @@
|
||||
{% include 'header' %}
|
||||
<main class="np-main np-homepage">
|
||||
<div class="np-homepage-hero">
|
||||
<h1 class="community-headline">Explore</h1>
|
||||
</div>
|
||||
{% include 'sub_navigation' %}
|
||||
<div class='discussion-container' style="border: 18px solid rgb(0, 0, 0); overflow: hidden; margin-left: 100px; margin-top: 35px; width: 1065px;">
|
||||
<iframe scrolling="yes" src="https://www.mountainproject.com/" style="border: 0px none; margin-left: 15px auto; height: 1954px; margin-top: -100px; width: 1060px;"></iframe>
|
||||
</div>
|
||||
</main>
|
||||
{% include 'footer' %}
|
||||
|
||||
<style>
|
||||
.discussion-frame {
|
||||
min-width: 100%;
|
||||
margin-left: -10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.discussion-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.community-headline {
|
||||
font-size: 32px;
|
||||
line-height: 48px;
|
||||
font-family: "Raleway", "Helvetic", "Arial" sans-serif;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
||||
13
app/templates/cmtest.html
Normal file
13
app/templates/cmtest.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
{% include 'head.html' %}
|
||||
{% include 'header.html' %}
|
||||
{% include 'logo.html' %}
|
||||
<p> </p>
|
||||
{% if error %}
|
||||
<h3>{{ error }}</h4>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST" action="/">
|
||||
{{ form.name.label }} {{ form.name(size=20) }}
|
||||
<input type="submit" value="Go">
|
||||
</form>
|
||||
@ -17,8 +17,8 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename="css/styles.css") }}" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/WebCoder49/code-input@1.2/code-input.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/WebCoder49/code-input@1.2/code-input.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/gh/WebCoder49/code-input@1.2.2/code-input.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/WebCoder49/code-input@1.2.2/code-input.min.css">
|
||||
<link href="{{ url_for('static', filename='css/prism.css')}}" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<p>
|
||||
</p>
|
||||
<form action="{{ url_for("ask_key")}}" method="post">
|
||||
<input id="fields" type="text" name="apikey">
|
||||
<input id="fields" type="password" name="apikey">
|
||||
<input id="fields" type="submit" value="Submit">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -6,6 +6,17 @@
|
||||
{% if error %}
|
||||
<h3>{{ error }}</h4>
|
||||
{% endif %}
|
||||
|
||||
{% if button %}
|
||||
<br>
|
||||
<form
|
||||
id="undo_template"
|
||||
method="post"
|
||||
action="{{ url_for('undo_template')}}">
|
||||
<input type="submit" name="undo_template" value="Undo Last Upload"></input>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if templates %}
|
||||
<h2> Here are the liquid templates for </h2>
|
||||
<h2 style="color:#F05323"><strong> {{ session.school }} </strong></h2>
|
||||
@ -18,10 +29,9 @@
|
||||
id="templates"
|
||||
action="{{ url_for('templates')}}"
|
||||
method="post">
|
||||
<h2>
|
||||
{{ templates[0] }}
|
||||
</h2>
|
||||
<code-input
|
||||
<h2> {{ templates[0] }}</h2>
|
||||
<h3> Last Updated: {{ templates [2] }}</h3>
|
||||
<code-input
|
||||
lang="HTML"
|
||||
value="{{ templates[1] }}"
|
||||
id="editor"
|
||||
@ -29,10 +39,6 @@
|
||||
template="code-input">
|
||||
{{ templates[1] }}
|
||||
</code-input>
|
||||
<textarea
|
||||
name="loaded-content"
|
||||
id="loaded-content"
|
||||
style="width:100%;"></textarea>
|
||||
<label for="template_name">
|
||||
Create New Template Name (optional):
|
||||
</label>
|
||||
@ -42,12 +48,6 @@
|
||||
value="{{ templates[0] }}">
|
||||
</input>
|
||||
<p> </p>
|
||||
<div
|
||||
name="load-template"
|
||||
value="Load Changes"
|
||||
onclick="copyToTextArea()"
|
||||
style="cursor:pointer"
|
||||
>Click here to load changes</div>
|
||||
<input
|
||||
type="submit"
|
||||
name="submit-template"
|
||||
@ -58,7 +58,7 @@
|
||||
</div>
|
||||
<p> </p>
|
||||
{% endfor %}
|
||||
<!--
|
||||
{% if templates %}
|
||||
<h3> Advanced users only: create new template </h3>
|
||||
<p> </p>
|
||||
<div class="html_code">
|
||||
@ -69,12 +69,12 @@
|
||||
<h2>
|
||||
Enter the code below
|
||||
</h2>
|
||||
<textarea
|
||||
id="body"
|
||||
name="body"
|
||||
rows="35"
|
||||
cols="100">
|
||||
</textarea>
|
||||
<code-input
|
||||
lang="HTML"
|
||||
id="editor"
|
||||
name="body"
|
||||
template="code-input">
|
||||
</code-input>
|
||||
<p> </p>
|
||||
<label for="template_name">
|
||||
Template Name:
|
||||
@ -86,7 +86,8 @@
|
||||
<p> </p>
|
||||
<input type="submit" name="submit_template" value="Submit Template"</input>
|
||||
</form>
|
||||
</div> -->
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
codeInput.registerTemplate("code-input", codeInput.templates.hljs(hljs,
|
||||
@ -96,13 +97,3 @@
|
||||
<script>
|
||||
codeInput.registerTemplate("code-input", codeInput.templates.prism(Prism, []));
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function copyToTextArea() {
|
||||
var code = document.getElementById("editor").value;
|
||||
let textarea = document.getElementById("loaded-content");
|
||||
textarea.textContent = code;
|
||||
console.log(code);
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user