init commit
This commit is contained in:
parent
ae639426d0
commit
7a491681cc
257 changed files with 95524 additions and 80 deletions
88
templates/account-channels.pug
Normal file
88
templates/account-channels.pug
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
if !loggedIn
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-danger.messagebox.center
|
||||
strong Authorization Required
|
||||
p You must be <a href="/login">logged in</a> to view this page.
|
||||
else
|
||||
.col-lg-6.col-md-6
|
||||
h3 My Channels
|
||||
if deleteChannelError
|
||||
.alert.alert-danger.center.messagebox
|
||||
strong Channel Deletion Failed
|
||||
p= deleteChannelError
|
||||
if channels.length == 0
|
||||
.center
|
||||
strong You haven't registered any channels
|
||||
else
|
||||
table.table.table-bordered
|
||||
thead
|
||||
tr
|
||||
th Channel
|
||||
tbody
|
||||
for c in channels
|
||||
tr
|
||||
th
|
||||
form.form-inline.pull-right(action="/account/channels", method="post", onsubmit="return confirm('Are you sure you want to delete " +c.name+ "? This cannot be undone');")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
input(type="hidden", name="action", value="delete_channel")
|
||||
input(type="hidden", name="name", value=c.name)
|
||||
button.btn.btn-xs.btn-danger(type="submit") Delete
|
||||
span.glyphicon.glyphicon-trash
|
||||
a(href=`/${channelPath}/${c.name}`, style="margin-left: 5px")= c.name
|
||||
.col-lg-6.col-md-6
|
||||
h3 Register a new channel
|
||||
if newChannelError
|
||||
.alert.alert-danger.messagebox.center
|
||||
strong Channel Registration Failed
|
||||
p= newChannelError
|
||||
form(action="/account/channels", method="post")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
input(type="hidden", name="action", value="new_channel")
|
||||
.form-group
|
||||
label.control-label(for="channelname") Channel URL
|
||||
.input-group
|
||||
span.input-group-addon #{baseUrl}/#{channelPath}/
|
||||
input#channelname.form-control(type="text", name="name", maxlength="30", onkeyup="checkChannel()")
|
||||
p#validate_channel.text-danger.pull-right
|
||||
button#register.btn.btn-primary.btn-block(type="submit") Register
|
||||
|
||||
append footer
|
||||
script(type='text/javascript').
|
||||
function checkChannel(){
|
||||
function nameIsInvalid(id){
|
||||
if(/\s/.test(id)){
|
||||
return 'Channel URL may not contain spaces';
|
||||
}
|
||||
if(id === ''){
|
||||
return 'Channel URL must not be empty';
|
||||
}
|
||||
if(!/^[\w-]{1,30}$/.test(id)){
|
||||
return 'Channel URL may only consist of a-z, A-Z, 0-9, - and _';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var box = $("#channelname");
|
||||
var value = box.val();
|
||||
var lastkey = Date.now();
|
||||
box.data("lastkey", lastkey);
|
||||
|
||||
setTimeout(function () {
|
||||
if (box.data("lastkey") !== lastkey || box.val() !== value) {
|
||||
return;
|
||||
}
|
||||
if(nameIsInvalid(value)){
|
||||
$('#validate_channel').text(nameIsInvalid(value))
|
||||
.parent().addClass('has-error').removeClass('has-success');
|
||||
$('#register').addClass('disabled');
|
||||
} else {
|
||||
$('#validate_channel').text('')
|
||||
.parent().addClass('has-success').removeClass('has-error');
|
||||
$('#register').removeClass('disabled');
|
||||
}
|
||||
}, 200);
|
||||
|
||||
}
|
||||
51
templates/account-delete.pug
Normal file
51
templates/account-delete.pug
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
if internalError
|
||||
h2 Error
|
||||
p
|
||||
| Your account deletion request could not be processed due to an internal
|
||||
| error. Please try again later and ask an administrator for assistance
|
||||
| if the problem persists.
|
||||
else if !loggedIn
|
||||
h2 Authentication Required
|
||||
p
|
||||
| You must
|
||||
a(href="/login") log in
|
||||
| before requesting deletion of your account.
|
||||
else if authFailed
|
||||
h2 Authentication failed
|
||||
p= reason
|
||||
else if channelCount > 0
|
||||
h2 Delete Account
|
||||
p
|
||||
| Your account cannot be deleted because you have one or more channels
|
||||
| registered. In order to delete your account, you must first
|
||||
a(href="/account/channels") delete them
|
||||
| or ask an administrator to transfer ownership of these channels
|
||||
| to another account.
|
||||
else
|
||||
h2 Delete Account
|
||||
p
|
||||
strong Submitting this form will initiate permanent deletion of your account.
|
||||
| After 7 days, your account will be permanently deleted and unrecoverable.
|
||||
| During this time, you will not be able to log in, but you can ask an
|
||||
| administrator to restore your account if the deletion was requested in error.
|
||||
| Please confirm your password to continue.
|
||||
form(action="/account/delete", method="post")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
.form-group(class=wrongPassword ? "has-error" : "")
|
||||
label.control-label(for="password") Password
|
||||
input#password.form-control(type="password", name="password")
|
||||
if wrongPassword
|
||||
p.text-danger.
|
||||
Password was incorrect
|
||||
.checkbox
|
||||
label
|
||||
input#confirm-delete(type="checkbox", name="confirmed")
|
||||
| I acknowledge that by submitting this request, my account will be permanently deleted unrecoverably
|
||||
if missingConfirmation
|
||||
p.text-danger.
|
||||
You must check the box to confirm you want to delete your account
|
||||
button.btn.btn-danger.btn-block(type="submit") Delete Account
|
||||
11
templates/account-deleted.pug
Normal file
11
templates/account-deleted.pug
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
h2 Account Deleted
|
||||
p.
|
||||
Your account has been flagged for deletion. After 7 days, your user data
|
||||
will be premanently deleted from the database. During this time, you will
|
||||
not be able to log in, but you can ask an administrator for help if your
|
||||
deletion request was in error. After 7 days, your account will no longer
|
||||
be recoverable.
|
||||
83
templates/account-edit.pug
Normal file
83
templates/account-edit.pug
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
if !loggedIn
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-danger.messagebox.center
|
||||
strong Authorization Required
|
||||
p You must be <a href="/login">logged in</a> to view this page.
|
||||
else
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
if successMessage
|
||||
.alert.alert-success.center
|
||||
p= successMessage
|
||||
else if errorMessage
|
||||
.alert.alert-danger.center
|
||||
p= errorMessage
|
||||
h3 Change Password
|
||||
form(action="/account/edit", method="post", onsubmit="return validatePasswordChange()")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
input(type="hidden", name="action", value="change_password")
|
||||
.form-group
|
||||
label.control-label(for="username") Username
|
||||
input#username.form-control(type="text", name="name", value=loginName, disabled=true)
|
||||
.form-group
|
||||
label.control-label(for="oldpassword") Current Password
|
||||
input#oldpassword.form-control(type="password", name="oldpassword")
|
||||
.form-group
|
||||
label.control-label(for="newpassword") New Password
|
||||
input#newpassword.form-control(type="password", name="newpassword")
|
||||
.form-group
|
||||
label.control-label(for="newpassword_confirm") Confirm New Password
|
||||
input#newpassword_confirm.form-control(type="password", name="newpassword_confirm")
|
||||
button#changepassbtn.btn.btn-danger.btn-block(type="submit") Change Password
|
||||
hr
|
||||
h3 Change Email
|
||||
form(action="/account/edit", method="post", onsubmit="return submitEmail()")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
input(type="hidden", name="action", value="change_email")
|
||||
.form-group
|
||||
label.control-label(for="username2") Username
|
||||
input#username2.form-control(type="text", name="name", value=loginName, disabled=true)
|
||||
.form-group
|
||||
label.control-label(for="password2") Password
|
||||
input#password2.form-control(type="password", name="password")
|
||||
.form-group
|
||||
label.control-label(for="email") New Email
|
||||
input#email.form-control(type="email", name="email")
|
||||
button#changeemailbtn.btn.btn-danger.btn-block(type="submit") Change Email
|
||||
|
||||
append footer
|
||||
script(type="text/javascript").
|
||||
function validatePasswordChange() {
|
||||
var pw = $("#newpassword").val();
|
||||
var pwc = $("#newpassword_confirm").val();
|
||||
$("#passwordempty").remove();
|
||||
$("#passwordmismatch").remove();
|
||||
|
||||
if (pw === '') {
|
||||
$("#newpassword").parent().addClass("has-error");
|
||||
$("<p/>").addClass("text-danger")
|
||||
.attr("id", "passwordempty")
|
||||
.text("Password must not be empty")
|
||||
.insertAfter($("#newpassword"));
|
||||
return false;
|
||||
} else {
|
||||
if (pw !== pwc) {
|
||||
$("#newpassword_confirm").parent().addClass("has-error");
|
||||
$("#newpassword").parent().addClass("has-error");
|
||||
$("<p/>").addClass("text-danger")
|
||||
.attr("id", "passwordmismatch")
|
||||
.text("Passwords do not match")
|
||||
.insertAfter($("#newpassword_confirm"));
|
||||
return false;
|
||||
} else {
|
||||
$("#username").attr("disabled", false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
function submitEmail() {
|
||||
$("#username2").attr("disabled", false);
|
||||
return true;
|
||||
}
|
||||
16
templates/account-passwordrecover.pug
Normal file
16
templates/account-passwordrecover.pug
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
h3 Recover Password
|
||||
if recovered
|
||||
.alert.alert-success.center.messagebox
|
||||
strong Your password has been changed
|
||||
p Your account has been assigned the temporary password <code>#{recoverPw}</code>. You may now use this password to log in and choose a new password by visiting the <a href="/account/edit">change password/email</a> page.
|
||||
else if confirm
|
||||
form(role="form", method="POST")
|
||||
button.btn.btn-primary.btn-block(type="submit") Click here to reset password
|
||||
else
|
||||
.alert.alert-danger.center.messagebox
|
||||
strong Password recovery failed
|
||||
p= recoverErr
|
||||
22
templates/account-passwordreset.pug
Normal file
22
templates/account-passwordreset.pug
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
h3 Reset Password
|
||||
if reset
|
||||
.alert.alert-success.center.messagebox
|
||||
strong Password reset request sent
|
||||
p Please check #{resetEmail} for your recovery link.
|
||||
else if resetErr
|
||||
.alert.alert-danger.center.messagebox
|
||||
strong Error
|
||||
p= resetErr
|
||||
form(action="/account/passwordreset", method="post", role="form")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
.form-group
|
||||
label.control-label(for="username") Username
|
||||
input#username.form-control(type="text", name="name")
|
||||
.form-group
|
||||
label.control-label(for="email") Email address
|
||||
input#email.form-control(type="email", name="email")
|
||||
button.btn.btn-primary.btn-block(type="submit") Send reset request
|
||||
62
templates/account-profile.pug
Normal file
62
templates/account-profile.pug
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
if !loggedIn
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-danger.messagebox.center
|
||||
strong Authorization Required
|
||||
p You must be <a href="/login">logged in</a> to view this page.
|
||||
else
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
h3 Profile
|
||||
if profileError
|
||||
.alert.alert-danger.center.messagebox
|
||||
strong Profile Error
|
||||
p= profileError
|
||||
.profile-box.linewrap(style="position: inherit; z-index: auto;")
|
||||
img.profile-image(src=profileImage)
|
||||
strong= loginName
|
||||
p= profileText
|
||||
h3 Edit Profile
|
||||
form(action="/account/profile", method="post", role="form")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
.form-group
|
||||
label.control-label(for="profileimage") Image
|
||||
input#profileimage.form-control(type="text", name="image", maxlength="255")
|
||||
.form-group
|
||||
label.control-label(for="profiletext") Text
|
||||
textarea#profiletext.form-control(cols="10", name="text", maxlength="255")= profileText
|
||||
button.btn.btn-primary.btn-block(type="submit") Save
|
||||
|
||||
append footer
|
||||
script(type="text/javascript").
|
||||
var $profileImage = $("#profileimage");
|
||||
$profileImage.val("#{profileImage}");
|
||||
var hasError = false;
|
||||
function validateImage() {
|
||||
var value = $profileImage.val().trim();
|
||||
$profileImage.val(value);
|
||||
if (!/^$|^https:/.test(value)) {
|
||||
hasError = true;
|
||||
$profileImage.parent().addClass("has-error");
|
||||
var $error = $("#profileimage-error");
|
||||
if ($error.length === 0) {
|
||||
$error = $("<p/>")
|
||||
.attr({ id: "profileimage-error" })
|
||||
.addClass("text-danger")
|
||||
.html("Profile image must be a URL beginning with <code>https://</code>")
|
||||
.insertAfter($profileImage);
|
||||
}
|
||||
} else {
|
||||
hasError = false;
|
||||
$profileImage.parent().removeClass("has-error");
|
||||
$("#profileimage-error").remove();
|
||||
}
|
||||
}
|
||||
|
||||
$("form").submit(function (event) {
|
||||
validateImage();
|
||||
if (hasError) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
131
templates/acp.pug
Normal file
131
templates/acp.pug
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
doctype html
|
||||
html(lang="en")
|
||||
head
|
||||
include head
|
||||
+head()
|
||||
link(rel="stylesheet", type="text/css", href="/css/acp.css")
|
||||
body
|
||||
#wrap
|
||||
nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation")
|
||||
include nav
|
||||
+navheader()
|
||||
#nav-collapsible.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav
|
||||
+navdefaultlinks()
|
||||
li#nav-acp-section.dropdown
|
||||
a#nav-acp-dd-toggle.dropdown-toggle(data-toggle="dropdown", href="javascript:void(0)") Menu
|
||||
span.caret
|
||||
ul.dropdown-menu
|
||||
+navloginlogout()
|
||||
section#mainpage
|
||||
.container
|
||||
.row
|
||||
#acp-logview.acp-panel.col-md-12(style="display: none")
|
||||
h3 Log Viewer
|
||||
.input-group
|
||||
div.input-group-btn
|
||||
button#acp-syslog-btn.btn.btn-default Syslog
|
||||
button#acp-errlog-btn.btn.btn-default Error log
|
||||
button#acp-httplog-btn.btn.btn-default HTTP log
|
||||
input#acp-chanlog-name.form-control(type="text", placeholder="Channel name")
|
||||
pre#acp-log
|
||||
#acp-announcements.acp-panel.col-md-6.col-md-offset-3(style="display: none")
|
||||
h3 Announcements
|
||||
h3 New Announcement
|
||||
div
|
||||
form.form-horizontal(action="javascript:void(0)", role="form")
|
||||
.form-group
|
||||
label.control-label.col-sm-2(for="acp-announce-title") Title
|
||||
.col-sm-10
|
||||
input#acp-announce-title.form-control(type="text")
|
||||
.form-group
|
||||
label.control-label.col-sm-2(for="acp-announce-content") Text
|
||||
.col-sm-10
|
||||
textarea#acp-announce-content.form-control(type="text", rows="10")
|
||||
.form-group
|
||||
.col-sm-10.col-sm-offset-2
|
||||
button#acp-announce-submit.btn.btn-primary Announce
|
||||
#acp-global-bans.acp-panel.col-md-12(style="display: none")
|
||||
h3 Global Bans
|
||||
table.table.table-striped.table-bordered
|
||||
thead
|
||||
tr
|
||||
th
|
||||
th IP Address
|
||||
th Note
|
||||
h3 New Global Ban
|
||||
div(style="max-width: 50%")
|
||||
form.form-horizontal(action="javascript:void(0)", role="form")
|
||||
.form-group
|
||||
label.control-label.col-sm-3(for="acp-gban-ip") IP Address
|
||||
.col-sm-9
|
||||
input#acp-gban-ip.form-control(type="text")
|
||||
.form-group
|
||||
label.control-label.col-sm-3(for="acp-gban-note") Note
|
||||
.col-sm-9
|
||||
input#acp-gban-note.form-control(type="text")
|
||||
.form-group
|
||||
.col-sm-9.col-sm-offset-3
|
||||
button#acp-gban-submit.btn.btn-danger Add ban
|
||||
#acp-user-lookup.acp-panel.col-md-12(style="display: none")
|
||||
h3 Users
|
||||
.input-group(style="max-width: 50%")
|
||||
input#acp-ulookup-query.form-control(type="text")
|
||||
span.input-group-btn
|
||||
button#acp-ulookup-btn-name.btn.btn-default(data-field="name") Search Name
|
||||
span.input-group-btn
|
||||
button#acp-ulookup-btn-email.btn.btn-default(data-field="email") Search Email
|
||||
table.table.table-bordered.table-striped(style="margin-top: 20px")
|
||||
thead
|
||||
tr
|
||||
th.sort(data-key="id") ID
|
||||
th.sort(data-key="name") Name
|
||||
th.sort(data-key="global_rank") Rank
|
||||
th.sort(data-key="email") Email
|
||||
th Actions
|
||||
#acp-channel-lookup.acp-panel.col-md-12(style="display: none")
|
||||
h3 Channels
|
||||
form.form-inline(action="javascript:void(0)", role="form")
|
||||
.form-group
|
||||
input#acp-clookup-value.form-control(type="text", placeholder="Name")
|
||||
.form-group
|
||||
select#acp-clookup-field.form-control
|
||||
option(value="name") Channel Name
|
||||
option(value="owner") Channel Owner
|
||||
button#acp-clookup-submit.btn.btn-default Search
|
||||
table.table.table-bordered.table-striped(style="margin-top: 20px")
|
||||
thead
|
||||
tr
|
||||
th.sort(data-key="id") ID
|
||||
th.sort(data-key="name") Name
|
||||
th.sort(data-key="owner") Owner
|
||||
th.sort(data-key="last_loaded") Last Loaded
|
||||
th.sort(data-key="owner_last_seen") Owner Last Seen
|
||||
th Control
|
||||
#acp-loaded-channels.acp-panel.col-md-12(style="display: none")
|
||||
h3 Loaded Channels
|
||||
button#acp-lchannels-refresh.btn.btn-default Refresh
|
||||
table.table.table-bordered.table-striped(style="margin-top: 20px")
|
||||
thead
|
||||
tr
|
||||
th Title
|
||||
th Usercount
|
||||
th Now Playing
|
||||
th Registered
|
||||
th Public
|
||||
th Control
|
||||
#acp-eventlog.acp-panel.col-md-12(style="display: none")
|
||||
h3 Event Log
|
||||
strong Filter event types
|
||||
select#acp-eventlog-filter.form-control(multiple="multiple", style="max-width: 25%")
|
||||
button#acp-eventlog-refresh.btn.btn-default Refresh
|
||||
pre#acp-eventlog-text
|
||||
|
||||
include footer
|
||||
+footer()
|
||||
script(src=sioSource)
|
||||
script(type="text/javascript").
|
||||
window.IO_SERVERS = !{ioServers};
|
||||
script(src="/js/util.js")
|
||||
script(src="/js/paginator.js")
|
||||
script(src="/js/acp.js")
|
||||
262
templates/channel.pug
Normal file
262
templates/channel.pug
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
doctype html
|
||||
html(lang="en")
|
||||
head
|
||||
include head
|
||||
+head()
|
||||
link(href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css", rel="stylesheet")
|
||||
link(rel="stylesheet", href="/css/video-js.css")
|
||||
link(rel="stylesheet", href="/css/videojs-resolution-switcher.css")
|
||||
body
|
||||
#wrap
|
||||
nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation")
|
||||
include nav
|
||||
+navheader()
|
||||
#nav-collapsible.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav
|
||||
+navdefaultlinks()
|
||||
li: a(href="javascript:void(0)", onclick="javascript:showUserOptions()") Options
|
||||
li: a#showchansettings(href="javascript:void(0)", onclick="javascript:showChannelSettings()") Channel Settings
|
||||
li.dropdown
|
||||
a.dropdown-toggle(href="#", data-toggle="dropdown") Layout
|
||||
b.caret
|
||||
ul.dropdown-menu
|
||||
li: a(href="#" onclick="javascript:chatOnly()") Chat Only
|
||||
li: a(href="#" onclick="javascript:removeVideo(event)") Remove Video
|
||||
+navsuperadmin(true)
|
||||
+navloginlogout()
|
||||
section#mainpage
|
||||
.container
|
||||
#motdrow.row
|
||||
.col-lg-12.col-md-12
|
||||
#motdwrap.well
|
||||
button#togglemotd.close.pull-right(type="button")
|
||||
span.glyphicon.glyphicon-minus
|
||||
#motd
|
||||
.clear
|
||||
#announcements.row
|
||||
#drinkbarwrap.row
|
||||
#drinkbar.col-lg-12.col-md-12
|
||||
h1#drinkcount
|
||||
#main.row
|
||||
#chatwrap.col-lg-5.col-md-5
|
||||
#chatheader
|
||||
i#userlisttoggle.glyphicon.glyphicon-chevron-down.pull-left.pointer(title="Show/Hide Userlist")
|
||||
span#usercount.pointer Not Connected
|
||||
span#modflair.label.label-default.pull-right.pointer Name Color
|
||||
#userlist
|
||||
#messagebuffer.linewrap
|
||||
form(action="javascript:void(0)")
|
||||
input#chatline.form-control(type="text", maxlength="320", style="display: none")
|
||||
#guestlogin.input-group
|
||||
span.input-group-addon Guest login
|
||||
input#guestname.form-control(type="text", placeholder="Name")
|
||||
#videowrap.col-lg-7.col-md-7
|
||||
p#videowrap-header
|
||||
span#resize-video-smaller.glyphicon.glyphicon-minus.pointer(title="Make the video smaller")
|
||||
span#resize-video-larger.glyphicon.glyphicon-plus.pointer(title="Make the video larger")
|
||||
span#currenttitle Nothing Playing
|
||||
.embed-responsive.embed-responsive-16by9
|
||||
#ytapiplayer.embed-responsive-item
|
||||
#controlsrow.row
|
||||
#leftcontrols.col-lg-5.col-md-5
|
||||
button#newpollbtn.btn.btn-sm.btn-default New Poll
|
||||
button#emotelistbtn.btn.btn-sm.btn-default Emote List
|
||||
#rightcontrols.col-lg-7.col-md-7
|
||||
#plcontrol.btn-group
|
||||
button#showsearch.btn.btn-sm.btn-default(title="Search for a video", data-toggle="collapse", data-target="#searchcontrol")
|
||||
span.glyphicon.glyphicon-search
|
||||
button#showmediaurl.btn.btn-sm.btn-default(title="Add video from URL", data-toggle="collapse", data-target="#addfromurl")
|
||||
span.glyphicon.glyphicon-plus
|
||||
button#showcustomembed.btn.btn-sm.btn-default(title="Embed a custom frame", data-toggle="collapse", data-target="#customembed")
|
||||
span.glyphicon.glyphicon-th-large
|
||||
button#showplaylistmanager.btn.btn-sm.btn-default(title="Manage playlists", data-toggle="collapse", data-target="#playlistmanager")
|
||||
span.glyphicon.glyphicon-list
|
||||
button#clearplaylist.btn.btn-sm.btn-default(title="Clear the playlist")
|
||||
span.glyphicon.glyphicon-trash
|
||||
button#shuffleplaylist.btn.btn-sm.btn-default(title="Shuffle the playlist")
|
||||
span.glyphicon.glyphicon-sort
|
||||
button#qlockbtn.btn.btn-sm.btn-danger(title="Playlist locked")
|
||||
span.glyphicon.glyphicon-lock
|
||||
#videocontrols.btn-group.pull-right
|
||||
button#mediarefresh.btn.btn-sm.btn-default(title="Reload the video player")
|
||||
span.glyphicon.glyphicon-retweet
|
||||
button#fullscreenbtn.btn.btn-sm.btn-default(title="Make the video player fullscreen")
|
||||
span.glyphicon.glyphicon-fullscreen
|
||||
button#getplaylist.btn.btn-sm.btn-default(title="Retrieve playlist links")
|
||||
span.glyphicon.glyphicon-link
|
||||
button#voteskip.btn.btn-sm.btn-default(title="Voteskip")
|
||||
span.glyphicon.glyphicon-step-forward
|
||||
|
||||
#playlistrow.row
|
||||
#leftpane.col-lg-5.col-md-5
|
||||
#leftpane-inner.row
|
||||
#pollwrap.col-lg-12.col-md-12
|
||||
#playlistmanagerwrap.col-lg-12.col-md-12
|
||||
#rightpane.col-lg-7.col-md-7
|
||||
#rightpane-inner.row
|
||||
#searchcontrol.collapse.plcontrol-collapse.col-lg-12.col-md-12
|
||||
.vertical-spacer
|
||||
.input-group
|
||||
input#library_query.form-control(type="text", placeholder="Search query")
|
||||
span.input-group-btn
|
||||
button#library_search.btn.btn-default Library
|
||||
span.input-group-btn
|
||||
button#youtube_search.btn.btn-default YouTube
|
||||
.checkbox
|
||||
label
|
||||
input.add-temp(type="checkbox")
|
||||
| Add as temporary
|
||||
ul#library.videolist.col-lg-12.col-md-12
|
||||
#addfromurl.collapse.plcontrol-collapse.col-lg-12.col-md-12
|
||||
.vertical-spacer
|
||||
.input-group
|
||||
input#mediaurl.form-control(type="text", placeholder="Media URL")
|
||||
span.input-group-btn
|
||||
button#queue_next.btn.btn-default Queue next
|
||||
span.input-group-btn
|
||||
button#queue_end.btn.btn-default Queue last
|
||||
.checkbox
|
||||
label
|
||||
input.add-temp(type="checkbox")
|
||||
| Add as temporary
|
||||
div#addfromurl-queue
|
||||
#customembed.collapse.plcontrol-collapse.col-lg-12.col-md-12
|
||||
.vertical-spacer
|
||||
.input-group
|
||||
input#customembed-title.form-control(type="text", placeholder="Title (optional)")
|
||||
span.input-group-btn
|
||||
button#ce_queue_next.btn.btn-default Queue next
|
||||
span.input-group-btn
|
||||
button#ce_queue_end.btn.btn-default Queue last
|
||||
.checkbox
|
||||
label
|
||||
input.add-temp(type="checkbox")
|
||||
| Add as temporary
|
||||
| Paste the embed code below and click Next or At End.
|
||||
| Acceptable embed codes are <code><iframe></code> and <code><object></code> tags. <strong>CUSTOM EMBEDS CANNOT BE SYNCHRONIZED.</strong>
|
||||
textarea#customembed-content.input-block-level.form-control(rows="3")
|
||||
#playlistmanager.collapse.plcontrol-collapse.col-lg-12.col-md-12
|
||||
.vertical-spacer
|
||||
.input-group
|
||||
input#userpl_name.form-control(type="text", placeholder="Playlist Name")
|
||||
span.input-group-btn
|
||||
button#userpl_save.btn.btn-default Save
|
||||
.checkbox
|
||||
label
|
||||
input.add-temp(type="checkbox")
|
||||
| Add as temporary
|
||||
ul#userpl_list.videolist
|
||||
#queuefail.col-lg-12.col-md-12
|
||||
.vertical-spacer
|
||||
.col-lg-12.col-md-12
|
||||
ul#queue.videolist
|
||||
#plmeta
|
||||
span#plcount 0 items
|
||||
span#pllength 00:00:00
|
||||
#resizewrap.row
|
||||
.col-lg-5.col-md-5
|
||||
#videowidth.col-lg-7.col-md-7
|
||||
#sitefooter
|
||||
include pagefooter
|
||||
#useroptions.modal.fade(tabindex="-1", role="dialog", aria-hidden="true")
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
button.close(data-dismiss="modal", aria-hidden="true") ×
|
||||
h4 User Preferences
|
||||
ul.nav.nav-tabs
|
||||
li: a(href="#us-general", data-toggle="tab") General
|
||||
li: a(href="#us-playback", data-toggle="tab") Playback
|
||||
li: a(href="#us-chat", data-toggle="tab") Chat
|
||||
li: a(href="#us-scriptcontrol", data-toggle="tab") Script Access
|
||||
li: a(href="#us-mod", data-toggle="tab", style="") Moderator
|
||||
.modal-body
|
||||
.tab-content
|
||||
include useroptions
|
||||
+us-general()
|
||||
+us-playback()
|
||||
+us-chat()
|
||||
+us-scripts()
|
||||
+us-mod()
|
||||
.modal-footer
|
||||
button.btn.btn-primary(type="button", data-dismiss="modal", onclick="javascript:saveUserOptions()") Save
|
||||
button.btn.btn-default(type="button", data-dismiss="modal") Close
|
||||
#emotelist.modal.fade(tabindex="-1", role="dialog", aria-hidden="true")
|
||||
.modal-dialog.modal-dialog-nonfluid
|
||||
.modal-content
|
||||
.modal-header
|
||||
button.close(data-dismiss="modal", aria-hidden="true") ×
|
||||
h4 Emote List
|
||||
.modal-body
|
||||
.pull-left
|
||||
input.emotelist-search.form-control(type="text", placeholder="Search")
|
||||
.pull-right
|
||||
.checkbox
|
||||
label
|
||||
input.emotelist-alphabetical(type="checkbox")
|
||||
| Sort alphabetically
|
||||
.emotelist-paginator-container
|
||||
table.emotelist-table
|
||||
tbody
|
||||
.modal-footer
|
||||
#channeloptions.modal.fade(tabindex="-1", role="dialog", aria-hidden="true")
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
button.close(data-dismiss="modal", aria-hidden="true") ×
|
||||
h4 Channel Settings
|
||||
ul.nav.nav-tabs
|
||||
li.active: a(href="#cs-miscoptions", data-toggle="tab") General Settings
|
||||
li: a(href="#cs-adminoptions", data-toggle="tab") Admin Settings
|
||||
li.dropdown
|
||||
a#cs-edit-dd-toggle(href="#", data-toggle="dropdown") Edit
|
||||
span.caret
|
||||
ul.dropdown-menu
|
||||
li: a(href="#cs-chatfilters", data-toggle="tab", onclick="javascript:socket.emit('requestChatFilters')") Chat Filters
|
||||
li: a(href="#cs-emotes", data-toggle="tab") Emotes
|
||||
li: a(href="#cs-motdeditor", data-toggle="tab", tabindex="-1") MOTD
|
||||
li: a(href="#cs-csseditor", data-toggle="tab", tabindex="-1") CSS
|
||||
li: a(href="#cs-jseditor", data-toggle="tab", tabindex="-1") Javascript
|
||||
li: a(href="#cs-permedit", data-toggle="tab", tabindex="-1") Permissions
|
||||
li: a(href="#cs-chanranks", data-toggle="tab", tabindex="-1", onclick="javascript:socket.emit('requestChannelRanks')") Moderators
|
||||
li: a(href="#cs-banlist", data-toggle="tab", tabindex="-1", onclick="javascript:socket.emit('requestBanlist')") Ban list
|
||||
li: a(href="#cs-chanlog", data-toggle="tab", onclick="javascript:socket.emit('readChanLog')") Log
|
||||
.modal-body
|
||||
.tab-content
|
||||
include channeloptions
|
||||
+miscoptions()
|
||||
+adminoptions()
|
||||
+motdeditor()
|
||||
+csseditor()
|
||||
+jseditor()
|
||||
+banlist()
|
||||
+recentjoins()
|
||||
+chanranks()
|
||||
+chatfilters()
|
||||
+emotes()
|
||||
+chanlog()
|
||||
+permeditor()
|
||||
.modal-footer
|
||||
button.btn.btn-default(type="button", data-dismiss="modal") Close
|
||||
#pmbar
|
||||
include footer
|
||||
+footer()
|
||||
script(id="socketio-js", src=sioSource)
|
||||
script(src="/js/data.js")
|
||||
script(src="/js/util.js")
|
||||
script(src="/js/tabcomplete.js")
|
||||
script(src="/js/player.js")
|
||||
script(src="/js/paginator.js")
|
||||
script(src="/js/ui.js")
|
||||
script(src="/js/callbacks.js")
|
||||
script(defer, src="https://www.youtube.com/iframe_api")
|
||||
script(defer, src="https://api.dmcdn.net/all.js")
|
||||
script(defer, src="https://player.vimeo.com/api/player.js")
|
||||
script(defer, src="/js/sc.js")
|
||||
script(defer, src="/js/video.js")
|
||||
script(defer, src="/js/videojs-contrib-hls.min.js")
|
||||
script(defer, src="/js/videojs-resolution-switcher.js")
|
||||
script(defer, src="/js/playerjs-0.0.12.js")
|
||||
script(defer, src="/js/dash.all.min.js")
|
||||
script(defer, src="/js/videojs-dash.js")
|
||||
script(defer, src="https://player.twitch.tv/js/embed/v1.js")
|
||||
228
templates/channeloptions.pug
Normal file
228
templates/channeloptions.pug
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
mixin lcheckbox(id, label)
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for=id)= label
|
||||
.col-sm-8
|
||||
.checkbox
|
||||
input(type="checkbox", id=id)
|
||||
|
||||
mixin rcheckbox(id, label)
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
.checkbox
|
||||
label(for=id)
|
||||
input(type="checkbox", id=id)
|
||||
= label
|
||||
|
||||
mixin textbox(id, label, placeholder)
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for=id)= label
|
||||
.col-sm-8
|
||||
if placeholder
|
||||
input.form-control(id=id, type="text", placeholder=placeholder)
|
||||
else
|
||||
input.form-control(id=id, type="text")
|
||||
|
||||
mixin lcheckbox-auto(id, label)
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for=id)= label
|
||||
.col-sm-8
|
||||
.checkbox
|
||||
input.cs-checkbox(type="checkbox", id=id)
|
||||
|
||||
mixin rcheckbox-auto(id, label)
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
.checkbox
|
||||
label(for=id)
|
||||
input.cs-checkbox(type="checkbox", id=id)
|
||||
= label
|
||||
|
||||
mixin textbox-auto(id, label, placeholder)
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for=id)= label
|
||||
.col-sm-8
|
||||
if placeholder
|
||||
input.form-control.cs-textbox(id=id, type="text", placeholder=placeholder)
|
||||
else
|
||||
input.form-control.cs-textbox(id=id, type="text")
|
||||
|
||||
mixin textbox-timeinput-auto(id, label, placeholder)
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for=id)= label
|
||||
.col-sm-8
|
||||
if placeholder
|
||||
input.form-control.cs-textbox-timeinput(id=id, type="text", placeholder=placeholder)
|
||||
else
|
||||
input.form-control.cs-textbox-timeinput(id=id, type="text")
|
||||
|
||||
mixin miscoptions
|
||||
#cs-miscoptions.tab-pane.active
|
||||
h4 General Settings
|
||||
form.form-horizontal(action="javascript:void(0)")
|
||||
+rcheckbox-auto("cs-allow_voteskip", "Allow voteskip")
|
||||
+rcheckbox-auto("cs-allow_dupes", "Allow duplicate videos on the playlist")
|
||||
+textbox-auto("cs-voteskip_ratio", "Voteskip ratio", "0.5")
|
||||
+textbox-auto("cs-maxlength", "Max video length", "HH:MM:SS")
|
||||
+textbox-timeinput-auto("cs-playlist_max_duration_per_user", "Max total queue time per user", "HH:MM:SS")
|
||||
+textbox-auto("cs-afk_timeout", "Auto-AFK Delay", "0 (disabled)")
|
||||
.form-group
|
||||
.col-sm-offset-4
|
||||
h4 Chat Settings
|
||||
form.form-horizontal(action="javascript:void(0)")
|
||||
+rcheckbox-auto("cs-enable_link_regex", "Convert URLs in chat to links")
|
||||
+rcheckbox-auto("cs-chat_antiflood", "Throttle chat")
|
||||
+textbox-auto("cs-chat_antiflood_burst", "# of messages allowed before throttling")
|
||||
+textbox-auto("cs-chat_antiflood_sustained", "# of messages (after burst) allowed per second")
|
||||
+textbox-timeinput-auto("cs-new_user_chat_delay", "Delay before new accounts can chat", "0")
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
span.text-info Restrictions to new accounts can be disabled by setting the delay to 0.
|
||||
+textbox-timeinput-auto("cs-new_user_chat_link_delay", "Delay before new accounts can post links in chat", "0")
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
span.text-info Changes are automatically saved.
|
||||
|
||||
mixin adminoptions
|
||||
#cs-adminoptions.tab-pane
|
||||
h4 Admin-Only Settings
|
||||
form.form-horizontal(action="javascript:void(0)")
|
||||
- var defname = `CyTube - /${channelPath}/${channelName}`
|
||||
+textbox-auto("cs-pagetitle", "Page title", defname)
|
||||
+textbox-auto("cs-password", "Password", "leave blank to disable")
|
||||
+textbox-auto("cs-externalcss", "External CSS", "Stylesheet URL")
|
||||
+textbox-auto("cs-externaljs", "External Javascript", "Script URL")
|
||||
+rcheckbox-auto("cs-show_public", "List channel publicly")
|
||||
+rcheckbox-auto("cs-torbanned", "Block connections from Tor")
|
||||
+rcheckbox-auto("cs-block_anonymous_users", "Block anonymous users")
|
||||
+rcheckbox-auto("cs-allow_ascii_control", "Allow ASCII control characters (e.g. newlines)")
|
||||
+textbox-auto("cs-playlist_max_per_user", "Maximum # of videos per user")
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
span.text-info Set to 0 for no limit
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
span.text-info Changes are automatically saved.
|
||||
|
||||
mixin motdeditor
|
||||
#cs-motdeditor.tab-pane
|
||||
h4 MOTD editor
|
||||
p The MOTD can be formatted using a subset of HTML. Tags which attempt to execute Javascript will be removed.
|
||||
textarea.form-control#cs-motdtext(rows="10")
|
||||
button.btn.btn-primary#cs-motdsubmit Save MOTD
|
||||
|
||||
mixin csseditor
|
||||
#cs-csseditor.tab-pane
|
||||
h4 CSS editor
|
||||
p Maximum size 20KB. If more space is required, use the External CSS option under General Settings to link to an externally hosted stylesheet.
|
||||
textarea.form-control#cs-csstext(rows="10")
|
||||
button.btn.btn-primary#cs-csssubmit Save CSS
|
||||
|
||||
mixin jseditor
|
||||
#cs-jseditor.tab-pane
|
||||
h4 JS editor
|
||||
p Maximum size 20KB. If more space is required, use the External JS option under General Settings to link to an externally hosted stylesheet.
|
||||
textarea.form-control#cs-jstext(rows="10")
|
||||
button.btn.btn-primary#cs-jssubmit Save JS
|
||||
|
||||
mixin banlist
|
||||
#cs-banlist.tab-pane
|
||||
h4 Ban list
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th Unban
|
||||
th IP
|
||||
th Name
|
||||
th Banned by
|
||||
|
||||
mixin recentjoins
|
||||
#cs-recentjoins.tab-pane
|
||||
h4 Recent connections
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th Name
|
||||
th Aliases
|
||||
th Time
|
||||
|
||||
mixin chanranks
|
||||
#cs-chanranks.tab-pane
|
||||
h4 Moderator List
|
||||
form.form-inline(action="javascript:void(0)", role="form")
|
||||
.input-group
|
||||
input#cs-chanranks-name.form-control(type="text", placeholder="Name")
|
||||
span.input-group-btn
|
||||
button#cs-chanranks-mod.btn.btn-success +Mod
|
||||
button#cs-chanranks-adm.btn.btn-info +Admin
|
||||
button#cs-chanranks-owner.btn.btn-info +Owner
|
||||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th Name
|
||||
th Rank
|
||||
|
||||
mixin chatfilters
|
||||
#cs-chatfilters.tab-pane
|
||||
h4 Chat Filters
|
||||
form.form-horizontal(action="javascript:void(0)", role="form")
|
||||
+textbox("cs-chatfilters-newname", "Filter name")
|
||||
+textbox("cs-chatfilters-newregex", "Filter regex")
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="cs-chatfilters-newflags") Flags
|
||||
.col-sm-8
|
||||
input#cs-chatfilters-newflags.form-control.cs-textbox(type="text", value="g")
|
||||
+textbox("cs-chatfilters-newreplace", "Replacement")
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
button#cs-chatfilters-newsubmit.btn.btn-primary Create Filter
|
||||
table.table.table-striped.table-condensed
|
||||
thead
|
||||
tr
|
||||
th Control
|
||||
th Name
|
||||
th Active
|
||||
|
||||
button#cs-chatfilters-export.btn.btn-default Export filter list
|
||||
button#cs-chatfilters-import.btn.btn-default Import filter list
|
||||
textarea#cs-chatfilters-exporttext.form-control(rows="5")
|
||||
|
||||
mixin emotes
|
||||
#cs-emotes.tab-pane
|
||||
h4 Emotes
|
||||
form.form-horizontal(action="javascript:void(0)", role="form")
|
||||
+textbox("cs-emotes-newname", "Emote name")
|
||||
+textbox("cs-emotes-newimage", "Emote image")
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
button#cs-emotes-newsubmit.btn.btn-primary Create Emote
|
||||
form.form-inline
|
||||
.form-group
|
||||
input.emotelist-search.form-control(type="text", placeholder="Search")
|
||||
.form-group
|
||||
.checkbox
|
||||
label
|
||||
input.emotelist-alphabetical(type="checkbox")
|
||||
| Sort alphabetically
|
||||
.emotelist-paginator-container
|
||||
table.emotelist-table.table.table-striped.table-condensed
|
||||
thead
|
||||
tr
|
||||
th Delete
|
||||
th Name
|
||||
th Image
|
||||
tbody
|
||||
|
||||
button#cs-emotes-export.btn.btn-default Export emote list
|
||||
button#cs-emotes-import.btn.btn-default Import emote list
|
||||
textarea#cs-emotes-exporttext.form-control(rows="5")
|
||||
|
||||
mixin chanlog
|
||||
#cs-chanlog.tab-pane
|
||||
h4 Channel Log
|
||||
strong Filter Log:
|
||||
select#cs-chanlog-filter.form-control(multiple="multiple")
|
||||
pre#cs-chanlog-text
|
||||
button.btn.btn-default#cs-chanlog-refresh Refresh
|
||||
|
||||
mixin permeditor
|
||||
#cs-permedit.tab-pane
|
||||
34
templates/contact.pug
Normal file
34
templates/contact.pug
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
extends layout.pug
|
||||
|
||||
mixin email(e, k)
|
||||
button.btn.btn-xs.btn-default(onclick="showEmail(this, '"+e+"', '"+k+"')") Show Email
|
||||
|
||||
block content
|
||||
.col-md-8.col-md-offset-2
|
||||
h1 Contact
|
||||
h3 Email
|
||||
if contacts.length == 0
|
||||
p No contacts listed.
|
||||
else
|
||||
each contact in contacts
|
||||
strong= contact.name
|
||||
p.text-muted= contact.title
|
||||
+email(contact.email, contact.emkey)
|
||||
br
|
||||
hr
|
||||
|
||||
append footer
|
||||
script(type="text/javascript").
|
||||
function showEmail(btn, email, key) {
|
||||
email = unescape(email);
|
||||
key = unescape(key);
|
||||
var dest = new Array(email.length);
|
||||
for (var i = 0; i < email.length; i++) {
|
||||
dest[i] = String.fromCharCode(email.charCodeAt(i) ^ key.charCodeAt(i % key.length));
|
||||
}
|
||||
email = dest.join("");
|
||||
$("<a/>").attr("href", "mailto:" + email)
|
||||
.text(email)
|
||||
.insertBefore(btn);
|
||||
$(btn).remove();
|
||||
}
|
||||
15
templates/csrferror.pug
Normal file
15
templates/csrferror.pug
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-md-12
|
||||
.alert.alert-danger
|
||||
h1 Invalid Session
|
||||
p Your browser attempted to submit form data to <code>#{path}</code> with an invalid authentication token. This may be because:
|
||||
ul
|
||||
li Your session has expired
|
||||
li Your request was missing the authentication token
|
||||
li A malicious user has attempted to tamper with your session
|
||||
li Your browser does not support cookies, or they are not enabled
|
||||
| If the problem persists, please contact an administrator.
|
||||
if referer
|
||||
a(href=referer) Return to previous page
|
||||
10
templates/footer.pug
Normal file
10
templates/footer.pug
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
mixin footer
|
||||
footer#footer
|
||||
.container
|
||||
p.text-muted.credit.
|
||||
ourfore.st is hosted on fore.st, a fork of <a href="https://github.com/calzoneman/sync" target="_blank" rel="noreferrer noopener">CyTube</a> built for the TTN community. It's source code is available <a href="https://gitlab.com/SovietBear/fore.st" target="_blank" rel="noreferrer noopener">here</a>.
|
||||
script(src="/js/jquery-1.11.0.min.js")
|
||||
// Must be included before jQuery-UI since jQuery-UI overrides jQuery.fn.button
|
||||
// I should really abandon this crap one day
|
||||
script(src="/js/jquery-ui.js")
|
||||
script(src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js")
|
||||
47
templates/google_drive_userscript.pug
Normal file
47
templates/google_drive_userscript.pug
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-md-8.col-md-offset-2
|
||||
h1 Google Drive Userscript
|
||||
h2 Disclaimer
|
||||
.alert.alert-danger.messagebox
|
||||
strong Unsupported
|
||||
p.
|
||||
This functionality is provided <strong>as-is</strong> for backwards
|
||||
compatibility for existing users for whom it already is known to work.
|
||||
There are many reasons, known and unknown, for which it may
|
||||
<strong>not</strong> work for you; please note the staff in CyTube
|
||||
support channels cannot provide any troubleshooting assistance and you
|
||||
will be asked to simply use a different video provider.
|
||||
p.
|
||||
This functionality was originally added so that users could share their
|
||||
own personal videos stored in their Drive. No support whatsoever will
|
||||
be provided to users attempting to use it to circumvent copyright
|
||||
restrictions on third-party video hosts.
|
||||
h2 How It Works
|
||||
p.
|
||||
The userscript is a short script that you can install using a browser
|
||||
extension such as Greasemonkey or Tampermonkey that runs on the page
|
||||
and provides additional functionality needed to play Google Drive
|
||||
videos.
|
||||
h2 Installation
|
||||
ul
|
||||
li
|
||||
strong Chrome
|
||||
| —Install <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo" target="_blank">Tampermonkey</a>.
|
||||
li
|
||||
strong Firefox
|
||||
| —Install <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/" target="_blank">Tampermonkey</a>
|
||||
| or <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/" target="_blank">Greasemonkey</a>.
|
||||
li
|
||||
strong Other Browsers
|
||||
| —Install the appropriate userscript plugin for your browser.
|
||||
| Tampermonkey supports many browsers besides Chrome.
|
||||
p.
|
||||
Once you have installed the userscript manager addon for your browser,
|
||||
you can <a href="/js/cytube-google-drive.user.js" target="_blank">
|
||||
install the userscript</a>. If this link 404s, it means the administrator
|
||||
of this server hasn't generated it yet.
|
||||
p.
|
||||
You can find a guide with screenshots of the installation process
|
||||
<a href="https://github.com/calzoneman/sync/wiki/Google-Drive-Userscript-Installation-Guide" target="_blank">on GitHub</a>.
|
||||
27
templates/head.pug
Normal file
27
templates/head.pug
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
- var DEFAULT_THEME = "/css/themes/fore.st.css";
|
||||
|
||||
mixin head()
|
||||
meta(charset="utf-8")
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1.0")
|
||||
meta(name="description", content=siteDescription)
|
||||
|
||||
title= siteTitle
|
||||
link(href="/css/sticky-footer-navbar.css", rel="stylesheet")
|
||||
link(href="/css/cytube.css", rel="stylesheet")
|
||||
link(id="usertheme", href=DEFAULT_THEME, rel="stylesheet")
|
||||
|
||||
if channelName
|
||||
script(type="text/javascript").
|
||||
var DEFAULT_THEME = '#{DEFAULT_THEME}';
|
||||
var CHANNELPATH = '#{channelPath}';
|
||||
var CHANNELNAME = '#{channelName}';
|
||||
else
|
||||
script(type="text/javascript").
|
||||
var DEFAULT_THEME = '#{DEFAULT_THEME}';
|
||||
var CHANNELPATH = '#{channelPath}';
|
||||
|
||||
script(src="/js/theme.js")
|
||||
//[if lt IE 9]
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||
//[endif]
|
||||
23
templates/httperror.pug
Normal file
23
templates/httperror.pug
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
extends layout.pug
|
||||
|
||||
mixin notfound()
|
||||
h1 Not Found
|
||||
p The page you were looking for doesn't seem to exist. Please check that you typed the URL correctly.
|
||||
if message
|
||||
p Reason: #{message}
|
||||
mixin forbidden()
|
||||
h1 Forbidden
|
||||
p You don't have permission to access <code>#{path}</code>
|
||||
mixin genericerror()
|
||||
h1 Oops
|
||||
p Your request could not be processed. Status code: <code>#{status}</code>, message: <code>#{message}</code>
|
||||
|
||||
block content
|
||||
.col-md-12
|
||||
.alert.alert-danger
|
||||
if status == 404
|
||||
+notfound()
|
||||
else if status == 403
|
||||
+forbidden()
|
||||
else
|
||||
+genericerror()
|
||||
25
templates/index.pug
Normal file
25
templates/index.pug
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-lg-9.col-md-9
|
||||
h3 Public Channels
|
||||
table.table.table-bordered.table-striped
|
||||
thead
|
||||
th Channel
|
||||
th # Connected
|
||||
th Now Playing
|
||||
tbody
|
||||
each chan in channels
|
||||
tr
|
||||
td: a(href=`/${channelPath}/${chan.name}`) #{chan.pagetitle} (#{chan.name})
|
||||
td= chan.usercount
|
||||
td= chan.mediatitle
|
||||
.col-lg-3.col-md-3
|
||||
|
||||
append footer
|
||||
script(type="text/javascript").
|
||||
$("#channelname").keydown(function (ev) {
|
||||
if (ev.keyCode === 13) {
|
||||
location.href = "/#{channelPath}/" + $("#channelname").val();
|
||||
}
|
||||
});
|
||||
22
templates/layout.pug
Normal file
22
templates/layout.pug
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
doctype html
|
||||
html(lang="en")
|
||||
head
|
||||
block head
|
||||
include head
|
||||
+head()
|
||||
body
|
||||
#wrap
|
||||
nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation")
|
||||
include nav
|
||||
+navheader()
|
||||
#nav-collapsible.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav
|
||||
+navdefaultlinks()
|
||||
+navsuperadmin(false)
|
||||
+navloginlogout()
|
||||
section#mainpage
|
||||
.container
|
||||
block content
|
||||
block footer
|
||||
include footer
|
||||
+footer()
|
||||
39
templates/login.pug
Normal file
39
templates/login.pug
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
if wasAlreadyLoggedIn
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-info.messagebox.center
|
||||
h3(style="margin: 5px auto") Logged in as #{loginName}
|
||||
else if !loggedIn
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
if loginError
|
||||
.alert.alert-danger.messagebox.center
|
||||
strong Login Failed
|
||||
p= loginError
|
||||
h2 Login
|
||||
form(role="form", action="/login", method="post")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
if redirect
|
||||
input(type="hidden", name="dest", value=redirect)
|
||||
.form-group
|
||||
label(for="username") Username
|
||||
input#username.form-control(type="text", name="name")
|
||||
.form-group
|
||||
label(for="password") Password
|
||||
input#password.form-control(type="password", name="password")
|
||||
a(href="/account/passwordreset") Forgot password?
|
||||
.form-group
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox", name="remember")
|
||||
| Remember me
|
||||
button.btn.btn-success.btn-block(type="submit") Login
|
||||
else
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-success.messagebox.center
|
||||
strong Login Successful
|
||||
p Logged in as #{loginName}
|
||||
if redirect
|
||||
br
|
||||
a(href=redirect) Return to previous page
|
||||
9
templates/logout.pug
Normal file
9
templates/logout.pug
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-info.center.messagebox
|
||||
strong Logged out
|
||||
p
|
||||
if redirect
|
||||
a(href=redirect) Return to previous page
|
||||
67
templates/nav.pug
Normal file
67
templates/nav.pug
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
mixin navheader()
|
||||
.navbar-header
|
||||
button.navbar-toggle(type="button", data-toggle="collapse", data-target="#nav-collapsible")
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
a.navbar-brand(href="/")= siteTitle
|
||||
|
||||
mixin navdefaultlinks()
|
||||
li
|
||||
a(href="/") Home
|
||||
li.dropdown
|
||||
a.dropdown-toggle(href="#", data-toggle="dropdown") Account
|
||||
b.caret
|
||||
ul.dropdown-menu
|
||||
if loggedIn
|
||||
li: a(href="javascript:$('#logoutform').submit();") Log out
|
||||
li.divider
|
||||
li: a(href="/account/channels") Channels
|
||||
li: a(href="/account/profile") Profile
|
||||
li: a(href="/account/edit") Change Password/Email
|
||||
li: a(href="/account/delete") Delete Account
|
||||
else
|
||||
li: a(href="/login") Login
|
||||
li: a(href="/register") Register
|
||||
|
||||
mixin navsuperadmin(newTab)
|
||||
if superadmin
|
||||
if newTab
|
||||
li: a(href="/acp", target="_blank") ACP
|
||||
else
|
||||
li: a(href="/acp") ACP
|
||||
|
||||
mixin navloginlogout()
|
||||
if loggedIn
|
||||
+navlogoutform()
|
||||
else
|
||||
+navloginform()
|
||||
|
||||
mixin navloginform()
|
||||
.visible-lg
|
||||
form#loginform.navbar-form.navbar-right(action="/login", method="post")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
.form-group
|
||||
input#username.form-control(type="text", name="name", placeholder="Username")
|
||||
.form-group
|
||||
input#password.form-control(type="password", name="password", placeholder="Password")
|
||||
.form-group
|
||||
.checkbox
|
||||
label
|
||||
input(type="checkbox", name="remember")
|
||||
span.navbar-text-nofloat Remember me
|
||||
button#login.btn.btn-default(type="submit") Login
|
||||
.visible-md
|
||||
p#loginform.navbar-text.pull-right
|
||||
a#login.navbar-link(href="/login") Log in
|
||||
span ·
|
||||
a#register.navbar-link(href="/register") Register
|
||||
|
||||
|
||||
mixin navlogoutform()
|
||||
form#logoutform.navbar-text.pull-right(action="/logout", method="post")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
span#welcome Welcome, #{loginName}
|
||||
span ·
|
||||
input#logout.navbar-link(type="submit", value="Log out")
|
||||
|
||||
0
templates/pagefooter.pug
Normal file
0
templates/pagefooter.pug
Normal file
121
templates/register.pug
Normal file
121
templates/register.pug
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
extends layout.pug
|
||||
|
||||
block content
|
||||
if loggedIn
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-danger.messagebox.center
|
||||
strong Already logged in
|
||||
p.
|
||||
You are already logged in. If you intend to register a new account, please <a href="/logout?redirect=/register">Logout</a> first.
|
||||
// TODO Link to My Account page
|
||||
else if !registered
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
if registerError
|
||||
.alert.alert-danger.messagebox.center
|
||||
strong Registration Failed
|
||||
p= registerError
|
||||
h2 Register
|
||||
form(role="form", action="/register", method="post", onsubmit="return verify()")
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
.form-group
|
||||
label.control-label(for="username") Username
|
||||
input#register-username.form-control(type="text", name="name", onkeyup="checkUsername()", maxlength="20")
|
||||
p#validate_username.text-danger.pull-right
|
||||
.form-group
|
||||
label.control-label(for="password") Password
|
||||
input#register-password.form-control(type="password", name="password", onkeyup="checkPasswords()")
|
||||
p#validate_password.text-danger.pull-right
|
||||
.form-group
|
||||
label.control-label(for="password_confirm") Confirm Password
|
||||
input#register-password-confirm.form-control(type="password", onkeyup="checkPasswords()")
|
||||
p#validate_confirm.text-danger.pull-right
|
||||
.form-group
|
||||
label.control-label(for="email") Email (optional)
|
||||
input#register-email.form-control(type="email", name="email")
|
||||
p#validate_email.text-danger.pull-right
|
||||
p
|
||||
| Providing an email address is optional and will allow you to recover your account via email if you forget your password.
|
||||
strong If you do not provide an email address, you will not be able to recover a lost account!
|
||||
if hCaptchaSiteKey
|
||||
noscript
|
||||
.text-danger This website requires JavaScript in order to display a CAPTCHA.
|
||||
.form-group
|
||||
div.h-captcha(data-sitekey=hCaptchaSiteKey)
|
||||
button#registerbtn.btn.btn-success.btn-block(type="submit") Register
|
||||
else
|
||||
.col-lg-6.col-lg-offset-3.col-md-6.col-md-offset-3
|
||||
.alert.alert-success.messagebox.center
|
||||
strong Registration Successful
|
||||
p Thanks for registering, #{registerName}! Now you can <a href="/login">Login</a> to use your account.
|
||||
|
||||
append footer
|
||||
if hCaptchaSiteKey
|
||||
script(src="https://hcaptcha.com/1/api.js" async defer)
|
||||
script(type="text/javascript").
|
||||
function verify() {
|
||||
var valid = checkUsername();
|
||||
valid = checkPasswords() && valid;
|
||||
valid = checkEmail() && valid;
|
||||
return valid;
|
||||
}
|
||||
function checkUsername() {
|
||||
function stateError(text){
|
||||
target.parent()
|
||||
.addClass("has-error")
|
||||
.removeClass("has-success");
|
||||
$("#validate_username").text(text);
|
||||
}
|
||||
var target = $("#register-username");
|
||||
var name = target.val();
|
||||
if (name === "") {
|
||||
stateError('Username must not be empty')
|
||||
return false;
|
||||
} else if (!(/^[-\w\u00c0-\u00ff]{1,20}$/).test(name)) {
|
||||
stateError("Username must consist of 1-20 characters" +
|
||||
" a-Z, A-Z, 0-9, -, or _.");
|
||||
return false;
|
||||
} else {
|
||||
target.parent()
|
||||
.removeClass("has-error")
|
||||
.addClass("has-success");
|
||||
$("#validate_username").text('');
|
||||
}
|
||||
}
|
||||
function checkPasswords() {
|
||||
function stateError(text, target, validator){
|
||||
target.parent()
|
||||
.addClass("has-error")
|
||||
.removeClass("has-success");
|
||||
$(`#${validator}`).text(text);
|
||||
}
|
||||
var target = $("#register-password");
|
||||
var target2 = $("#register-password-confirm");
|
||||
var pw = target.val();
|
||||
var pwc = target2.val();
|
||||
|
||||
$("#validate_password").text('');
|
||||
$("#validate_confirm").text('');
|
||||
if (pw === "") {
|
||||
stateError('Password must not be empty', target, 'validate_password')
|
||||
return false;
|
||||
} else {
|
||||
target.parent()
|
||||
.removeClass("has-error")
|
||||
.addClass("has-success");
|
||||
if (pw !== pwc) {
|
||||
stateError('Passwords do not match', target2, 'validate_confirm')
|
||||
return false;
|
||||
} else {
|
||||
target2.parent()
|
||||
.removeClass("has-error")
|
||||
.addClass("has-success");
|
||||
}
|
||||
}
|
||||
}
|
||||
function checkEmail() {
|
||||
var email = $("#register-email").val();
|
||||
if (email.trim() === "") {
|
||||
return confirm("Are you sure you want to register without setting a recovery email address? If you lose the password, or if your account is compromised, you WILL NOT be able to recover it.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
142
templates/useroptions.pug
Normal file
142
templates/useroptions.pug
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
mixin lcheckbox(id, label)
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for=id)= label
|
||||
.col-sm-8
|
||||
.checkbox
|
||||
input(type="checkbox", id=id)
|
||||
|
||||
mixin rcheckbox(id, label)
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4
|
||||
.checkbox
|
||||
label(for=id)
|
||||
input(type="checkbox", id=id)
|
||||
= label
|
||||
|
||||
mixin textbox(id, label, placeholder)
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for=id)= label
|
||||
.col-sm-8
|
||||
if placeholder
|
||||
input.form-control(id=id, type="text", placeholder=placeholder)
|
||||
else
|
||||
input.form-control(id=id, type="text")
|
||||
|
||||
mixin us-general
|
||||
#us-general.tab-pane
|
||||
h4 General Preferences
|
||||
form.form-horizontal(action="javascript:void(0)")
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="#us-theme") Theme
|
||||
.col-sm-8
|
||||
select#us-theme.form-control
|
||||
option(value="/css/themes/light.css") Light
|
||||
option(value="/css/themes/bootstrap-theme.min.css") Bootstrap
|
||||
option(value="/css/themes/slate.css") Slate
|
||||
option(value="/css/themes/cyborg.css") Cyborg
|
||||
option(value="/css/themes/modern.css") Modern
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="#us-layout") Layout
|
||||
.col-sm-8
|
||||
select#us-layout.form-control
|
||||
option(value="default") Compact
|
||||
option(value="synchtube") Synchtube (flipped)
|
||||
option(value="fluid") Fluid
|
||||
option(value="synchtube-fluid") Synchtube + Fluid
|
||||
option(value="hd") HD
|
||||
.col-sm-4
|
||||
.col-sm-8
|
||||
p.text-danger Changing layouts may require refreshing to take effect.
|
||||
+rcheckbox("us-no-channelcss", "Ignore Channel CSS")
|
||||
+rcheckbox("us-no-channeljs", "Ignore Channel Javascript")
|
||||
.clear
|
||||
|
||||
mixin us-scripts
|
||||
#us-scriptcontrol.tab-pane
|
||||
h4 Script Access
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th Channel
|
||||
th Type
|
||||
th Preference
|
||||
th Clear
|
||||
|
||||
mixin us-playback
|
||||
#us-playback.tab-pane
|
||||
h4 Playback Preferences
|
||||
form.form-horizontal(action="javascript:void(0)")
|
||||
+rcheckbox("us-synch", "Synchronize video playback")
|
||||
+textbox("us-synch-accuracy", "Synch threshold (seconds)", "2")
|
||||
+rcheckbox("us-wmode-transparent", "Set wmode=transparent")
|
||||
.form-group
|
||||
.col-sm-4
|
||||
.col-sm-8
|
||||
p.text-info Setting <code>wmode=transparent</code> allows objects to be displayed above the video player, but may cause performance issues on some systems.
|
||||
+rcheckbox("us-hidevideo", "Remove the video player")
|
||||
+rcheckbox("us-playlistbuttons", "Hide playlist buttons by default")
|
||||
+rcheckbox("us-oldbtns", "Old style playlist buttons")
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="#us-default-quality") Quality Preference
|
||||
.col-sm-8
|
||||
select#us-default-quality.form-control
|
||||
option(value="auto") Auto
|
||||
option(value="240") 240p
|
||||
option(value="360") 360p
|
||||
option(value="480") 480p
|
||||
option(value="720") 720p
|
||||
option(value="1080") 1080p
|
||||
option(value="best") Highest Available
|
||||
.form-group
|
||||
.col-sm-4
|
||||
.col-sm-8
|
||||
p.text-info Due to technical changes on YouTube's side, the CyTube quality preference can no longer be automatically applied on YouTube videos. See <a href="https://github.com/calzoneman/sync/issues/726" rel="noopener noreferer" target="_blank">this GitHub issue</a> for details.
|
||||
|
||||
mixin us-chat
|
||||
#us-chat.tab-pane
|
||||
h4 Chat Preferences
|
||||
form.form-horizontal(action="javascript:void(0)")
|
||||
+rcheckbox("us-chat-timestamp", "Show timestamps in chat")
|
||||
+rcheckbox("us-sort-rank", "Sort userlist by rank")
|
||||
+rcheckbox("us-sort-afk", "Sort AFKers to bottom")
|
||||
.col-sm-4
|
||||
.col-sm-8
|
||||
p.text-info The following 3 options apply to how and when you will be notified if a new chat message is received while CyTube is not the active window.
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="#us-blink-title") Blink page title on new messages
|
||||
.col-sm-8
|
||||
select#us-blink-title.form-control
|
||||
option(value="never") Never
|
||||
option(value="onlyping") Only when I am mentioned or PMed
|
||||
option(value="always") Always
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="#us-ping-sound") Notification sound on new messages
|
||||
.col-sm-8
|
||||
select#us-ping-sound.form-control
|
||||
option(value="never") Never
|
||||
option(value="onlyping") Only when I am mentioned or PMed
|
||||
option(value="always") Always
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="#us-notifications") Desktop notifications on new messages
|
||||
.col-sm-8
|
||||
select#us-notifications.form-control
|
||||
option(value="never") Never
|
||||
option(value="onlyping") Only when I am mentioned or PMed
|
||||
option(value="always") Always
|
||||
+rcheckbox("us-sendbtn", "Add a send button to chat")
|
||||
+rcheckbox("us-no-emotes", "Disable chat emotes")
|
||||
+rcheckbox("us-strip-image", "Remove images from chat")
|
||||
.form-group
|
||||
label.control-label.col-sm-4(for="#us-chat-tab-method") Tab completion method
|
||||
.col-sm-8
|
||||
select#us-chat-tab-method.form-control
|
||||
option(value="Cycle options") Cycle options
|
||||
option(value="Longest unique match") Longest unique match
|
||||
|
||||
mixin us-mod
|
||||
#us-mod.tab-pane
|
||||
h4 Moderator Preferences
|
||||
form.form-horizontal(action="javascript:void(0)")
|
||||
+rcheckbox("us-modflair", "Show name color")
|
||||
+rcheckbox("us-shadowchat", "Show shadowmuted messages")
|
||||
+rcheckbox("us-show-ip-in-tooltip", "Show IP addresses in profile tooltip")
|
||||
Loading…
Add table
Add a link
Reference in a new issue