532 lines
25 KiB
HTML
532 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=yes">
|
|
<meta name="description" content="CTB Recorder is a free recording software for Chaturbate">
|
|
<meta name="author" content="">
|
|
<meta name="version" content="${project.version}">
|
|
<title>CTB Recorder ${project.version}</title>
|
|
<!-- Bootstrap core CSS -->
|
|
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
|
<!-- Custom fonts for this template -->
|
|
<link href="vendor/font-awesome/css/all.min.css" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
|
|
<!-- Plugin CSS -->
|
|
<link href="vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
|
|
<link href="vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
|
|
<link id="mainTheme" href="freelancer.css" rel="stylesheet">
|
|
<!-- Flowplayer -->
|
|
<link rel="stylesheet" href="vendor/flowplayer/skin/skin.css">
|
|
<!-- custom css -->
|
|
<link rel="stylesheet" href="custom.css">
|
|
<link rel="shortcut icon" href="favicon.svg" type="image/x-icon" />
|
|
<style>
|
|
.ui-front {
|
|
z-index: 2000;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body id="page-top">
|
|
<!-- Navigation -->
|
|
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav">
|
|
<div class="container">
|
|
<a class="navbar-brand js-scroll-trigger" href="index.html">
|
|
<img src="ctbrec.svg" alt="Logo" height="56" />
|
|
</a>
|
|
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> Menu <i class="fa fa-bars"></i>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
|
<ul class="navbar-nav nav ml-auto" role="tablist">
|
|
<li class="nav-item mx-0 mx-lg-1">
|
|
<a id="models-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded active" href="#models" role="tab" aria-controls="models" aria-selected="true">Models</a>
|
|
</li>
|
|
<li class="nav-item mx-0 mx-lg-1">
|
|
<a id="recordings-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#recordings" role="tab" aria-controls="recordings" aria-selected="false">Recordings</a>
|
|
</li>
|
|
<li class="nav-item mx-0 mx-lg-1">
|
|
<a id="configuration-tab" data-toggle="tab" class="nav-link py-3 px-0 px-lg-3 rounded" href="#configuration" role="tab" aria-controls="settings" aria-selected="false">Settings</a>
|
|
</li>
|
|
<li class="nav-item mx-0 mx-lg-1">
|
|
<a id="help-page" class="nav-link py-3 px-0 px-lg-3 rounded" href="../docs/index.md" target="_blank" aria-controls="help" aria-selected="false">Help</a>
|
|
</li>
|
|
<li class="mx-0 mx-lg-1">
|
|
<a id="dark-mode-toggle" class="nav-link py-3 px-0 px-lg-3 rounded fa fa-moon" href="#configuration" role="tab" aria-controls="settings" aria-selected="false"></a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<div id="alert-container"></div>
|
|
<!-- Contact sheet modal -->
|
|
<div class="modal fade" id="contactsheetModal" tabindex="-1" aria-labelledby="contactsheetModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog" style="max-width: 80vw;">
|
|
<div class="modal-content modal-content-contactsheet">
|
|
<div class="modal-header">
|
|
<h6 class="modal-title title-truncate" id="contactsheetModalLabel">Image Title</h6>
|
|
<button type="button" class="btn btn-danger fa fa-times-circle" data-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body text-center">
|
|
<img id="imageModalImage" src="" alt="Contact Sheet" class="img-fluid" style="max-width: 100%; height: auto;" />
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="deleteRecordingBtn" type="button" class="btn btn-danger">
|
|
<i class="fa fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tab-content" id="myTabContent">
|
|
<section id="models" class="tab-pane fade active show" role="tabpanel" aria-labelledby="models-tab">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<div class="form-group">
|
|
<label for="addModelByUrl">Add Model</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="addModelByUrl" placeholder="e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/" onKeyUp="addModelKeyPressed(event)">
|
|
<div class="input-group-append">
|
|
<button class="btn btn-outline-secondary" type="button" id="toggleStopAtOptions">
|
|
<i class="fa fa-clock"></i>
|
|
</button>
|
|
<button class="btn btn-primary" type="button" onclick="addModel()"> Add </button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Stop At Options - Initially hidden -->
|
|
<div class="row" id="stopAtOptionsRow" style="display: none;">
|
|
<div class="col-lg-10 mx-auto">
|
|
<div class="form-group">
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<span class="input-group-text">Stop at</span>
|
|
</div>
|
|
<input type="datetime-local" class="form-control" id="recordUntilDate">
|
|
<div class="input-group-append">
|
|
<select class="form-control" id="recordUntilAction">
|
|
<option value="REMOVE">REMOVE</option>
|
|
<option value="PAUSE">PAUSE</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<p class="lead"></p>
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-hover table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<a href="#" data-bind="orderable: {collection: 'models', field: 'ko_name'}">Model</a>
|
|
</th>
|
|
<th>
|
|
<a href="#" data-bind="orderable: {collection: 'models', field: 'ko_online'}">Online</a>
|
|
</th>
|
|
<th>
|
|
<a href="#" data-bind="orderable: {collection: 'models', field: 'ko_recording'}">Recording</a>
|
|
</th>
|
|
<th></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody data-bind="foreach: models">
|
|
<tr>
|
|
<td>
|
|
<a data-bind="attr: { href: ko_url, title: ko_name }, text: ko_name"></a>
|
|
</td>
|
|
<td>
|
|
<span data-bind="checked: ko_online, class: ko_online() ? `fa fa-check-square checkmark-green` : ``" style="font-size: 2em"></span>
|
|
</td>
|
|
<td>
|
|
<span data-bind="checked: ko_recording, class: ko_recording() ? `fa fa-circle red` : ``" style="font-size: 2em; width: 40px; display: inline-block; text-align: center;"></span>
|
|
<span data-bind="if: ko_recordUntil && ko_recordUntil() !== undefined && ko_recordUntil() !== null && ko_recordUntil() !== 9000000000000000000">
|
|
<i class="fa fa-clock" title="Scheduled stop time" style="font-size: 2em; color: #ffffff;"></i>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-play" title="Resume recording" data-bind="click: resume, visible: ko_suspended"></button>
|
|
<button class="btn btn-secondary fa fa-pause" title="Pause recording" data-bind="click: suspend, hidden: ko_suspended"></button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-trash" title="Remove model" data-bind="click: stop"></button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section id="recordings" class="tab-pane fade" role="tabpanel" aria-labelledby="recordings-tab">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-4 mx-auto"> Space left: <span data-bind="text: space.text()"></span>
|
|
<span data-bind="text: throughput.text()" style="float:right"></span>
|
|
<span class="progress">
|
|
<span class="progress-bar progress-bar-striped" role="progressbar" aria-valuemin="0" data-bind="attr: {'aria-valuemax': space.total, 'aria-valuenow': space.free},
|
|
style: { width: space.percent() + '%' },
|
|
text: space.percent() + '%'"></span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<p class="lead"></p>
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-hover table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'model.name'}">Model</a>
|
|
</th>
|
|
<th>
|
|
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'startDate'}">Date</a>
|
|
</th>
|
|
<th>
|
|
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'ko_status'}">Status</a>
|
|
</th>
|
|
<th>
|
|
<a href="#" data-bind="orderable: {collection: 'recordings', field: 'sizeInByte'}">Size</a>
|
|
</th>
|
|
<th></th>
|
|
<th></th>
|
|
<th></th>
|
|
<th></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody data-bind="foreach: recordings">
|
|
<tr>
|
|
<td>
|
|
<a data-bind="attr: { href: model.url, title: model.name }, text: model.name"></a>
|
|
</td>
|
|
<td data-bind="text: ko_date"></td>
|
|
<td data-bind="text: ko_status"></td>
|
|
<td data-bind="text: ko_size"></td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-image" data-bind="enable: (ko_status() == 'FINISHED' && ko_contactSheet() !== null), click: show, attr: { title: (ko_status() == 'FINISHED' && ko_contactSheet() !== null) ? 'Show contact sheet' : 'No contact sheet' }"></button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-play" title="Play recording" data-bind="click: play"></button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-download" title="Download recording" data-bind="enable: ko_status() == 'FINISHED' && singleFile, click: download"></button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-recycle" title="Rerun processing" data-bind="enable: (ko_status() == 'WAITING' || ko_status() == 'FAILED' || ko_status() == 'FINISHED'),
|
|
click: rerunProcessing"></button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-secondary fa fa-trash" title="Delete recording" data-bind="enable: (ko_status() == 'FINISHED' || ko_status() == 'WAITING' || ko_status() == 'FAILED'),
|
|
click: deleteRecording"></button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section id="configuration" class="tab-pane fade" role="tabpanel" aria-labelledby="configuration-tab">
|
|
<div class="container">
|
|
<form data-bind="submit: saveConfig">
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<p class="lead"></p>
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-hover table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Parameter</th>
|
|
<th>Value</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody data-bind="foreach: settings">
|
|
<tr>
|
|
<td data-bind="text: name"></td>
|
|
<td>
|
|
<input class="form-control" data-bind="value: ko_value, valueUpdate: 'input'" style="width: 100%" />
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-lg-10 mx-auto">
|
|
<button type="submit" class="btn btn-primary btn-lg btn-block" style="margin-bottom: 2em">Save</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
<!-- Bootstrap core JavaScript -->
|
|
<script src="vendor/jquery/jquery.min.js"></script>
|
|
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
|
<!-- Plugin JavaScript -->
|
|
<script src="vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
|
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
|
<script src="vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
|
<script src="vendor/notify.js/notify.min.js"></script>
|
|
<script src="vendor/cookie_js/dist/cookie.umd.min.js"></script>
|
|
<!-- knockout -->
|
|
<script src="vendor/knockout/knockout-3.5.0.js"></script>
|
|
<script src="vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
|
<!-- Custom scripts for this template -->
|
|
<script src="freelancer.min.js"></script>
|
|
<div id="player-window" class="modal">
|
|
<div class="modal-content">
|
|
<span class="close">×</span>
|
|
<video id="player" preload="none" controls width="100%"></video>
|
|
</div>
|
|
</div>
|
|
<!-- Video Player -->
|
|
<script src="vendor/hls.js/hls.js"></script>
|
|
<script src="modal.js"></script>
|
|
<!-- CryptoJS for HMAc authentication -->
|
|
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
|
<!-- ctbrec stuff -->
|
|
<script src="config.js"></script>
|
|
<script src="recordings.js"></script>
|
|
<script src="models.js"></script>
|
|
<script>
|
|
let cookieJar = cookie.cookie;
|
|
cookieJar.defaults.expires = 365 * 100;
|
|
cookieJar.defaults.secure = true;
|
|
let darkMode = false;
|
|
let onlineModels = [];
|
|
let observableModelsArray = ko.observableArray();
|
|
let observableRecordingsArray = ko.observableArray();
|
|
let observableSettingsArray = ko.observableArray();
|
|
let space = {
|
|
free: ko.observable(0),
|
|
total: ko.observable(0),
|
|
percent: ko.observable(0),
|
|
text: ko.observable('')
|
|
};
|
|
let throughput = {
|
|
bytes: ko.observable(0),
|
|
timeframe: ko.observable(0),
|
|
text: ko.observable('')
|
|
};
|
|
let hmac;
|
|
$(document).ready(function() {
|
|
if (localStorage !== undefined && localStorage.hmac !== undefined) {
|
|
if (console) console.log('using hmac from local storage');
|
|
hmac = localStorage.hmac;
|
|
} else {
|
|
if (console) console.log('hmac not found in local storage. requesting hmac from server');
|
|
const abortController = new AbortController();
|
|
const timeoutId = setTimeout(() => abortController.abort(), 60000); // 60 seconds timeout
|
|
fetch('../secured/hmac', {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json'
|
|
},
|
|
signal: abortController.signal
|
|
}).then(response => {
|
|
clearTimeout(timeoutId); // Clear timeout on success
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}).then(data => {
|
|
hmac = data.hmac;
|
|
if (localStorage !== undefined) {
|
|
if (console) console.log('saving hmac to local storage');
|
|
localStorage.setItem("hmac", hmac);
|
|
}
|
|
}).catch(error => {
|
|
clearTimeout(timeoutId); // Clear timeout on error
|
|
if (console) console.log('Request failed:', error);
|
|
$.notify('Could not get HMAC', 'error');
|
|
hmac = '';
|
|
});
|
|
}
|
|
|
|
function tab(id) {
|
|
$(id).css('display: block');
|
|
}
|
|
var navMain = $("#mainNav");
|
|
navMain.on("click", "a", null, function() {
|
|
navMain.collapse('hide');
|
|
$('#navbarResponsive').collapse('hide');
|
|
});
|
|
ko.applyBindings({
|
|
models: observableModelsArray,
|
|
recordings: observableRecordingsArray,
|
|
settings: observableSettingsArray,
|
|
space,
|
|
throughput
|
|
});
|
|
// Required for contact sheets
|
|
window.onload = function() {
|
|
loadConfig();
|
|
};
|
|
updateOnlineModels();
|
|
updateRecordings();
|
|
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
|
|
let selectedTab = e.target.attributes['id'].value;
|
|
if (selectedTab === 'configuration-tab') {
|
|
loadConfig();
|
|
}
|
|
});
|
|
loadConfig();
|
|
$('#dark-mode-toggle').click(toggleDarkMode);
|
|
if (cookieJar.get('darkMode') === 'true') {
|
|
toggleDarkMode();
|
|
}
|
|
// Toggle visibility of stopAt options
|
|
$('#toggleStopAtOptions').click(function() {
|
|
$('#stopAtOptionsRow').toggle();
|
|
// Set default datetime to current time + 1 hour when showing options
|
|
if ($('#stopAtOptionsRow').is(':visible') && !$('#recordUntilDate').val()) {
|
|
let now = new Date();
|
|
now.setHours(now.getHours() + 1);
|
|
// Format the date for datetime-local input
|
|
let year = now.getFullYear();
|
|
let month = (now.getMonth() + 1).toString().padStart(2, '0');
|
|
let day = now.getDate().toString().padStart(2, '0');
|
|
let hours = now.getHours().toString().padStart(2, '0');
|
|
let minutes = now.getMinutes().toString().padStart(2, '0');
|
|
$('#recordUntilDate').val(`${year}-${month}-${day}T${hours}:${minutes}`);
|
|
}
|
|
});
|
|
});
|
|
// Function to add model (shared by Enter key and Add button)
|
|
function addModel() {
|
|
let val = $('#addModelByUrl').val();
|
|
if (!val) return; // Don't do anything if the input is empty
|
|
// Store if we need to add stopAt parameters
|
|
let useStopAt = $('#stopAtOptionsRow').is(':visible') && $('#recordUntilDate').val();
|
|
let stopAtDate = useStopAt ? $('#recordUntilDate').val() : null;
|
|
let stopAtAction = useStopAt ? $('#recordUntilAction').val() : null;
|
|
let modelInput = val;
|
|
// Step 1: Add the model using the standard approach
|
|
add(val, undefined, function() {
|
|
console.log("Model added successfully");
|
|
$('#addModelByUrl').val('');
|
|
if (useStopAt) {
|
|
// Step 2: After a delay, set stopAt parameters
|
|
setTimeout(function() {
|
|
// Extract model name from the input
|
|
let modelName = modelInput;
|
|
if (modelName.includes(':')) {
|
|
modelName = modelName.split(':')[1];
|
|
} else if (modelName.includes('/')) {
|
|
modelName = modelName.split('/').filter(Boolean).pop();
|
|
}
|
|
console.log("Looking for model: " + modelName);
|
|
console.log("Models in array:", observableModelsArray());
|
|
// Try to find the model in the observableModelsArray
|
|
let existingModel = null;
|
|
// Try various ways to find the model
|
|
for (let i = 0; i < observableModelsArray().length; i++) {
|
|
let model = observableModelsArray()[i];
|
|
if (model.name === modelName) {
|
|
existingModel = model;
|
|
break;
|
|
}
|
|
if (model.ko_name && typeof model.ko_name === 'function' && model.ko_name() === modelName) {
|
|
existingModel = model;
|
|
break;
|
|
}
|
|
// Check if URL contains the model name
|
|
if (model.url && model.url.includes(modelName)) {
|
|
existingModel = model;
|
|
break;
|
|
}
|
|
}
|
|
if (!existingModel) {
|
|
console.log("Could not find the model. Creating a simple model object instead.");
|
|
// If we can't find the model, create a simple object with just the necessary properties
|
|
existingModel = {
|
|
name: modelName
|
|
};
|
|
}
|
|
// Add stopAt parameters to the model
|
|
// Always use milliseconds for recordUntil
|
|
let dateTimestamp = new Date(stopAtDate).getTime();
|
|
existingModel.recordUntil = dateTimestamp;
|
|
existingModel.recordUntilSubsequentAction = stopAtAction;
|
|
console.log("Using milliseconds for recordUntil. Value: " + existingModel.recordUntil);
|
|
console.log("Model with stopAt parameters:", existingModel);
|
|
// Send the stopAt action
|
|
let stopAtMsg = '{"action": "stopAt", "model": ' + JSON.stringify(existingModel) + '}';
|
|
const abortController = new AbortController();
|
|
const timeoutId = setTimeout(() => abortController.abort(), 60000); // 60 seconds timeout
|
|
fetch('../rec', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'CTBREC-HMAC': CryptoJS.HmacSHA256(stopAtMsg, hmac)
|
|
},
|
|
body: stopAtMsg,
|
|
signal: abortController.signal
|
|
}).then(response => {
|
|
clearTimeout(timeoutId); // Clear timeout on success
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}).then(data => {
|
|
console.log("StopAt response:", data);
|
|
if (data.status === 'success') {
|
|
// Use simple date formatting
|
|
let date = new Date(stopAtDate).toLocaleString();
|
|
$.notify('Recording will stop at ' + date, 'info');
|
|
// Reset the datetime input and hide the options
|
|
$('#recordUntilDate').val('');
|
|
$('#stopAtOptionsRow').hide();
|
|
} else {
|
|
$.notify('Setting stopAt parameters failed', 'error');
|
|
}
|
|
}).catch(error => {
|
|
clearTimeout(timeoutId); // Clear timeout on error
|
|
console.log('Request failed:', error);
|
|
$.notify('Setting stopAt parameters failed', 'error');
|
|
});
|
|
}, 3000); // Wait 3 seconds to make sure the model is registered
|
|
}
|
|
});
|
|
}
|
|
|
|
function addModelKeyPressed(e) {
|
|
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
|
|
if (charCode === 13) {
|
|
addModel();
|
|
} else {
|
|
$('#addModelByUrl').autocomplete({
|
|
source: ["BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "MyFreeCams:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
|
|
});
|
|
}
|
|
}
|
|
|
|
function toggleDarkMode() {
|
|
darkMode = !darkMode;
|
|
var newCss = document.createElement("link");
|
|
newCss.setAttribute("rel", "stylesheet");
|
|
newCss.setAttribute("type", "text/css");
|
|
newCss.setAttribute("href", darkMode ? "freelancer-dark.css" : "freelancer.css");
|
|
console.log(darkMode, newCss);
|
|
$('#mainTheme').remove();
|
|
$('head').append(newCss);
|
|
$('#dark-mode-toggle').removeClass('fa-moon');
|
|
$('#dark-mode-toggle').removeClass('fa-sun');
|
|
$('#dark-mode-toggle').addClass(darkMode ? 'fa-sun' : 'fa-moon');
|
|
cookieJar.set("darkMode", darkMode);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |