initial commit
This commit is contained in:
739
scripts/pictureViewer.js
Normal file
739
scripts/pictureViewer.js
Normal file
@ -0,0 +1,739 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
formatDates();
|
||||
getPostTexts();
|
||||
getUserDetails();
|
||||
handleAvatarHover();
|
||||
initFileUpload();
|
||||
setupUploadPostPreview();
|
||||
});
|
||||
|
||||
function formatDates() {
|
||||
let timestampElements = document.querySelectorAll('.postdate[ts]');
|
||||
timestampElements.forEach(function(el) {
|
||||
let d = new Date(el.attributes['ts'].value * 1000);
|
||||
el.innerText = d.toLocaleString();
|
||||
});
|
||||
}
|
||||
|
||||
function getPostTexts() {
|
||||
let boxes = document.querySelectorAll('.post-box');
|
||||
// TODO: Implement multiple fetches with one call
|
||||
boxes.forEach(function(el) {
|
||||
let id = el.querySelector('.post-meta-box').querySelector('span').innerText.replace('#','');
|
||||
get('get_posttext.php?id='+id).then(function(response) {
|
||||
let info;
|
||||
try {
|
||||
info = JSON.parse(response);
|
||||
} catch (error) {
|
||||
showError(`Unknown response from server: ${response}`);
|
||||
return;
|
||||
}
|
||||
let postTextBox = el.querySelector('.post-text-box');
|
||||
let loader = postTextBox.querySelector('.loader');
|
||||
if (loader !== null) {
|
||||
postTextBox.removeChild(loader);
|
||||
}
|
||||
if (info.success) {
|
||||
postTextBox.innerHTML = info.text;
|
||||
} else {
|
||||
if (info.error == 404) {
|
||||
console.log('Post ', id, ' not found');
|
||||
} else {
|
||||
console.log('Error fetching post ', id, 'Error code: ', info.error);
|
||||
}
|
||||
}
|
||||
}, function(error) {
|
||||
console.log('Error fetching post ', id, error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getUserDetails() {
|
||||
let boxes = document.querySelectorAll('.user-box');
|
||||
// TODO: Implement multiple fetches with one call
|
||||
boxes.forEach(function(el) {
|
||||
let userBox = el.querySelector('.username');
|
||||
if (userBox == null) {
|
||||
userBox = el.querySelector('span');
|
||||
}
|
||||
let id = userBox.innerText;
|
||||
get('get_userdetails.php?id='+id).then(function(response) {
|
||||
let info;
|
||||
try {
|
||||
info = JSON.parse(response);
|
||||
} catch (error) {
|
||||
showError(`Unknown response from server: ${response}`);
|
||||
return;
|
||||
}
|
||||
let loader = userBox.querySelector('.loader-small');
|
||||
if (loader !== null) {
|
||||
userBox.removeChild(loader);
|
||||
}
|
||||
if (info.success) {
|
||||
if (info.name != null) {
|
||||
el.innerHTML = '<span>' + info.name + '</span><span class="username">' + id + '</span>';
|
||||
}
|
||||
let presenceIndicator = document.createElement('div');
|
||||
presenceIndicator.classList.add('presence-indicator');
|
||||
switch (info.presence) {
|
||||
case 0:
|
||||
presenceIndicator.style.borderColor = "red";
|
||||
presenceIndicator.style.setProperty('border-color', 'var(--red)');
|
||||
break;
|
||||
case 1:
|
||||
presenceIndicator.style.borderColor = "green";
|
||||
presenceIndicator.style.setProperty('border-color', 'var(--green)');
|
||||
break;
|
||||
}
|
||||
el.appendChild(presenceIndicator);
|
||||
} else {
|
||||
if (info.error == 404) {
|
||||
console.log('User ', id, ' not found');
|
||||
} else {
|
||||
console.log('Error fetching user ', id, 'Error code: ', info.error);
|
||||
}
|
||||
}
|
||||
}, function(error) {
|
||||
console.log('Error fetching user ', id, error);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function handleAvatarHover() {
|
||||
let avatars = document.querySelectorAll('img.avatar');
|
||||
avatars.forEach(function(el) {
|
||||
el.addEventListener('mouseenter', function() {
|
||||
// Blur non hovered
|
||||
let nonHovered = document.querySelectorAll('img.avatar:not(:hover)');
|
||||
nonHovered.forEach(function(img) {
|
||||
img.classList.add('blur');
|
||||
//img.classList.remove('unblur');
|
||||
//img.style.filter = "blur(5px)";
|
||||
});
|
||||
});
|
||||
el.addEventListener('mouseleave', function() {
|
||||
// Unblur
|
||||
let avtrs = document.querySelectorAll('img.avatar');
|
||||
avtrs.forEach(function(img) {
|
||||
img.classList.remove('blur');
|
||||
//img.classList.add('unblur');
|
||||
//img.style.filter = "blur(0px)";
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showLoginForm(authUrl) {
|
||||
const authDiv = document.getElementById("authorizeDiv");
|
||||
authDiv.classList.remove("hidden");
|
||||
authDiv.querySelector("a").href = authUrl;
|
||||
}
|
||||
|
||||
function initUploadForm(formId) {
|
||||
document.querySelectorAll('.upload-form').forEach(function(el) {
|
||||
el.style.display = "none";
|
||||
});
|
||||
const form = document.getElementById(formId);
|
||||
form.style.display = form.offsetHeight == 0 ? "inherit" : "none";
|
||||
form.reset();
|
||||
removeImage();
|
||||
document.getElementById('uploadProgress').style.display = "none";
|
||||
}
|
||||
|
||||
function showAvatarResetDate(e) {
|
||||
const val = e.valueAsNumber || parseInt(e.value) || 3;
|
||||
let d = new Date();
|
||||
d.setDate(d.getDate() + val);
|
||||
e.parentElement.querySelector('span').innerText = d.toLocaleDateString();
|
||||
}
|
||||
|
||||
function checkLoginStatus() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('GET', 'set_temp_avatar.php?status');
|
||||
|
||||
req.onload = function() {
|
||||
// This is called even on 404 etc
|
||||
// so check the status
|
||||
console.log("onload", req);
|
||||
if (req.status == 200) {
|
||||
// Resolve the promise with the response text
|
||||
//resolve(req.response);
|
||||
let result = req.response;
|
||||
if (result.length < 1) {
|
||||
var error = new Error("No response");
|
||||
error.name = "NoResponse";
|
||||
reject(error);
|
||||
}
|
||||
try {
|
||||
result = JSON.parse(result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(result);
|
||||
} else {
|
||||
// Otherwise reject with the status text
|
||||
// which will hopefully be a meaningful error
|
||||
reject(Error(req.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle network errors
|
||||
req.onerror = function() {
|
||||
console.error("Network error");
|
||||
reject(Error('Network Error'));
|
||||
};
|
||||
// Make the request
|
||||
req.send();
|
||||
});
|
||||
}
|
||||
|
||||
function transitionEndEventName () {
|
||||
var i,
|
||||
undefined,
|
||||
el = document.createElement('div'),
|
||||
transitions = {
|
||||
'transition':'transitionend',
|
||||
'OTransition':'otransitionend', // oTransitionEnd in very old Opera
|
||||
'MozTransition':'transitionend',
|
||||
'WebkitTransition':'webkitTransitionEnd'
|
||||
};
|
||||
|
||||
for (i in transitions) {
|
||||
if (transitions.hasOwnProperty(i) && el.style[i] !== undefined) {
|
||||
return transitions[i];
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: throw 'TransitionEnd event is not supported in this browser';
|
||||
}
|
||||
|
||||
function initFileUpload() {
|
||||
document.getElementById('showUploadForm').addEventListener('click', e => {
|
||||
initUploadForm('manuallyAddAvatarForm');
|
||||
});
|
||||
document.getElementById('addTempAvatar').addEventListener('click', e => {
|
||||
document.getElementById('loadingLoginStatus').classList.remove('hidden');
|
||||
checkLoginStatus().then(result => {
|
||||
document.getElementById('loadingLoginStatus').classList.add('hidden');
|
||||
console.log("Authentication status: ", result, !!result.authenticated);
|
||||
if(result.error) {
|
||||
console.error(`Error checking authentication status: ${result.error.message}`);
|
||||
return;
|
||||
}
|
||||
if (!!result.authenticated) {
|
||||
initUploadForm('uploadTempAvatar');
|
||||
document.getElementById('uploadTempAvatar').querySelector('textarea').maxlength = result.maxPostLength;
|
||||
} else {
|
||||
showLoginForm(result.authUrl);
|
||||
}
|
||||
}, error => {
|
||||
console.error("Error checking authentication status", error);
|
||||
});
|
||||
});
|
||||
document.getElementById('cbShouldPostAvatar').addEventListener('click', e => {
|
||||
document.getElementById('uploadTempAvatar').querySelector('.posttext').style.display = e.target.checked ? 'inherit' : 'none';
|
||||
});
|
||||
document.querySelectorAll('.posttext').forEach(el => {
|
||||
el.addEventListener('focus', e => {
|
||||
if (e.target.selectionStart == e.target.selectionEnd && !document.firstTextboxFocus) {
|
||||
document.firstTextboxFocus = true;
|
||||
setTimeout(function() {
|
||||
console.log("Selecting start", e, e.target);
|
||||
e.target.selectionStart = 0;
|
||||
e.target.selectionEnd = 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
const durationInput = document.querySelector('#uploadTempAvatar input[name=avatarDuration]');
|
||||
showAvatarResetDate(durationInput);
|
||||
durationInput.addEventListener('change', e => {
|
||||
showAvatarResetDate(e.target);
|
||||
});
|
||||
var isAdvancedUpload = function() {
|
||||
var div = document.createElement('div');
|
||||
return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window;
|
||||
}();
|
||||
if (isAdvancedUpload) {
|
||||
window.droppedFile = false;
|
||||
const dragOverEvents = ['dragover', 'dragenter'];
|
||||
const dragEndEvents = ['dragleave', 'dragend', 'drop'];
|
||||
const events = dragOverEvents.concat(dragEndEvents).concat(['drag', 'dragstart']);
|
||||
|
||||
const zone = document.querySelectorAll('.file-upload').forEach(function(form) {
|
||||
form.classList.add('advanced-upload');
|
||||
const fileSelect = form.querySelector('input[type=file]');
|
||||
events.forEach(event => {
|
||||
form.addEventListener(event, e => {
|
||||
// preventing the unwanted behaviours
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
dragOverEvents.forEach(event => {
|
||||
form.addEventListener(event, e => {
|
||||
form.classList.add('is-dragover');
|
||||
});
|
||||
});
|
||||
dragEndEvents.forEach(event => {
|
||||
form.addEventListener(event, e => {
|
||||
form.classList.remove('is-dragover');
|
||||
});
|
||||
});
|
||||
form.addEventListener('drop', e => {
|
||||
imageAdded(e.dataTransfer.files, e);
|
||||
});
|
||||
fileSelect.addEventListener('change', e => {
|
||||
imageAdded(e.target.files, e);
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log(document.querySelectorAll('.removeImage'));
|
||||
document.querySelectorAll('.removeImage').forEach(function(el) {
|
||||
el.style.zIndex = 1000;
|
||||
el.addEventListener('click', e => {
|
||||
removeImage();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function removeImage(e) {
|
||||
console.log(e);
|
||||
window.droppedFile = null;
|
||||
//document.getElementById('removeImage').style.display = "none";
|
||||
document.querySelectorAll('.removeImage').forEach(function(el) {
|
||||
el.style.display = "none";
|
||||
});
|
||||
document.querySelectorAll('.avatarPreview').forEach(function(el) {
|
||||
window.URL.revokeObjectURL(el.src);
|
||||
el.style.display = "none";
|
||||
el.src = "";
|
||||
});
|
||||
}
|
||||
|
||||
function handleManualAvatarUpload(e) {
|
||||
startUpload(e).then(result => {
|
||||
notify("Avatar added successfully", {type: "success"});
|
||||
showNewlyAddedPicture(result);
|
||||
e.style.display = "none";
|
||||
}, error => {
|
||||
notify(`Error saving avatar: ${error.message}`, {type: "error"});
|
||||
e.style.display = "none";
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleTempAvatarUpload(e) {
|
||||
startUpload(e).then(result => {
|
||||
if (result.warn) {
|
||||
notify(result.warn, {type: "warn"});
|
||||
} else {
|
||||
notify("Your temporary theme avatar has been saved succesfully", {type: "success"});
|
||||
}
|
||||
e.style.display = "none";
|
||||
}, error => {
|
||||
console.error(error);
|
||||
notify(`Error saving avatar: ${error.message}`, {type: "error"});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function startUpload(e) {
|
||||
console.log(window.droppedFile, e);
|
||||
const progress = e.querySelector('.uploadProgress');
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
postForm(e, (position, total) => {
|
||||
progress.max = total;
|
||||
progress.value = position;
|
||||
progress.style.display = "inherit";
|
||||
}).then(result => {
|
||||
if (result.length < 1) {
|
||||
reject({'message': 'Received no response from server :( '});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
result = JSON.parse(result);
|
||||
} catch (err) {
|
||||
reject({'message': `Unknown response from server: ${result}`});
|
||||
return;
|
||||
}
|
||||
console.log("Done", result);
|
||||
if (result.error) {
|
||||
reject(result.error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}, error => {
|
||||
console.error(error);
|
||||
let serverError;
|
||||
if (typeof(error) == "object") {
|
||||
serverError = error;
|
||||
} else {
|
||||
try {
|
||||
serverError = JSON.parse(error);
|
||||
} catch (err) {
|
||||
serverError = {message: "Unknown error"};
|
||||
}
|
||||
}
|
||||
reject(serverError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showNewlyAddedPicture(postDetails) {
|
||||
// TODO: Consolidate together with getUserDetails()
|
||||
// TODO: Check if user already has a picture for this theme
|
||||
document.querySelector('.avatar-grid').appendChild(createPostBox(info));
|
||||
}
|
||||
|
||||
function createPostBox(info) {
|
||||
/*
|
||||
* info.succes: true (hopefully)
|
||||
* info.error: Error object, containing "code" and "message"
|
||||
* info.src: Avatar image
|
||||
* info.presence: -1: gray, 0: red, 1: green
|
||||
* info.user: @username. Might be null or empty string
|
||||
* info.realname: Realname. Might be null or empty string
|
||||
* info.posttext: HTML or pure text content of the post
|
||||
* info.postid: ID of the post
|
||||
* info.timestamp: timestamp of the post
|
||||
*/
|
||||
console.log(info);
|
||||
const grid = document.querySelector('.avatar-grid');
|
||||
let postBox = null;
|
||||
// If there is already oen in the grid, clone that
|
||||
if (grid.querySelectorAll(".avatar-box").length) {
|
||||
postBox = grid.children[grid.children.length - 1].cloneNode(true);
|
||||
} else {
|
||||
console.error("No images in grid yet. Not fully implemented. TODO");
|
||||
}
|
||||
|
||||
// New image
|
||||
postBox.querySelector('.avatar').src = info.img;
|
||||
|
||||
// User info
|
||||
const userDetails = postBox.querySelector('.user-box');
|
||||
|
||||
if (info.realname != null) {
|
||||
userDetails.innerHTML = '<span>' + info.realname + '</span><span class="username">' + info.user + '</span>';
|
||||
} else {
|
||||
userDetails.innerHTML = '<span>' + info.user + '</span>';
|
||||
}
|
||||
/*
|
||||
console.log(userDetails.outerHTML);
|
||||
let userBox = userDetails.querySelector('.username');
|
||||
if (userBox == null) {
|
||||
userBox = userDetails.querySelector('span');
|
||||
}
|
||||
console.log(userBox.outerHTML);
|
||||
userBox.innerText = info.user;
|
||||
console.log(userBox.outerHTML);
|
||||
|
||||
if (info.realname != null) {
|
||||
userBox.innerHTML = '<span>' + info.realname + '</span><span class="username">' + info.user + '</span>';
|
||||
}*/
|
||||
let presenceIndicator = userDetails.querySelector('.presence-indicator');
|
||||
if (presenceIndicator == null) {
|
||||
presenceIndicator = document.createElement('div');
|
||||
presenceIndicator.classList.add('presence-indicator');
|
||||
userDetails.appendChild(presenceIndicator);
|
||||
}
|
||||
switch (info.presence) {
|
||||
case -1:
|
||||
presenceIndicator.style.borderColor = "gray";
|
||||
presenceIndicator.style.setProperty('border-color', 'var(--gray-dark)');
|
||||
break;
|
||||
case 0:
|
||||
presenceIndicator.style.borderColor = "red";
|
||||
presenceIndicator.style.setProperty('border-color', 'var(--red)');
|
||||
break;
|
||||
case 1:
|
||||
presenceIndicator.style.borderColor = "green";
|
||||
presenceIndicator.style.setProperty('border-color', 'var(--green)');
|
||||
break;
|
||||
}
|
||||
|
||||
// Post info
|
||||
const postTextBox = postBox.querySelector('.post-text-box');
|
||||
postTextBox.innerHTML = info.posttext;
|
||||
|
||||
const postMetaBox = postBox.querySelector('.post-meta-box');
|
||||
const postLink = postMetaBox.querySelector('a');
|
||||
postLink.href = "https://posts.pnut.io/" + info.postid;
|
||||
postLink.innerText = '#' + info.postid;
|
||||
let d = new Date(info.timestamp * 1000);
|
||||
postMetaBox.querySelector('.postdate').innerText = d.toLocaleString();
|
||||
return postBox;
|
||||
}
|
||||
|
||||
function postForm(form, progressCallback) {
|
||||
// Return a new promise.
|
||||
console.log('Form: ', form);
|
||||
return new Promise(function(resolve, reject) {
|
||||
//const promise = new Promise();
|
||||
if (form.id == 'manuallyAddAvatarForm') {
|
||||
const idInput = form.querySelector('[name=postID]');
|
||||
const pID = getPostID(idInput);
|
||||
if (pID == -1) {
|
||||
reject("Invalid post id");
|
||||
} else {
|
||||
idInput.value = pID;
|
||||
}
|
||||
}
|
||||
|
||||
var ajaxData = new FormData(form);
|
||||
if (window.droppedFile) {
|
||||
ajaxData.append(form.querySelector('input[type=file]').getAttribute('name'), window.droppedFile);
|
||||
}
|
||||
const id = getQueries().id;
|
||||
if (!id) {
|
||||
reject("Unknown theme id");
|
||||
} else {
|
||||
ajaxData.append('theme', id);
|
||||
ajaxData.append('submit', 'submit');
|
||||
console.log(ajaxData);
|
||||
// Do the usual XHR stuff
|
||||
let req = new XMLHttpRequest();
|
||||
req.open(form.method, form.action);
|
||||
|
||||
req.onload = function() {
|
||||
// This is called even on 404 etc
|
||||
// so check the status
|
||||
console.log("onload", req);
|
||||
if (req.status == 200) {
|
||||
// Resolve the promise with the response text
|
||||
resolve(req.response);
|
||||
}
|
||||
else {
|
||||
// Otherwise reject with the status text
|
||||
// which will hopefully be a meaningful error
|
||||
reject(Error(req.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle network errors
|
||||
req.onerror = function() {
|
||||
console.error("Network error");
|
||||
reject(Error('Network Error'));
|
||||
};
|
||||
if (progressCallback) {
|
||||
var eventSource = req.upload || req;
|
||||
eventSource.addEventListener("progress", function(e) {
|
||||
// normalize position attributes across XMLHttpRequest versions and browsers
|
||||
var position = e.position || e.loaded;
|
||||
var total = e.totalSize || e.total;
|
||||
progressCallback(position, total);
|
||||
});
|
||||
}
|
||||
|
||||
// Make the request
|
||||
req.send(ajaxData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function imageAdded(arr, evt) {
|
||||
if (!arr.length) {
|
||||
return;
|
||||
}
|
||||
console.log("Dropped image", arr, evt);
|
||||
window.droppedFile = arr[0];
|
||||
const imgSize = window.droppedFile.size;
|
||||
console.log("Size in Bytes", imgSize);
|
||||
const maxSize = parseInt(document.querySelector('input[name=MAX_FILE_SIZE]').value);
|
||||
let errorSpan = document.getElementById('imgTooLarge');
|
||||
if (imgSize > maxSize) {
|
||||
//<span style="display: block;">Image too large</span>
|
||||
if (errorSpan == null) {
|
||||
errorSpan = document.createElement('span');
|
||||
errorSpan.classList.add('imgTooLarge');
|
||||
errorSpan.innerText = `Image is too large. It is ${(imgSize/1024/1024).toFixed(2)}MiB, but should be <= ${(maxSize/1024/1024).toFixed(2)}MiB`;
|
||||
errorSpan.style.display = 'inherit';
|
||||
errorSpan.id = 'imgTooLarge';
|
||||
document.querySelector('#uploadTempAvatar table tr td:nth-child(2)').appendChild(errorSpan);
|
||||
} else {
|
||||
errorSpan.style.display = 'inherit';
|
||||
}
|
||||
} else if (errorSpan != null) {
|
||||
errorSpan.style.display = 'none';
|
||||
}
|
||||
getOrientation(function(e){
|
||||
console.log("Orientation", e);
|
||||
let prv = evt.target.parentElement.querySelector('.avatarPreview');
|
||||
switch (e) {
|
||||
case 1:
|
||||
prv.style.transform = "rotate(0)";
|
||||
break;
|
||||
case 3:
|
||||
prv.style.transform = "rotate(180deg)";
|
||||
break;
|
||||
case 6:
|
||||
prv.style.transform = "rotate(90deg)";
|
||||
break;
|
||||
case 8:
|
||||
prv.style.transform = "rotate(270deg)";
|
||||
break;
|
||||
default:
|
||||
prv.style.transform = "rotate(0)";
|
||||
break;
|
||||
}
|
||||
if (prv.src) {
|
||||
window.URL.revokeObjectURL(prv.src);
|
||||
}
|
||||
prv.style.display = "inherit";
|
||||
prv.src = window.URL.createObjectURL(droppedFile);
|
||||
evt.target.parentElement.querySelector('.removeImage').style.display = "inherit";
|
||||
evt.target.parentElement.querySelector('.removeImage').style.zIndex = 1000;zIndex = 1000;
|
||||
});
|
||||
}
|
||||
|
||||
function getOrientation(callback) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var view = new DataView(e.target.result);
|
||||
const magic = view.getUint16(0, false);
|
||||
if (magic != 0xFFD8) {
|
||||
console.warn("Unknown magic bytes: ", magic.toString(16))
|
||||
return callback(-2);
|
||||
}
|
||||
var length = view.byteLength, offset = 2;
|
||||
console.debug("Length: ", length);
|
||||
while (offset < length) {
|
||||
const firstBytesTest = view.getUint16(offset+2, false);
|
||||
if (firstBytesTest <= 8) {
|
||||
console.warn("firstBytesTest <=8", firstBytesTest);
|
||||
return callback(-1);
|
||||
}
|
||||
var marker = view.getUint16(offset, false);
|
||||
offset += 2;
|
||||
if (marker == 0xFFE1) {
|
||||
console.debug("Found marker");
|
||||
if (view.getUint32(offset += 2, false) != 0x45786966) {
|
||||
console.debug("Invalid sequence following marker: ", view.getUint32(offset += 2, false).toString(16));
|
||||
return callback(-1);
|
||||
}
|
||||
|
||||
var little = view.getUint16(offset += 6, false) == 0x4949;
|
||||
offset += view.getUint32(offset + 4, little);
|
||||
var tags = view.getUint16(offset, little);
|
||||
offset += 2;
|
||||
for (var i = 0; i < tags; i++) {
|
||||
if (view.getUint16(offset + (i * 12), little) == 0x0112) {
|
||||
console.debug("Found rotation tag");
|
||||
return callback(view.getUint16(offset + (i * 12) + 8, little));
|
||||
}
|
||||
}
|
||||
} else if ((marker & 0xFF00) != 0xFF00) {
|
||||
break;
|
||||
} else {
|
||||
offset += view.getUint16(offset, false);
|
||||
}
|
||||
}
|
||||
return callback(-1);
|
||||
};
|
||||
reader.readAsArrayBuffer(window.droppedFile);
|
||||
}
|
||||
|
||||
function setupUploadPostPreview() {
|
||||
const postIdInput = document.querySelector('input[name=postID]');
|
||||
window.lastPostIDKey = 0;
|
||||
postIdInput.onkeyup = function() {
|
||||
window.lastPostIDKey = Date.now();
|
||||
setTimeout(function() {
|
||||
const now = Date.now();
|
||||
const diff = now - window.lastPostIDKey;
|
||||
console.log(diff);
|
||||
if (diff < 500) {
|
||||
return;
|
||||
}
|
||||
const pID = getPostID(postIdInput);
|
||||
get(`get_posttext.php?id=${pID}&avatarWidth=40`).then(function(response) {
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (error) {
|
||||
showError(`Unknown response from server: ${response}`);
|
||||
return;
|
||||
}
|
||||
if (response.error) {
|
||||
showError(`Error loading post #${pID}: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
const tbl = document.querySelector('.upload-form').querySelector('table');
|
||||
let i = tbl.rows[1].cells.length - 1;
|
||||
// Remove existing post boxes
|
||||
while (tbl.rows[1].cells[i].querySelector('.post-box')) {
|
||||
tbl.rows[1].removeChild(tbl.rows[1].cells[i]);
|
||||
i = tbl.rows[1].cells.length - 1;
|
||||
}
|
||||
const cell = tbl.rows[1].insertCell();
|
||||
const postBox = createPostBox(response);
|
||||
const ab = postBox.querySelector('.avatar');
|
||||
ab.style.width = '20px';
|
||||
postBox.removeChild(ab.parentElement);
|
||||
const ub = postBox.querySelector('.post-box').querySelector('.user-box');
|
||||
ub.insertBefore(ab, ub.firstChild);
|
||||
ab.className = "";
|
||||
ab.style.borderRadius = "3px";
|
||||
cell.style.verticalAlign = 'top';
|
||||
cell.appendChild(postBox);
|
||||
// Post box overflows horizontically (long links, etc). Remove set width and let it goooo
|
||||
if (postBox.scrollWidth > postBox.clientWidth) {
|
||||
postBox.style.width = "initial";
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
}
|
||||
|
||||
function getPostID(input) {
|
||||
const parsed = parseInt(input.value);
|
||||
if (isNaN(parsed)) {
|
||||
matches = input.value.match(/(?:https?:\/\/)?((posts.*pnut.*)|.*pnut.*posts)\/(\d+)/);
|
||||
if (!matches || Number.isInteger(matches[matches.length-1])) {
|
||||
return -1;
|
||||
}
|
||||
return parseInt(matches[matches.length-1]);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function showError(message, params = {}) {
|
||||
params.type = 'error';
|
||||
notify(message, params);
|
||||
}
|
||||
|
||||
function notify(message, params) {
|
||||
defaultOptions = {
|
||||
class: 'notif',
|
||||
position: 'top|right',
|
||||
duration: 5,
|
||||
type: 'info'
|
||||
};
|
||||
const mergedOptions = {
|
||||
...defaultOptions,
|
||||
...params,
|
||||
};
|
||||
mergedOptions.duration *= 1000;
|
||||
switch (mergedOptions.type) {
|
||||
case 'success':
|
||||
console.log(message);
|
||||
toast.success(message, mergedOptions);
|
||||
break;
|
||||
case 'warn':
|
||||
console.warn(message);
|
||||
toast.warn(message, mergedOptions);
|
||||
break;
|
||||
case 'error':
|
||||
console.error(message);
|
||||
toast.alert(message, mergedOptions);
|
||||
break;
|
||||
default:
|
||||
console.log(message);
|
||||
toast.message(message, mergedOptions);
|
||||
break;
|
||||
}
|
||||
}
|
222
scripts/roastmonday_common.js
Normal file
222
scripts/roastmonday_common.js
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* JS doesn't support directly declaring objects
|
||||
* with '--' and '-' in their properties.
|
||||
*
|
||||
* Well technically it deos using bracket-notation like this:
|
||||
* foo['--bar'] =42;
|
||||
* But that's not that much better.
|
||||
*
|
||||
* So instead this JSON.parse is used.
|
||||
*/
|
||||
|
||||
window.themes = JSON.parse('{\
|
||||
"white": {\
|
||||
"--main-highlight-color": "#EF940B",\
|
||||
"--main-bg-color": "white", \
|
||||
"--secondary-bg-color": "white", \
|
||||
"--main-text-color": "black",\
|
||||
"--main-link-color": "#106BF4",\
|
||||
"--shadow-color": "#888",\
|
||||
"--gray-light": "#cacaca",\
|
||||
"--gray-medium": "#e0e0e0",\
|
||||
"--gray-dark": "#4c4c4c",\
|
||||
"--red": "red",\
|
||||
"--green": "green"\
|
||||
},\
|
||||
"black": {\
|
||||
"--main-highlight-color": "#EE6E1F",\
|
||||
"--main-link-color": "#1191e0",\
|
||||
"--main-bg-color": "black",\
|
||||
"--secondary-bg-color": "black", \
|
||||
"--main-text-color": "white",\
|
||||
"--shadow-color": "#777",\
|
||||
"--gray-light": "#4c4c4c",\
|
||||
"--gray-medium": "#333",\
|
||||
"--gray-dark": "#cacaca",\
|
||||
"--red": "#ef0000",\
|
||||
"--green": "#00ab00"\
|
||||
},\
|
||||
"ash": {\
|
||||
"--main-highlight-color": "#EE6E1F",\
|
||||
"--main-link-color": "#1191e0",\
|
||||
"--main-bg-color": "#333",\
|
||||
"--secondary-bg-color": "#333", \
|
||||
"--main-text-color": "#d0d0d0",\
|
||||
"--shadow-color": "#656565",\
|
||||
"--gray-light": "#4c4c4c",\
|
||||
"--gray-medium": "#3b3b3b",\
|
||||
"--gray-dark": "#cacaca",\
|
||||
"--red": "#e4053e",\
|
||||
"--green": "#04c446"\
|
||||
},\
|
||||
"midnight": {\
|
||||
"--main-highlight-color": "#1caff6",\
|
||||
"--main-link-color": "#1caff6",\
|
||||
"--main-bg-color": "#111",\
|
||||
"--secondary-bg-color": "#045d89", \
|
||||
"--main-text-color": "#d0d0d0",\
|
||||
"--shadow-color": "#656565",\
|
||||
"--gray-light": "#3f3f3f",\
|
||||
"--gray-medium": "#3b3b3b",\
|
||||
"--gray-dark": "#cacaca",\
|
||||
"--red": "#f72900",\
|
||||
"--green": "#82dd00"\
|
||||
}\
|
||||
}');
|
||||
window.IS_TOUCH_DEVICE = false;
|
||||
window.addEventListener('touchstart', function() {
|
||||
window.IS_TOUCH_DEVICE = true;
|
||||
});
|
||||
|
||||
window.queries = getQueries();
|
||||
setTheme(queries.theme);
|
||||
if (queries.debugTouch) {
|
||||
IS_TOUCH_DEVICE = true;
|
||||
console.log("Touch device mode set");
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
displayThemeChooser();
|
||||
});
|
||||
|
||||
function setTheme(t, save = true, element = null) {
|
||||
let themeName = 'white';
|
||||
if (!!t && (t in themes)) {
|
||||
themeName = t;
|
||||
} else {
|
||||
const ls = localStorage.getItem('theme');
|
||||
if (!!ls && (ls in themes)) {
|
||||
themeName = ls;
|
||||
} else {
|
||||
save = false;
|
||||
// Use default. Switch to ash if dark mode and don't save
|
||||
if (matchMedia("(prefers-color-scheme: dark)")) {
|
||||
themeName = 'ash';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (save) {
|
||||
localStorage.setItem('theme', themeName);
|
||||
}
|
||||
const theme = themes[themeName];
|
||||
const doc = element || document.querySelector(':root');
|
||||
Object.keys(theme).forEach(varName => {
|
||||
doc.style.setProperty(varName, theme[varName]);
|
||||
});
|
||||
}
|
||||
|
||||
function displayThemeChooser() {
|
||||
const wrapper = document.getElementById('themeChooser');
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
const themeNames = Object.keys(themes);
|
||||
if (themeNames.length < 2) {
|
||||
return;
|
||||
}
|
||||
const list = document.createElement('ul');
|
||||
list.classList.add('theme-chooser-list');
|
||||
themeNames.forEach(t => {
|
||||
const link = document.createElement('a');
|
||||
link.style.position = 'relative';
|
||||
link.href = `?theme=${t}`;
|
||||
link.onclick = function (e) {
|
||||
const t = this.href.split('theme=')[1];
|
||||
e.preventDefault();
|
||||
if (!IS_TOUCH_DEVICE) {
|
||||
setTheme(t);
|
||||
return;
|
||||
}
|
||||
const callout = document.querySelector('.theme-preview');
|
||||
if (callout && callout.getAttribute('currentTheme') == t) {
|
||||
// Clicked on already seelcted theme - apply!
|
||||
callout.style.display = "none";
|
||||
document.querySelector('.themelist').style.filter = '';
|
||||
try {
|
||||
let d = document.querySelector('.dimmer').remove();
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
setTheme(t);
|
||||
return;
|
||||
} else {
|
||||
const rect = list.getBoundingClientRect();
|
||||
setTheme(t, false, callout);
|
||||
callout.setAttribute('currentTheme', t);
|
||||
callout.style.display = "inherit";
|
||||
callout.style.bottom = document.querySelector('.theme-chooser-list').getBoundingClientRect().top + "px";
|
||||
console.log(callout, callout.style);
|
||||
document.querySelector('.themelist').style.filter = 'blur(5px)';
|
||||
if (!document.querySelector('.dimmer')) {
|
||||
let dimmer = document.createElement('div');
|
||||
dimmer.classList.add('dimmer');
|
||||
document.body.appendChild(dimmer);
|
||||
}
|
||||
}
|
||||
};
|
||||
link.onmouseenter = function (e) {
|
||||
if (!IS_TOUCH_DEVICE) {
|
||||
const t = this.href.split('theme=')[1];
|
||||
setTheme(t, false);
|
||||
}
|
||||
};
|
||||
link.onmouseleave = function (e) {
|
||||
if (!IS_TOUCH_DEVICE) {
|
||||
setTheme(null, false);
|
||||
}
|
||||
};
|
||||
const container = document.createElement('li');
|
||||
container.classList.add('theme-chooser-list-item');
|
||||
const textContainer = document.createElement('span');
|
||||
link.appendChild(container);
|
||||
textContainer.innerText = t;
|
||||
container.appendChild(textContainer);
|
||||
list.appendChild(link);
|
||||
});
|
||||
wrapper.appendChild(list);
|
||||
}
|
||||
|
||||
function getQueries() {
|
||||
const query = window.location.search.substring(1);
|
||||
const vars = query.split('&');
|
||||
const queries = {};
|
||||
vars.forEach(v => {
|
||||
const pair = v.split('=');
|
||||
if (pair.length > 1) {
|
||||
queries[pair[0]] = decodeURIComponent(pair[1]);
|
||||
} else {
|
||||
queries[pair[0]] = null;
|
||||
}
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
function get(url) {
|
||||
// Return a new promise.
|
||||
return new Promise(function(resolve, reject) {
|
||||
// Do the usual XHR stuff
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('GET', url);
|
||||
|
||||
req.onload = function() {
|
||||
// This is called even on 404 etc
|
||||
// so check the status
|
||||
if (req.status == 200) {
|
||||
// Resolve the promise with the response text
|
||||
resolve(req.response);
|
||||
}
|
||||
else {
|
||||
// Otherwise reject with the status text
|
||||
// which will hopefully be a meaningful error
|
||||
reject(Error(req.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle network errors
|
||||
req.onerror = function() {
|
||||
reject(Error('Network Error'));
|
||||
};
|
||||
|
||||
// Make the request
|
||||
req.send();
|
||||
});
|
||||
}
|
157
scripts/toast.js
Normal file
157
scripts/toast.js
Normal file
@ -0,0 +1,157 @@
|
||||
const setStyles = (el, styles) => {
|
||||
Object.keys(styles).forEach((key) => {
|
||||
el.style[key] = styles[key];
|
||||
});
|
||||
};
|
||||
|
||||
const setAttrs = (el, attrs) => {
|
||||
Object.keys(attrs).forEach((key) => {
|
||||
el.setAttribute(key, attrs[key]);
|
||||
});
|
||||
};
|
||||
|
||||
const getAttr = (el, attr) => el.getAttribute(attr);
|
||||
|
||||
const privateKeys = {
|
||||
defaultOptions: Symbol('defaultOptions'),
|
||||
render: Symbol('render'),
|
||||
show: Symbol('show'),
|
||||
hide: Symbol('hide'),
|
||||
removeDOM: Symbol('removeDOM'),
|
||||
};
|
||||
|
||||
const siiimpleToast = {
|
||||
[privateKeys.defaultOptions]: {
|
||||
container: 'body',
|
||||
class: 'siiimpleToast',
|
||||
position: 'top|center',
|
||||
margin: 15,
|
||||
delay: 0,
|
||||
duration: 3000,
|
||||
style: {},
|
||||
},
|
||||
|
||||
setOptions(options = {}) {
|
||||
return {
|
||||
...siiimpleToast,
|
||||
[privateKeys.defaultOptions]: {
|
||||
...this[privateKeys.defaultOptions],
|
||||
...options,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
[privateKeys.render](state, message, options = {}) {
|
||||
const mergedOptions = {
|
||||
...this[privateKeys.defaultOptions],
|
||||
...options,
|
||||
};
|
||||
|
||||
const {
|
||||
class: className,
|
||||
position,
|
||||
delay,
|
||||
duration,
|
||||
style,
|
||||
} = mergedOptions;
|
||||
|
||||
const newToast = document.createElement('div');
|
||||
|
||||
// logging via attrs
|
||||
newToast.className = className;
|
||||
newToast.innerHTML = message;
|
||||
|
||||
setAttrs(newToast, {
|
||||
'data-position': position,
|
||||
'data-state': state,
|
||||
});
|
||||
|
||||
setStyles(newToast, style);
|
||||
|
||||
// use .setTimeout() instead of $.queue()
|
||||
let time = 0;
|
||||
setTimeout(() => {
|
||||
this[privateKeys.show](newToast, mergedOptions);
|
||||
}, time += delay);
|
||||
setTimeout(() => {
|
||||
this[privateKeys.hide](newToast, mergedOptions);
|
||||
}, time += duration);
|
||||
|
||||
// support method chaining
|
||||
return this;
|
||||
},
|
||||
|
||||
[privateKeys.show](el, { container, class: className, margin }) {
|
||||
const hasPos = (v, pos) => getAttr(v, 'data-position').indexOf(pos) > -1;
|
||||
|
||||
const root = document.querySelector(container);
|
||||
root.insertBefore(el, root.firstChild);
|
||||
|
||||
// set initial position
|
||||
setStyles(el, {
|
||||
position: container === 'body' ? 'fixed' : 'absolute',
|
||||
[hasPos(el, 'top') ? 'top' : 'bottom']: '-100px',
|
||||
[hasPos(el, 'left') && 'left']: '15px',
|
||||
[hasPos(el, 'center') && 'left']: `${(root.clientWidth / 2) - (el.clientWidth / 2)}px`,
|
||||
[hasPos(el, 'right') && 'right']: '15px',
|
||||
});
|
||||
|
||||
setStyles(el, {
|
||||
transform: 'scale(1)',
|
||||
opacity: 1,
|
||||
});
|
||||
|
||||
// push effect
|
||||
let pushStack = margin;
|
||||
|
||||
Array
|
||||
.from(document.querySelectorAll(`.${className}[data-position="${getAttr(el, 'data-position')}"]`))
|
||||
.filter(toast => toast.parentElement === el.parentElement)// matching container
|
||||
.forEach((toast) => {
|
||||
setStyles(toast, {
|
||||
[hasPos(toast, 'top') ? 'top' : 'bottom']: `${pushStack}px`,
|
||||
});
|
||||
|
||||
pushStack += toast.offsetHeight + margin;
|
||||
});
|
||||
},
|
||||
|
||||
[privateKeys.hide](el) {
|
||||
const hasPos = (v, pos) => getAttr(v, 'data-position').indexOf(pos) > -1;
|
||||
const { left, width } = el.getBoundingClientRect();
|
||||
|
||||
setStyles(el, {
|
||||
[hasPos(el, 'left') && 'left']: `${width}px`,
|
||||
[hasPos(el, 'center') && 'left']: `${left + width}px`,
|
||||
[hasPos(el, 'right') && 'right']: `-${width}px`,
|
||||
opacity: 0,
|
||||
});
|
||||
|
||||
const whenTransitionEnd = () => {
|
||||
this[privateKeys.removeDOM](el);
|
||||
el.removeEventListener('transitionend', whenTransitionEnd);
|
||||
};
|
||||
|
||||
el.addEventListener('transitionend', whenTransitionEnd);
|
||||
},
|
||||
|
||||
[privateKeys.removeDOM](el) {// eslint-disable-line
|
||||
const parent = el.parentElement;
|
||||
parent.removeChild(el);
|
||||
},
|
||||
|
||||
message(message, options) {
|
||||
return this[privateKeys.render]('default', message, options);
|
||||
},
|
||||
success(message, options) {
|
||||
return this[privateKeys.render]('success', message, options);
|
||||
},
|
||||
warn(message, options) {
|
||||
return this[privateKeys.render]('warn', message, options);
|
||||
},
|
||||
alert(message, options) {
|
||||
return this[privateKeys.render]('alert', message, options);
|
||||
}
|
||||
};
|
||||
|
||||
window.toast = siiimpleToast;
|
Reference in New Issue
Block a user